diff --git a/1984-hosting-support-ui.php b/1984-hosting-support-ui.php index b878347..24ff79d 100644 --- a/1984-hosting-support-ui.php +++ b/1984-hosting-support-ui.php @@ -15,26 +15,51 @@ * Feel free to remove this file if you don't intend to use the 1984/HostingSupport1984 * user interface additions. */ -class HostingSupport1984UI { +class HostingSupport1984UI +{ const VIEWS_DIR = './views/'; /** - * Construct a new HostingSupport1984UI object + * Build a new HostingSupport1984UI object */ - public function __construct() { + public function __construct() + { // Add the 1984 Support Information dashboard widget. add_action( 'wp_dashboard_setup', - array( $this, 'add_dashboard_widget' ) + array($this, + 'add_dashboard_widget') ); // Enqueue the admin JS and CSS. add_action( 'admin_enqueue_scripts', - array( $this, 'enqueue_admin_scripts' ) + array($this, + 'enqueue_admin_scripts') ); } + /** + * Add the 1984 Hosting dashboard widget. + */ + public function add_dashboard_widget() + { + wp_add_dashboard_widget( + '1984_hosting_support_widget', + __('1984 Hosting Support', 'hostingsupport1984'), + array($this, + 'render_dashboard_widget') + ); + } + + /** + * Render the 1984 Hosting dashboard widget. + */ + public function render_dashboard_widget() + { + $this->render_view('support-dashboard-widget.php'); + } + /** * Render a view * @@ -43,12 +68,13 @@ class HostingSupport1984UI { * * @return Boolean True if the user has access to the view. False if not. */ - public function render_view( string $view_file, bool $admin_only = true ) { + public function render_view(string $view_file, bool $admin_only = true): bool + { if ( - true === $admin_only && current_user_can( 'manage_options' ) || + true === $admin_only && current_user_can('manage_options') || false === $admin_only ) { - require self::view_path( $view_file ); + require self::view_path($view_file); return true; } return false; @@ -57,44 +83,20 @@ class HostingSupport1984UI { /** * Get full file path for a view * - * @param String $view_file The view file name. + * @param String $view_file The view file name. + * * @return String The path. */ - private function view_path( $view_file ) { - return plugin_dir_path( __FILE__ ) . self::VIEWS_DIR . $view_file; - } - - /** - * Check if the user can see the 1984 admin panel - * - * @return [type] [description] - */ - private function current_user_can_view_admin_panel() { - return ( true === current_user_can( 'manage_options' ) ); - } - - /** - * Add the 1984 Hosting dashboard widget. - */ - public function add_dashboard_widget() { - wp_add_dashboard_widget( - '1984_hosting_support_widget', - __( '1984 Hosting Support', 'hostingsupport1984' ), - array( $this, 'render_dashboard_widget' ) - ); - } - - /** - * Render the 1984 Hosting dashboard widget. - */ - public function render_dashboard_widget() { - $this->render_view( 'support-dashboard-widget.php' ); + private function view_path(string $view_file) + { + return plugin_dir_path(__FILE__) . self::VIEWS_DIR . $view_file; } /** * Enqueue the 1984 wp-admin scripts */ - public function enqueue_admin_scripts() { + public function enqueue_admin_scripts() + { wp_enqueue_script( '1984-hosting-support-admin', plugins_url( @@ -116,6 +118,16 @@ class HostingSupport1984UI { false ); } + + /** + * Check if the user can see the 1984 admin panel + * + * @return bool [type] [description] + */ + private function current_user_can_view_admin_panel(): bool + { + return (true === current_user_can('manage_options')); + } } $hosting_support_1984_ui = new HostingSupport1984UI(); diff --git a/1984-hosting-support.php b/1984-hosting-support.php index 3cc08bf..16b43d6 100644 --- a/1984-hosting-support.php +++ b/1984-hosting-support.php @@ -19,38 +19,77 @@ * There are other classes that may depend on this file, so only remove it if * you do not intend to use the plugin. */ -class HostingSupport1984 { +class HostingSupport1984 +{ const HOSTING_SUPPORT_VERSION = '0.4.0'; /** * Construct a new HostingSupport1984 object */ - public function __construct() { + public function __construct() + { // Loading the text domain for a mu-plugin is tricky. // We need to use admin_init instead of plugins_loaded as we are only // using the plugin in the admin interface. add_action( 'admin_init', - array( $this, 'load_textdomain' ) + array($this, + 'load_textdomain') ); // Register the activation hook. register_activation_hook( __FILE__, - array( $this, 'activation_hook' ) + array($this, + 'activation_hook') ); // Run on activation of any plugin. add_action( 'activated_plugin', - array( $this, 'plugin_activation_hook' ) + array($this, + 'plugin_activation_hook') ); // Run on activation of theme. add_action( 'switch_theme', - array( $this, 'theme_switch_hook' ) + array($this, + 'theme_switch_hook') ); + + // Add a clear warning on the Patchstack plugin row to discourage removal. + add_filter( + 'plugin_row_meta', + array($this, + 'add_patchstack_warning_row_meta'), + 10, + 2 + ); + + // Prevent deactivation/deletion of this plugin and Patchstack via UI links. + add_filter( + 'plugin_action_links', + array($this, + 'filter_plugin_action_links'), + 10, + 4 + ); + add_filter( + 'network_admin_plugin_action_links', + array($this, + 'filter_plugin_action_links'), + 10, + 4 + ); + + // Guard against direct requests (single and bulk) on Plugins screens and updater endpoints. + add_action('load-plugins.php', array($this, + 'protect_plugins_admin_actions')); + add_action('load-plugins-network.php', array($this, + 'protect_plugins_admin_actions')); + add_action('load-update.php', array($this, + 'protect_plugins_admin_actions')); } /** @@ -58,7 +97,8 @@ class HostingSupport1984 { * * @return Boolean */ - public function load_textdomain() { + public function load_textdomain() + { return load_plugin_textdomain( 'hostingsupport1984', false, @@ -66,38 +106,40 @@ class HostingSupport1984 { ); } - /** - * Check if Patchstack is enabled. - * - * @return Boolean - */ - public function is_patchstack_active() { - return is_plugin_active( 'patchstack/patchstack.php' ); - } - /** * On activation of this plugin, set auto-update option for core, * plus enable auto-updates for installed plugins and themes. * * This is skipped if Patchstack is active on the site. */ - public function activation_hook() { - if ( $this->is_patchstack_active() ) { + public function activation_hook() + { + if ($this->is_patchstack_active()) { // In case Patchstack is active, do not continue. return; } // Auto-update WordPress core. - update_site_option( 'auto_update_core_major', 'enabled' ); + update_site_option('auto_update_core_major', 'enabled'); // Auto-update plugins. - $all_plugins = apply_filters( 'all_plugins', get_plugins() ); + $all_plugins = apply_filters('all_plugins', get_plugins()); - update_site_option( 'auto_update_plugins', array_keys( $all_plugins ) ); + update_site_option('auto_update_plugins', array_keys($all_plugins)); // Auto-update themes. $all_themes = wp_get_themes(); - update_site_option( 'auto_update_themes', array_keys( $all_themes ) ); + update_site_option('auto_update_themes', array_keys($all_themes)); + } + + /** + * Check if Patchstack is enabled. + * + * @return Boolean + */ + public function is_patchstack_active() + { + return is_plugin_active('patchstack/patchstack.php'); } /** @@ -105,16 +147,17 @@ class HostingSupport1984 { * * This is skipped if Patchstack is active on the site. */ - public function plugin_activation_hook( $plugin ) { - if ( $this->is_patchstack_active() ) { + public function plugin_activation_hook($plugin) + { + if ($this->is_patchstack_active()) { // In case Patchstack is active, do not continue. return; } - $auto_updated_plugins = (array) get_site_option( 'auto_update_plugins', array() ); + $auto_updated_plugins = (array)get_site_option('auto_update_plugins', array()); $auto_updated_plugins[] = $plugin; - update_site_option( 'auto_update_plugins', array_unique( $auto_updated_plugins ) ); + update_site_option('auto_update_plugins', array_unique($auto_updated_plugins)); } /** @@ -122,19 +165,156 @@ class HostingSupport1984 { * * This is skipped if Patchstack is active on the site. */ - public function theme_switch_hook( $theme ) { - if ( $this->is_patchstack_active() ) { + public function theme_switch_hook($theme) + { + if ($this->is_patchstack_active()) { // In case Patchstack is active, do not continue. return; } $all_themes = wp_get_themes(); - update_site_option( 'auto_update_themes', array_keys( $all_themes ) ); + update_site_option('auto_update_themes', array_keys($all_themes)); + } + + /** + * Append a prominent warning to the Patchstack plugin row in the Plugins screen. + * + * @param array $links Existing row meta links. + * @param string $file Plugin file path about plugins directory. + * + * @return array Modified row meta-links. + */ + public function add_patchstack_warning_row_meta(array $links, string $file): array + { + // Only show the warning on the Patchstack row AND only if Patchstack is active. + if ('patchstack/patchstack.php' !== $file || !$this->is_patchstack_active()) { + return $links; + } + + $warning_text = '1984: Please do not deactivate or delete this plugin - part of your WordPress Service Pack.'; + + $logo_1984_url = plugins_url('icons/1984-hosting-logo.webp', __FILE__); + $logo_patchstack = plugins_url('icons/patchstack-logo.svg', __FILE__); + + $warning_icon = + ''; + + $links[] = + '' . $warning_icon . esc_html($warning_text) . '' . + '' + . + '' + . + '1984 Hosting' + . + '' + . + '' + . + 'Patchstack' + . + '' + . + ''; + + return $links; + } + + /** + * Remove Deactivate/Delete action links for protected plugins (single and network admin). + * + * @param array $actions + * @param string $plugin_file + * @param array $plugin_data + * @param string $context + * + * @return array + */ + public function filter_plugin_action_links(array $actions, string $plugin_file, array $plugin_data, + string $context): array + { + if ($this->is_protected_plugin($plugin_file)) { + unset($actions['deactivate']); + unset($actions['delete']); + } + + return $actions; + } + + /** + * Determine if a plugin file is in the protected list. + * + * @param string $plugin_file + * + * @return bool + */ + private function is_protected_plugin(string $plugin_file): bool + { + return in_array($plugin_file, $this->get_protected_plugins(), true); + } + + /** + * Return plugin basenames that must be protected from deactivation/deletion. + * + * @return string[] + */ + private function get_protected_plugins(): array + { + $own = plugin_basename(__FILE__); + return array( + $own, + 'patchstack/patchstack.php', + ); + } + + /** + * Intercept admin actions attempting to deactivate or delete protected plugins and block them. + */ + public function protect_plugins_admin_actions() + { + // Collect requested action(s). + $action = isset($_REQUEST['action']) ? sanitize_key((string)$_REQUEST['action']) : ''; + $action2 = isset($_REQUEST['action2']) ? sanitize_key((string)$_REQUEST['action2']) : ''; + + $targets = array(); + if (!empty($_REQUEST['plugin'])) { + $targets[] = sanitize_text_field((string)$_REQUEST['plugin']); + } + if (!empty($_REQUEST['checked']) && is_array($_REQUEST['checked'])) { + foreach ($_REQUEST['checked'] as $pf) { + $targets[] = sanitize_text_field((string)$pf); + } + } + + if (empty($targets)) { + return; + } + + $blocked_actions = array( + 'deactivate', + 'deactivate-selected', + 'delete', + 'delete-selected', + 'delete-plugin', + ); + + if (!in_array($action, $blocked_actions, true) && !in_array($action2, $blocked_actions, true)) { + return; + } + + $protected = $this->get_protected_plugins(); + $intersect = array_intersect($targets, $protected); + if (!empty($intersect)) { + wp_die( + __('For security, this action is blocked: 1984 Hosting Support and Patchstack cannot be deactivated or deleted while this policy is active.', 'hostingsupport1984'), + 403 + ); + } } } $hosting_support_1984 = new HostingSupport1984(); -define( 'A1984_HOSTING_SUPPORT_BASE_FILE', __FILE__ ); +const A1984_HOSTING_SUPPORT_BASE_FILE = __FILE__; require_once __DIR__ . '/1984-hosting-support-ui.php'; diff --git a/README.md b/README.md index 9d00019..8ac17d6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # 1984 Hosting Support Plugin -This is a plugin provided by [1984 Hosting Company](https://1984.hosting/) to improve users' support experience while using WordPress. It is primarily intended for customers of 1984 Hosting Company, though anyone can use it. If you are a 1984 customer, feel free to disable or remove the plugin on your site if you do not find it useful. +This is a plugin provided by [1984 Hosting Company](https://1984.hosting/) to improve users' support experience while +using WordPress. It is primarily intended for customers of 1984 Hosting Company, though anyone can use it. If you are a +1984 customer, feel free to disable or remove the plugin on your site if you do not find it useful. The main function is provide a widget to users, as shown below: diff --git a/icons/contact-support.svg b/icons/contact-support.svg index c419875..f4016c5 100644 --- a/icons/contact-support.svg +++ b/icons/contact-support.svg @@ -1 +1,10 @@ - \ No newline at end of file + + + + + + + + \ No newline at end of file diff --git a/icons/knowledge-base.svg b/icons/knowledge-base.svg index 4e2d78b..98e6fb5 100644 --- a/icons/knowledge-base.svg +++ b/icons/knowledge-base.svg @@ -1 +1,8 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/icons/patchstack-logo.svg b/icons/patchstack-logo.svg new file mode 100644 index 0000000..39579b1 --- /dev/null +++ b/icons/patchstack-logo.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/icons/profile.svg b/icons/profile.svg index 8fef49a..3b78de1 100644 --- a/icons/profile.svg +++ b/icons/profile.svg @@ -1 +1,7 @@ - + + + + + + diff --git a/icons/sites.svg b/icons/sites.svg index aa02e02..88030c8 100644 --- a/icons/sites.svg +++ b/icons/sites.svg @@ -1 +1,8 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/js/1984-hosting-support.js b/js/1984-hosting-support.js index 310ba84..c70d2fe 100644 --- a/js/1984-hosting-support.js +++ b/js/1984-hosting-support.js @@ -4,16 +4,19 @@ * @license https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html GNU General Public License v2.0 */ -function HostingSupport1984CopyEmailAddress( e ) { - e.preventDefault(); - var emailAddressTag = document.querySelector( '#hosting-support-1984-address' ); - emailAddressTag.select(); - document.execCommand( 'copy' ); +function HostingSupport1984CopyEmailAddress(e) { + e.preventDefault(); + const emailAddressTag = document.querySelector('#hosting-support-1984-address'); + navigator.clipboard.writeText(emailAddressTag.value) + .catch(err => console.error("Clipboard write failed:", err)); } -if (document.querySelector( '#hosting-support-1984-link' )) { - document.querySelector( '#hosting-support-1984-link' ).addEventListener( - 'click', - HostingSupport1984CopyEmailAddress - ); +document.querySelector('#hosting-support-1984-link') + ?.addEventListener('click', HostingSupport1984CopyEmailAddress); + +if (document.querySelector('#hosting-support-1984-link')) { + document.querySelector('#hosting-support-1984-link').addEventListener( + 'click', + HostingSupport1984CopyEmailAddress + ); } diff --git a/style/1984-hosting-support.css b/style/1984-hosting-support.css index 49ca127..538528b 100644 --- a/style/1984-hosting-support.css +++ b/style/1984-hosting-support.css @@ -7,51 +7,34 @@ * https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html **/ -#hostingsupport1984_support_widget { - background: #fffbee; -} - -#adminmenu a.toplevel_page_hostingsupport1984_manage { - background-color: #b48701; - font-weight: bold; -} - -#adminmenu a.toplevel_page_hostingsupport1984_manage:hover { - color: #fff; -} - -#adminmenu a.toplevel_page_hostingsupport1984_manage div.wp-menu-image::before { - color: #fff; -} - #hosting-support-1984-main-links { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 0.2rem; - list-style-type: none; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.2rem; + list-style-type: none; } .hosting-support-1984-li { } .hosting-support-1984-icon { - width: 5.3em; - height: auto; - display: block; - margin: auto; + width: 5.3em; + height: auto; + display: block; + margin: auto; } .hosting-support-1984-text { - text-align: center; + text-align: center; } #hosting-support-1984-logo { - display: flex; - justify-content: flex-end; + display: flex; + justify-content: flex-end; } #hosting-support-1984-logo img { - width: auto; - height: 1.5em; + width: auto; + height: 1.5em; } diff --git a/views/support-dashboard-widget.php b/views/support-dashboard-widget.php index 2fcaacc..aad2937 100644 --- a/views/support-dashboard-widget.php +++ b/views/support-dashboard-widget.php @@ -10,58 +10,67 @@ ?>