Last active
October 24, 2025 14:32
-
-
Save arenagroove/95d0fff37a0dbe400eb04aa5f2f36789 to your computer and use it in GitHub Desktop.
Toggleable hard lock for WordPress core, plugin, and theme updates. Includes Tools screen, admin bar toggle, REST protection, and WP-CLI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Plugin Name: LR Lock Updates | |
| * Description: Toggleable hard lock for WordPress core, plugin, and theme updates. Includes Tools screen, admin bar toggle, REST protection, and WP-CLI. | |
| * Author: Less Rain | |
| * Author URI: https://www.lessrain.com | |
| * Version: 2.2.0 | |
| * Network: true | |
| * Text Domain: lr-lock-updates | |
| * Domain Path: /languages | |
| * Requires PHP: 7.4 | |
| */ | |
| if (!defined('ABSPATH')) { | |
| exit; | |
| } | |
| /** | |
| * CONFIGURATION CONSTANTS (add to wp-config.php if needed) | |
| * | |
| * // Emergency unlock (bypasses all lock checks and UI state) | |
| * define('LR_LOCK_EMERGENCY_UNLOCK', true); | |
| * | |
| * // Force default state on new installs (overrides DEFAULT_STATE constant) | |
| * define('LR_LOCK_FORCE_DEFAULT', true); // or false | |
| */ | |
| final class LR_Lock_Updates { | |
| const VERSION = '2.2.0'; | |
| const DEFAULT_STATE = false; | |
| const FILTER_PRIORITY = PHP_INT_MAX; | |
| const OPT_ENABLED = 'lr_lock_updates_enabled'; | |
| const OPT_PARTS = 'lr_lock_updates_parts'; | |
| const OPT_LOG = 'lr_lock_updates_log'; | |
| const MAX_LOG_ENTRIES = 50; | |
| const DISPLAYED_LOG_ENTRIES = 10; | |
| private static $instance = null; | |
| public static function instance() { | |
| if (self::$instance === null) { | |
| self::$instance = new self(); | |
| } | |
| return self::$instance; | |
| } | |
| private function __construct() { | |
| add_action('init', [$this, 'load_textdomain']); | |
| if ($this->is_emergency_unlocked()) { | |
| // Emergency mode: force unlock via filters and skip all protection hooks | |
| add_filter('pre_option_' . self::OPT_ENABLED, '__return_zero', self::FILTER_PRIORITY); | |
| add_filter('pre_site_option_' . self::OPT_ENABLED, '__return_zero', self::FILTER_PRIORITY); | |
| add_action('cli_init', [$this, 'register_cli']); | |
| return; | |
| } | |
| $this->init_hooks(); | |
| } | |
| private function __clone() {} | |
| public function __wakeup() {} | |
| public function load_textdomain(): void { | |
| load_muplugin_textdomain('lr-lock-updates', dirname(plugin_basename(__FILE__)) . '/languages'); | |
| } | |
| private function init_hooks(): void { | |
| if ($this->is_enabled()) { | |
| $this->apply_protections(); | |
| } | |
| add_action('admin_bar_menu', [$this, 'admin_bar_toggle'], 100); | |
| add_action('admin_init', [$this, 'handle_toggle']); | |
| add_action('admin_init', [$this, 'export_log_csv'], 1); | |
| add_action('admin_menu', [$this, 'add_tools_page']); | |
| add_action('network_admin_menu', [$this, 'add_network_tools_page']); | |
| add_action('admin_post_lr_lock_save', [$this, 'handle_save']); | |
| add_filter('admin_footer_text', [$this, 'footer_text']); | |
| add_filter('mu_plugin_action_links', [$this, 'action_links'], 10, 2); | |
| add_action('cli_init', [$this, 'register_cli']); | |
| } | |
| /** | |
| * Check if emergency unlock constant is active. | |
| * | |
| * @return bool | |
| */ | |
| private function is_emergency_unlocked(): bool { | |
| return defined('LR_LOCK_EMERGENCY_UNLOCK') && LR_LOCK_EMERGENCY_UNLOCK === true; | |
| } | |
| /** | |
| * Apply comprehensive update protections based on enabled parts. | |
| * | |
| * @return void | |
| */ | |
| private function apply_protections(): void { | |
| $parts = $this->get_parts(); | |
| add_filter('file_mod_allowed', [$this, 'filter_file_mods'], 10, 2); | |
| if (!defined('DISALLOW_FILE_EDIT')) { | |
| define('DISALLOW_FILE_EDIT', true); | |
| } | |
| add_filter('automatic_updater_disabled', '__return_true', self::FILTER_PRIORITY); | |
| if ($parts['core']) { | |
| add_filter('allow_dev_auto_core_updates', '__return_false', self::FILTER_PRIORITY); | |
| add_filter('allow_minor_auto_core_updates', '__return_false', self::FILTER_PRIORITY); | |
| add_filter('allow_major_auto_core_updates', '__return_false', self::FILTER_PRIORITY); | |
| add_filter('auto_update_core', '__return_false', self::FILTER_PRIORITY); | |
| // Return empty update object to prevent core update checks from triggering | |
| add_filter('pre_site_transient_update_core', function () { | |
| return (object) [ | |
| 'last_checked' => time(), | |
| 'version_checked' => get_bloginfo('version'), | |
| 'updates' => [], | |
| 'translations' => [], | |
| 'locale' => get_locale(), | |
| ]; | |
| }, self::FILTER_PRIORITY); | |
| } | |
| if ($parts['plugins']) { | |
| add_filter('auto_update_plugin', '__return_false', self::FILTER_PRIORITY); | |
| add_filter('pre_site_transient_update_plugins', '__return_null', self::FILTER_PRIORITY); | |
| } | |
| if ($parts['themes']) { | |
| add_filter('auto_update_theme', '__return_false', self::FILTER_PRIORITY); | |
| add_filter('pre_site_transient_update_themes', '__return_null', self::FILTER_PRIORITY); | |
| } | |
| // Clear scheduled update checks for locked parts | |
| add_action('init', function () use ($parts) { | |
| if ($parts['core']) { | |
| wp_clear_scheduled_hook('wp_version_check'); | |
| remove_action('wp_maybe_auto_update', 'wp_maybe_auto_update'); | |
| } | |
| if ($parts['plugins']) { | |
| wp_clear_scheduled_hook('wp_update_plugins'); | |
| } | |
| if ($parts['themes']) { | |
| wp_clear_scheduled_hook('wp_update_themes'); | |
| } | |
| }, 1); | |
| add_action('admin_head', [$this, 'hide_update_ui']); | |
| add_action('admin_notices', [$this, 'admin_notice']); | |
| add_action('network_admin_notices', [$this, 'admin_notice']); | |
| add_action('muplugins_loaded', [$this, 'clear_transients']); | |
| add_filter('rest_pre_dispatch', [$this, 'protect_rest_api'], 10, 3); | |
| } | |
| /** | |
| * Block file modifications for update-related contexts. | |
| * | |
| * Note: $context can be null in some WordPress core contexts, so no type hint. | |
| * | |
| * @param bool $allowed Current allowed state. | |
| * @param string|null $context File modification context. | |
| * @return bool | |
| */ | |
| public function filter_file_mods($allowed, $context) { | |
| $blocked = ['update-core', 'plugin', 'theme', 'install']; | |
| return in_array($context, $blocked, true) ? false : $allowed; | |
| } | |
| /** | |
| * Hide update UI elements only for locked parts. | |
| * | |
| * Critical: Do NOT hide generic ".update-plugins" or admin menu counts will | |
| * disappear even when plugins/themes are unlocked. | |
| * | |
| * @return void | |
| */ | |
| public function hide_update_ui(): void { | |
| $p = $this->get_parts(); | |
| $rules = []; | |
| if ($p['core']) { | |
| $rules[] = '.update-nag'; | |
| $rules[] = '#update-nag'; | |
| } | |
| if ($p['plugins']) { | |
| $rules[] = '.plugin-update-tr'; | |
| $rules[] = '#menu-plugins .update-plugins'; | |
| } | |
| if ($p['themes']) { | |
| $rules[] = '.theme-update'; | |
| $rules[] = '#menu-appearance .update-plugins'; | |
| } | |
| if (empty($rules)) { | |
| return; | |
| } | |
| // Hardcoded selectors, no user input - no escaping needed | |
| echo '<style>' . implode(',', $rules) . '{display:none !important;}</style>'; | |
| } | |
| public function admin_notice(): void { | |
| if (!$this->current_user_can_manage()) { | |
| return; | |
| } | |
| if (!function_exists('get_current_screen')) { | |
| return; | |
| } | |
| $screen = get_current_screen(); | |
| if (!$screen) { | |
| return; | |
| } | |
| $allowed = ['dashboard', 'plugins', 'themes', 'update-core', 'tools_page_lr-lock-updates', 'settings_page_lr-lock-updates-network']; | |
| if (!in_array($screen->id, $allowed, true)) { | |
| return; | |
| } | |
| $parts = $this->get_parts(); | |
| $locked_parts = array_keys(array_filter($parts)); | |
| if (empty($locked_parts)) { | |
| return; | |
| } | |
| $what = implode(', ', $locked_parts); | |
| echo '<div class="notice notice-warning is-dismissible"><p><strong>' . esc_html__('Updates are locked', 'lr-lock-updates') . '</strong> ' . | |
| sprintf(esc_html__('for %s. Use the admin bar toggle or Tools screen to unlock.', 'lr-lock-updates'), esc_html($what)) . '</p></div>'; | |
| } | |
| public function clear_transients(): void { | |
| $parts = $this->get_parts(); | |
| if ($parts['core']) { | |
| delete_site_transient('update_core'); | |
| } | |
| if ($parts['plugins']) { | |
| delete_site_transient('update_plugins'); | |
| } | |
| if ($parts['themes']) { | |
| delete_site_transient('update_themes'); | |
| } | |
| } | |
| /** | |
| * Protect REST API endpoints for plugin and theme modifications. | |
| * | |
| * @param mixed $result Response to replace the requested version with. | |
| * @param WP_REST_Server $server Server instance. | |
| * @param WP_REST_Request $request Request used to generate the response. | |
| * @return mixed | |
| */ | |
| public function protect_rest_api($result, $server, $request) { | |
| if (!$this->is_enabled()) { | |
| return $result; | |
| } | |
| $route = $request->get_route(); | |
| $method = $request->get_method(); | |
| $parts = $this->get_parts(); | |
| if ($parts['plugins'] && strpos($route, '/wp/v2/plugins') !== false) { | |
| if (in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { | |
| return new WP_Error('lr_lock_active', __('Plugin updates are locked', 'lr-lock-updates'), ['status' => 403]); | |
| } | |
| } | |
| if ($parts['themes'] && strpos($route, '/wp/v2/themes') !== false) { | |
| if (in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { | |
| return new WP_Error('lr_lock_active', __('Theme updates are locked', 'lr-lock-updates'), ['status' => 403]); | |
| } | |
| } | |
| return $result; | |
| } | |
| public function admin_bar_toggle($wp_admin_bar): void { | |
| if (!is_user_logged_in() || !is_admin() || !$this->current_user_can_manage()) { | |
| return; | |
| } | |
| $locked = $this->is_enabled(); | |
| $action = $locked ? 'off' : 'on'; | |
| $url = wp_nonce_url(add_query_arg('lr_lock_toggle', $action), 'lr_lock_toggle'); | |
| $indicator = $locked ? '🔴' : '🟢'; | |
| $title = $locked | |
| ? $indicator . ' ' . __('Updates locked', 'lr-lock-updates') | |
| : $indicator . ' ' . __('Updates unlocked', 'lr-lock-updates'); | |
| $wp_admin_bar->add_node([ | |
| 'id' => 'lr-lock-updates', | |
| 'title' => $title, | |
| 'href' => $url, | |
| 'meta' => ['title' => __('Click to toggle update lock', 'lr-lock-updates')], | |
| ]); | |
| } | |
| public function handle_toggle(): void { | |
| if (!isset($_GET['lr_lock_toggle']) || !$this->current_user_can_manage()) { | |
| return; | |
| } | |
| if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'lr_lock_toggle')) { | |
| wp_die(esc_html__('Invalid security token', 'lr-lock-updates')); | |
| } | |
| $new_state = (sanitize_key($_GET['lr_lock_toggle']) === 'on'); | |
| $this->set_enabled($new_state); | |
| if (!$new_state) { | |
| $this->trigger_update_checks(); | |
| } | |
| wp_safe_redirect(remove_query_arg(['lr_lock_toggle', '_wpnonce'])); | |
| exit; | |
| } | |
| public function add_tools_page(): void { | |
| if (is_multisite() || !$this->current_user_can_manage()) { | |
| return; | |
| } | |
| add_management_page( | |
| __('LR Lock Updates', 'lr-lock-updates'), | |
| __('LR Lock Updates', 'lr-lock-updates'), | |
| 'manage_options', | |
| 'lr-lock-updates', | |
| [$this, 'render_tools_page'] | |
| ); | |
| } | |
| public function add_network_tools_page(): void { | |
| if (!is_multisite() || !$this->current_user_can_manage()) { | |
| return; | |
| } | |
| add_submenu_page( | |
| 'settings.php', | |
| __('LR Lock Updates', 'lr-lock-updates'), | |
| __('LR Lock Updates', 'lr-lock-updates'), | |
| 'manage_network_options', | |
| 'lr-lock-updates', | |
| [$this, 'render_tools_page'] | |
| ); | |
| } | |
| public function render_tools_page(): void { | |
| if (!$this->current_user_can_manage()) { | |
| wp_die(esc_html__('Permission denied', 'lr-lock-updates')); | |
| } | |
| $locked = $this->is_enabled(); | |
| $parts = $this->get_parts(); | |
| echo '<div class="wrap">'; | |
| echo '<h1>' . esc_html__('LR Lock Updates', 'lr-lock-updates') . '</h1>'; | |
| if (!empty($_GET['saved'])) { | |
| echo '<div class="updated notice is-dismissible"><p>' . esc_html__('Settings saved.', 'lr-lock-updates') . '</p></div>'; | |
| } | |
| echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">'; | |
| wp_nonce_field('lr_lock_save'); | |
| echo '<input type="hidden" name="action" value="lr_lock_save">'; | |
| echo '<table class="form-table" role="presentation">'; | |
| echo '<tr><th scope="row">' . esc_html__('Lock status', 'lr-lock-updates') . '</th><td>'; | |
| echo '<label><input type="checkbox" name="lr_lock_enabled" value="1" ' . checked($locked, true, false) . '> '; | |
| echo esc_html__('Enable lock', 'lr-lock-updates') . '</label>'; | |
| echo '<p class="description">' . esc_html__('When enabled, selected update types are blocked and nags are hidden.', 'lr-lock-updates') . '</p>'; | |
| echo '</td></tr>'; | |
| echo '<tr><th scope="row">' . esc_html__('Lock parts', 'lr-lock-updates') . '</th><td>'; | |
| echo '<label><input type="checkbox" name="lr_lock_core" value="1" ' . checked($parts['core'], 1, false) . '> '; | |
| echo esc_html__('Core', 'lr-lock-updates') . '</label><br>'; | |
| echo '<label><input type="checkbox" name="lr_lock_plugins" value="1" ' . checked($parts['plugins'], 1, false) . '> '; | |
| echo esc_html__('Plugins', 'lr-lock-updates') . '</label><br>'; | |
| echo '<label><input type="checkbox" name="lr_lock_themes" value="1" ' . checked($parts['themes'], 1, false) . '> '; | |
| echo esc_html__('Themes', 'lr-lock-updates') . '</label>'; | |
| echo '<p class="description">' . esc_html__('Uncheck any item you want to keep updatable while the lock is enabled.', 'lr-lock-updates') . '</p>'; | |
| echo '</td></tr>'; | |
| echo '</table>'; | |
| submit_button(__('Save changes', 'lr-lock-updates')); | |
| echo '</form>'; | |
| $this->render_activity_log(); | |
| echo '</div>'; | |
| } | |
| private function render_activity_log(): void { | |
| $log = $this->get_option(self::OPT_LOG, []); | |
| if (empty($log)) { | |
| return; | |
| } | |
| echo '<h2>' . esc_html__('Recent Activity', 'lr-lock-updates') . '</h2>'; | |
| $export_url = wp_nonce_url(add_query_arg('lr_lock_export_log', '1'), 'lr_lock_export_log'); | |
| echo '<p><a href="' . esc_url($export_url) . '" class="button">' . esc_html__('Download CSV', 'lr-lock-updates') . '</a></p>'; | |
| echo '<table class="wp-list-table widefat fixed striped"><thead><tr>'; | |
| echo '<th>' . esc_html__('Date', 'lr-lock-updates') . '</th>'; | |
| echo '<th>' . esc_html__('User', 'lr-lock-updates') . '</th>'; | |
| echo '<th>' . esc_html__('Action', 'lr-lock-updates') . '</th>'; | |
| echo '</tr></thead><tbody>'; | |
| foreach (array_reverse(array_slice($log, -self::DISPLAYED_LOG_ENTRIES)) as $entry) { | |
| $user = get_userdata($entry['user_id']); | |
| $username = $user ? $user->user_login : __('Unknown', 'lr-lock-updates'); | |
| echo '<tr>'; | |
| echo '<td>' . esc_html(wp_date(get_option('date_format') . ' ' . get_option('time_format'), $entry['time'])) . '</td>'; | |
| echo '<td>' . esc_html($username) . '</td>'; | |
| echo '<td>' . esc_html($entry['action']) . '</td>'; | |
| echo '</tr>'; | |
| } | |
| echo '</tbody></table>'; | |
| } | |
| public function handle_save(): void { | |
| if (!$this->current_user_can_manage()) { | |
| wp_die(esc_html__('Permission denied', 'lr-lock-updates')); | |
| } | |
| if (!wp_verify_nonce($_POST['_wpnonce'] ?? '', 'lr_lock_save')) { | |
| wp_die(esc_html__('Invalid security token', 'lr-lock-updates')); | |
| } | |
| $enabled = isset($_POST['lr_lock_enabled']); | |
| $this->set_enabled($enabled); | |
| $parts = [ | |
| 'core' => !empty($_POST['lr_lock_core']) ? 1 : 0, | |
| 'plugins' => !empty($_POST['lr_lock_plugins']) ? 1 : 0, | |
| 'themes' => !empty($_POST['lr_lock_themes']) ? 1 : 0, | |
| ]; | |
| $this->set_parts($parts); | |
| if (!$enabled) { | |
| $this->trigger_update_checks(); | |
| } | |
| $ref = wp_get_referer(); | |
| if (!$ref) { | |
| $ref = is_network_admin() | |
| ? network_admin_url('settings.php?page=lr-lock-updates') | |
| : admin_url('tools.php?page=lr-lock-updates'); | |
| } | |
| wp_safe_redirect(add_query_arg('saved', 1, $ref)); | |
| exit; | |
| } | |
| public function footer_text(string $text): string { | |
| if ($this->is_enabled()) { | |
| $text .= ' | ' . esc_html__('Updates locked by LR Lock Updates.', 'lr-lock-updates'); | |
| } | |
| return $text; | |
| } | |
| public function action_links(array $links, string $mu_plugin_file): array { | |
| if (basename($mu_plugin_file) !== basename(__FILE__)) { | |
| return $links; | |
| } | |
| $url = is_multisite() | |
| ? network_admin_url('settings.php?page=lr-lock-updates') | |
| : admin_url('tools.php?page=lr-lock-updates'); | |
| $links[] = '<a href="' . esc_url($url) . '">' . esc_html__('Settings', 'lr-lock-updates') . '</a>'; | |
| return $links; | |
| } | |
| /** | |
| * Get option from site or regular options based on multisite. | |
| * | |
| * @param string $key Option key. | |
| * @param mixed $default Default value. | |
| * @return mixed | |
| */ | |
| private function get_option(string $key, $default = null) { | |
| return is_multisite() ? get_site_option($key, $default) : get_option($key, $default); | |
| } | |
| /** | |
| * Set option in site or regular options based on multisite. | |
| * | |
| * @param string $key Option key. | |
| * @param mixed $value Option value. | |
| * @return bool | |
| */ | |
| private function set_option(string $key, $value): bool { | |
| return is_multisite() ? update_site_option($key, $value) : update_option($key, $value, false); | |
| } | |
| private function current_user_can_manage(): bool { | |
| return current_user_can('manage_options') || current_user_can('manage_network_options'); | |
| } | |
| /** | |
| * Check if lock is currently enabled. | |
| * | |
| * Auto-initializes to DEFAULT_STATE on first run, or uses LR_LOCK_FORCE_DEFAULT | |
| * if defined in wp-config.php. | |
| * | |
| * @return bool | |
| */ | |
| public function is_enabled(): bool { | |
| $val = $this->get_option(self::OPT_ENABLED, null); | |
| if ($val === null) { | |
| $initial = defined('LR_LOCK_FORCE_DEFAULT') | |
| ? (LR_LOCK_FORCE_DEFAULT ? 1 : 0) | |
| : (self::DEFAULT_STATE ? 1 : 0); | |
| $this->set_option(self::OPT_ENABLED, $initial); | |
| return (bool) $initial; | |
| } | |
| return (bool) $val; | |
| } | |
| public function set_enabled(bool $enabled): void { | |
| $old = $this->is_enabled(); | |
| $this->set_option(self::OPT_ENABLED, $enabled ? 1 : 0); | |
| if ($old !== $enabled) { | |
| $this->log_action($enabled ? 'Lock enabled' : 'Lock disabled'); | |
| do_action('lr_lock_state_changed', $enabled, $old, get_current_user_id()); | |
| } | |
| } | |
| /** | |
| * Get which parts (core/plugins/themes) are locked. | |
| * | |
| * @return array Associative array with 'core', 'plugins', 'themes' keys (1 or 0). | |
| */ | |
| public function get_parts(): array { | |
| $parts = $this->get_option(self::OPT_PARTS, null); | |
| if (!is_array($parts)) { | |
| $parts = ['core' => 1, 'plugins' => 1, 'themes' => 1]; | |
| $this->set_option(self::OPT_PARTS, $parts); | |
| } | |
| // Ensure all keys exist with defaults | |
| return [ | |
| 'core' => (int) !empty($parts['core']), | |
| 'plugins' => (int) !empty($parts['plugins']), | |
| 'themes' => (int) !empty($parts['themes']), | |
| ]; | |
| } | |
| public function set_parts(array $parts): void { | |
| $clean = [ | |
| 'core' => empty($parts['core']) ? 0 : 1, | |
| 'plugins' => empty($parts['plugins']) ? 0 : 1, | |
| 'themes' => empty($parts['themes']) ? 0 : 1, | |
| ]; | |
| $this->set_option(self::OPT_PARTS, $clean); | |
| $enabled_parts = array_keys(array_filter($clean)); | |
| $this->log_action('Parts updated: ' . (!empty($enabled_parts) ? implode(', ', $enabled_parts) : 'none')); | |
| } | |
| private function log_action(string $action): void { | |
| $log = $this->get_option(self::OPT_LOG, []); | |
| $log[] = ['time' => time(), 'user_id' => get_current_user_id(), 'action' => $action]; | |
| $log = array_slice($log, -self::MAX_LOG_ENTRIES); | |
| $this->set_option(self::OPT_LOG, $log); | |
| } | |
| public function export_log_csv(): void { | |
| if (!isset($_GET['lr_lock_export_log']) || !$this->current_user_can_manage()) { | |
| return; | |
| } | |
| if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'lr_lock_export_log')) { | |
| wp_die(esc_html__('Invalid security token', 'lr-lock-updates')); | |
| } | |
| $log = $this->get_option(self::OPT_LOG, []); | |
| if (empty($log)) { | |
| wp_die(esc_html__('No log entries to export', 'lr-lock-updates')); | |
| } | |
| header('Content-Type: text/csv; charset=utf-8'); | |
| header('Content-Disposition: attachment; filename=lr-lock-activity-' . date('Y-m-d') . '.csv'); | |
| header('Pragma: no-cache'); | |
| header('Expires: 0'); | |
| $output = fopen('php://output', 'w'); | |
| fputcsv($output, ['Date', 'User ID', 'Username', 'Action']); | |
| foreach ($log as $entry) { | |
| $user = get_userdata($entry['user_id']); | |
| $username = $user ? $user->user_login : 'Unknown'; | |
| fputcsv($output, [wp_date('Y-m-d H:i:s', $entry['time']), $entry['user_id'], $username, $entry['action']]); | |
| } | |
| fclose($output); | |
| exit; | |
| } | |
| public function trigger_update_checks(): void { | |
| if (function_exists('wp_version_check')) { | |
| wp_version_check(); | |
| } | |
| if (function_exists('wp_update_plugins')) { | |
| wp_update_plugins(); | |
| } | |
| if (function_exists('wp_update_themes')) { | |
| wp_update_themes(); | |
| } | |
| } | |
| public function register_cli(): void { | |
| if (!defined('WP_CLI') || !WP_CLI) { | |
| return; | |
| } | |
| WP_CLI::add_command('lr-lock', 'LR_Lock_CLI_Command'); | |
| } | |
| } | |
| if (defined('WP_CLI') && WP_CLI) { | |
| class LR_Lock_CLI_Command { | |
| public function status(): void { | |
| $plugin = LR_Lock_Updates::instance(); | |
| $locked = $plugin->is_enabled(); | |
| WP_CLI::log($locked ? 'Status: Locked' : 'Status: Unlocked'); | |
| $parts = $plugin->get_parts(); | |
| $enabled = array_keys(array_filter($parts)); | |
| WP_CLI::log('Protected: ' . (empty($enabled) ? 'none' : implode(', ', $enabled))); | |
| } | |
| public function enable(): void { | |
| $plugin = LR_Lock_Updates::instance(); | |
| $plugin->set_enabled(true); | |
| WP_CLI::success('Lock enabled'); | |
| } | |
| public function disable(): void { | |
| $plugin = LR_Lock_Updates::instance(); | |
| $plugin->set_enabled(false); | |
| $plugin->trigger_update_checks(); | |
| WP_CLI::success('Lock disabled. Update checks triggered.'); | |
| } | |
| public function toggle(): void { | |
| $plugin = LR_Lock_Updates::instance(); | |
| $new = !$plugin->is_enabled(); | |
| $plugin->set_enabled($new); | |
| if (!$new) { | |
| $plugin->trigger_update_checks(); | |
| } | |
| WP_CLI::success('Lock ' . ($new ? 'enabled' : 'disabled')); | |
| } | |
| public function parts($args = [], $assoc_args = []): void { | |
| $plugin = LR_Lock_Updates::instance(); | |
| if (empty($assoc_args)) { | |
| $parts = $plugin->get_parts(); | |
| WP_CLI::log('Parts configuration:'); | |
| WP_CLI::log(' Core: ' . ($parts['core'] ? 'locked' : 'unlocked')); | |
| WP_CLI::log(' Plugins: ' . ($parts['plugins'] ? 'locked' : 'unlocked')); | |
| WP_CLI::log(' Themes: ' . ($parts['themes'] ? 'locked' : 'unlocked')); | |
| return; | |
| } | |
| $parts = $plugin->get_parts(); | |
| if (isset($assoc_args['core'])) { | |
| $parts['core'] = (int) $assoc_args['core']; | |
| } | |
| if (isset($assoc_args['plugins'])) { | |
| $parts['plugins'] = (int) $assoc_args['plugins']; | |
| } | |
| if (isset($assoc_args['themes'])) { | |
| $parts['themes'] = (int) $assoc_args['themes']; | |
| } | |
| $plugin->set_parts($parts); | |
| WP_CLI::success('Parts updated'); | |
| } | |
| } | |
| } | |
| LR_Lock_Updates::instance(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment