'limit_post_revisions', 'description' => 'Limit or disable post revisions', 'details' => 'Limit post revisions to improve database efficiency. This all applies to all post types.' ), array( 'name' => 'disable_emoticons', 'description' => 'Disable emoticons', 'details' => 'Stops the WordPress core emoticons Javascript files from loading. Ensure you are not using these emoticons on your frontend.' ), array( 'name' => 'disable_pingback_trackback_emails', 'description' => 'Disable pingback and trackback notification emails', 'details' => 'Stops potential spam by disabling WordPress pingback and trackbacks including on existing posts.' ), array( 'name' => 'disable_self_pingback', 'description' => 'Disable self-pingbacks', 'details' => 'Stops pinging from your own website / domain.' ), array( 'name' => 'disable_xml_rpc', 'description' => 'Disable XML-RPC', 'details' => 'Disable this legacy core WordPress API entirely to prevent malicious activity. Ensure you do not have any services using the service.' ), array( 'name' => 'disable_media_comments', 'description' => 'Disable Media Comments', 'details' => 'Removes the comments field from all media attachments to prevent spam.' ), array( 'name' => 'remove_rsd_links', 'description' => 'Remove RSD links', 'details' => 'Removes the Really Simple Discovery service endpoint.' ), ); $toolkit_security_options = array( array( 'name' => 'disable_editors', 'description' => 'Disable theme and plugin editors', 'details' => 'Disable the theme and plugin code editors from all WordPress users.' ), array( 'name' => 'disable_installs_and_updates', 'description' => 'Limit the users who can install and update plugins and themes', 'details' => 'Limit the WordPress users who can install and update plugins and themes.' ), array( 'name' => 'hide_wordpress_version', 'description' => 'Hide WordPress version generator in front-end source code', 'details' => 'Hides the WordPress version generator in the frontend source code.' ), array( 'name' => 'remove_site_health', 'description' => 'Limit the users who can view Site Health', 'details' => 'Limit the WordPress users who can view Site Health.' ), array( 'name' => 'secure_wpconfig', 'description' => 'Secure the wp-config.php file using .htaccess', 'details' => 'Hardens the wp-config.php against attacks using .htaccess. Ensure your server supports .htaccess and use with caution as may break in some server environments.' ), array( 'name' => 'stop_user_enumeration', 'description' => 'Stop User Enumeration using .htaccess', 'details' => 'Prevent user enumeration to protect your login names at the basic level.' ), array( 'name' => 'checksum_verification_for_wordpress_core_files', 'description' => 'Checksum verification for WordPress core files', 'details' => ' Checks your WordPress core files against the official checksums daily to detect any suspicious core modifications.' ), array( 'name' => 'limit_users_modify_admin_email', 'description' => 'Limit users who can modify Admin Email Address', 'details' => 'Limit which WordPress users can modify the Admin Email Address in the WordPress General Options panel.' ), array( 'name' => 'disable_admin_notice', 'description' => 'Override the Disable Admin Notices Individually plugin to show all admin notices', 'details' => 'Override the Disable Admin Notices Individually plugin to show all admin notices for specific WordPress users. Requires the Disable Admin Notices Individually plugin to be installed.' ), array( 'name' => 'kadence_security_force_2fa', 'description' => 'Force Two-Factor setup in Kadence Security', 'details' => 'Removes the Skip option from the Kadence Security ( formerly Solid Security ) Two-Factor setup prompt and requires an emailed login code until a method is configured. Applies to all users except user groups excluded from Two-Factor in Kadence Security. Requires Kadence Security with the Email method enabled.' ), array( 'name' => 'users_summary', 'description' => 'Backend users summary: counts, last seen and Two-Factor enrollment', 'details' => 'Records when each backend user (administrator, shop manager, editor, author, contributor) was last active in the WordPress admin, at most once per hour, and exposes a counts-only summary to administrators at /wp-json/agency-toolkit/v1/users-summary: users by role, last-seen age buckets and Two-Factor enrollment. No usernames or emails are included. Two-Factor counts require Kadence Security; without it they are reported as unavailable. On by default since 1.0.36. Disabling this setting stops collection; timestamps already recorded remain in user meta until the user is deleted.' ), ); $toolkit_email_notification_options = array( array( 'name' => 'disable_auto_update_emails', 'description' => 'Disable admin notification emails', 'details' => 'Stop admin notification emails from being sent including new user, password change, and automatic WordPress update notifications.' ), array( 'name' => 'change_email_sender', 'description' => 'Globally change email sender', 'details' => 'Globally override all WordPress emails to originate from a specified email address instead of the default admin email. Note may not override all plugins.' ), array( 'name' => 'disable_admin_email_verification', 'description' => 'Disable WordPress Admin Email Verification prompt', 'details' => 'Stops the prompt that routinely asks admins to verify their email after sign-in.' ), array( 'name' => 'change_user_notifications_email', 'description' => 'Change admin email address shared in user emails', 'details' => 'Changes the admin email listed in user emails ( such as password resets ) to a specified email instead of the default admin email.' ), array( 'name' => 'enable_plugin_update_admin_notifications', 'description' => 'Receive email notifications when specific plugins are updated', 'details' => 'Sends an email notification to specified email addresses every time selected plugins are updated.' ), array( 'name' => 'enable_plugin_activations_admin_notifications', 'description' => 'Receive email notifications when specific plugins are activated and deactivated', 'details' => 'Sends an email notification to specified email addresses every time selected plugins are activated and deactivated.' ), array( 'name' => 'payments_test_mode_check', 'description' => 'Check WooCommerce payment gateways for test mode', 'details' => 'Checks WooCommerce payment gateways for test mode and sends an email notification to the admin if any are found. Currently only compatible with Stripe, Authorize.Net, and PayPal Payments. Runs every 5 minutes.' ), array( 'name' => 'payments_disable_check', 'description' => 'Check if WooCommerce payment gateways have been disabled', 'details' => 'Checks if any of the WooCommerce payment gateways have been disabled and sends an email notification to the admin if any have been disabled.' ), array( 'name' => 'comment_notification_recipients', 'description' => 'Send comment notification emails to specified addresses', 'details' => 'Sends the WordPress comment notification emails ( new comment, and comment awaiting moderation ), including WooCommerce product reviews, to the email addresses entered here instead of to the post author and the site admin email. Separate multiple addresses with commas. Editorial notes, pingbacks, trackbacks and other comment types are not redirected. The address list is not included in the settings export, since it is specific to this site. Whether these emails are sent at all is still controlled under Settings > Discussion.' ), ); $toolkit_options = array( array( 'name' => 'limit_admins', 'description' => 'Limit the users who can view the Agency Toolkit Settings Page', 'details' => 'Limit the WordPress users who can view and modify the Agency Toolkit settings.' ), array( 'name' => 'maintenance_mode', 'description' => 'Enable maintenance mode', 'details' => 'Activates maintenance mode for non-logged users on the frontend website, instead displaying a custom message.' ), array( 'name' => 'backend_footer', 'description' => 'Enable a custom agency footer with a name, URL and email in the WordPress Dashboard', 'details' => ' Enable a custom footer with your company name, URL and email address in the WordPress Dashboard footer.' ), array( 'name' => 'backend_logo', 'description' => 'Enable a custom agency logo on the WordPress login screen', 'details' => 'Enable a custom image logo on the WordPress Dashboard login screen.' ), array( 'name' => 'manage_environment_indicator', 'description' => 'Show Environment Indicator in Admin Bar', 'details' => 'Activates the environment indicator in the logged-in admin bar to clearly show if this is a staging or production website.' ), ); // Add options menu function inspry_toolkit_options_menu_item(){ $agency_toolkit_restriction_enabled = get_option( 'inspry_toolkit_limit_admins' ) === '1'; $agency_toolkit_access = get_option( 'inspry_toolkit_limit_admins_list' ); if ( is_admin() && ( ! $agency_toolkit_restriction_enabled || empty( $agency_toolkit_access ) || ( in_array( get_current_user_id(), $agency_toolkit_access ) ) ) ) { $icon_url = plugin_dir_url( __DIR__ ).'agency-toolkit/assets/images/agency-toolkit-white-logo.svg'; add_menu_page( 'Agency Toolkit Options', 'Agency Toolkit', 'manage_options', 'inspry-agency-toolkit', 'inspry_toolkit_options_page', "$icon_url", 80 ); add_submenu_page( 'inspry_toolkit_options_page', 'FAQs', 'FAQs', 'manage_options', 'inspry-toolkit-faqs', 'inspry_toolkit_faqs' ); } else { if ( isset( $_GET[ 'page' ] ) && $_GET[ 'page' ] == 'inspry-agency-toolkit' ) { wp_safe_redirect( admin_url() ); exit; } } } add_action( 'admin_menu', 'inspry_toolkit_options_menu_item' ); /* * Sanitize callbacks for the settings below. * * Each one stores exactly what the settings form has always stored, measured on * 1.0.38: a ticked box is '1', anything empty is '' (never 0, null or an empty * array), and a multi-select is an array of strings. Code across the plugin * compares these loosely and with empty(), and `users_summary` reads an explicit * 0 as "declined", so a callback that normalised the value would change * behaviour on the next save. They run for the settings form only: they are * registered on admin_init, and the import tool (init) and the rescue flow * (plugin load) write their options before that, as they always have. */ /** * An on/off setting. * * @param mixed $value Submitted value. * @return string '1' when on, '0' when explicitly set to 0, otherwise ''. */ function inspry_toolkit_sanitize_toggle( $value ) { if ( is_scalar( $value ) && 1 == $value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- '1', 1 and true are all "on". return '1'; } return ( 0 === $value || '0' === $value ) ? '0' : ''; } /** * A list of user IDs from a multi-select. * * @param mixed $value Submitted value. * @return string[]|string IDs as strings, or '' when none. */ function inspry_toolkit_sanitize_user_list( $value ) { $ids = array(); foreach ( is_array( $value ) ? $value : array() as $id ) { // Whole positive numbers only: absint() would turn "-3" or "2x" into a real-looking ID. $id = is_scalar( $id ) && ctype_digit( (string) $id ) ? (int) $id : 0; if ( $id > 0 ) { $ids[] = (string) $id; } } return $ids ? $ids : ''; } /** * A list of plugin files (folder/file.php) from a multi-select. * * @param mixed $value Submitted value. * @return string[]|string Plugin files, or '' when none. */ function inspry_toolkit_sanitize_plugin_list( $value ) { $plugins = array(); foreach ( is_array( $value ) ? $value : array() as $plugin ) { $plugin = is_scalar( $plugin ) ? sanitize_text_field( (string) $plugin ) : ''; if ( '' !== $plugin ) { $plugins[] = $plugin; } } return $plugins ? $plugins : ''; } /** * A list of option keys from a select (the environment banner colour names). * * @param mixed $value Submitted value. * @return string[]|string Keys, or '' when none. */ function inspry_toolkit_sanitize_key_list( $value ) { $keys = array(); foreach ( is_array( $value ) ? $value : array() as $key ) { $key = is_scalar( $key ) ? sanitize_key( (string) $key ) : ''; if ( '' !== $key ) { $keys[] = $key; } } return $keys ? $keys : ''; } /** * A single line of plain text (names, email addresses, a host name). * * @param mixed $value Submitted value. * @return string */ function inspry_toolkit_sanitize_text( $value ) { return is_scalar( $value ) ? sanitize_text_field( (string) $value ) : ''; } /** * Text that may contain basic HTML (the maintenance mode heading and sentence). * * @param mixed $value Submitted value. * @return string */ function inspry_toolkit_sanitize_html( $value ) { return is_scalar( $value ) ? wp_kses_post( (string) $value ) : ''; } /** * A URL. * * @param mixed $value Submitted value. * @return string */ function inspry_toolkit_sanitize_url( $value ) { return is_scalar( $value ) ? esc_url_raw( trim( (string) $value ) ) : ''; } /** * Turn the Unicode spaces that pasted address lists carry into plain spaces. * * Mail clients and word processors put a non-breaking space (U+00A0) after each * comma; French autocorrect uses U+202F, CJK input U+3000, and a copied first * line may start with a BOM (U+FEFF). Browsers treat all of these as whitespace, * but PCRE's \s without the u modifier is ASCII only, so without this step the * address carrying the stray bytes would fail is_email() and vanish on save. * Malformed UTF-8 makes preg_replace() return null; the input is then used as is. * * @param string $value Raw list. * @return string */ function inspry_toolkit_normalise_address_separators( $value ) { $normalised = preg_replace( '/[\x{00A0}\x{1680}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}\x{FEFF}]/u', ' ', (string) $value ); return null === $normalised ? (string) $value : $normalised; } /** * One or more email addresses, comma separated (the comment notification recipients). * * Addresses that are not valid are dropped rather than repaired: sanitize_email() * strips characters and could turn a typo into someone else's real address. * * @param mixed $value Submitted value. * @return string Valid addresses joined with ", ", or '' when none. */ function inspry_toolkit_sanitize_email_list( $value ) { if ( ! is_scalar( $value ) ) { return ''; } $emails = array(); $seen = array(); foreach ( preg_split( '/[\s,;]+/', inspry_toolkit_normalise_address_separators( $value ) ) as $email ) { $email = trim( $email ); // Mailboxes are case-insensitive in practice; keep the first spelling entered. if ( '' !== $email && is_email( $email ) && ! in_array( strtolower( $email ), $seen, true ) ) { $emails[] = $email; $seen[] = strtolower( $email ); } } return implode( ', ', $emails ); } /** * A whole number that may be left unset. * * @param mixed $value Submitted value. * @return string Digits, or '' when unset. */ function inspry_toolkit_sanitize_number( $value ) { if ( ! is_scalar( $value ) || '' === trim( (string) $value ) ) { return ''; } return (string) absint( $value ); } // Register options function inspry_toolkit_options_register(){ // Get options global $toolkit_options; global $toolkit_performance_options; global $toolkit_security_options; global $toolkit_email_notification_options; $toggle = array( 'sanitize_callback' => 'inspry_toolkit_sanitize_toggle' ); // Every feature's on/off setting. Each is registered here ONCE: registering an // option twice with different callbacks would run both on every save. foreach ( $toolkit_options as $option ) { register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_' . $option[ "name" ], $toggle ); } foreach ( $toolkit_performance_options as $option ) { register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_' . $option[ "name" ], $toggle ); } foreach ( $toolkit_security_options as $option ) { register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_' . $option[ "name" ], $toggle ); } foreach ( $toolkit_email_notification_options as $option ) { register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_' . $option[ "name" ], $toggle ); } // The settings that hang off those features, by the shape of their value. $text = array( 'sanitize_callback' => 'inspry_toolkit_sanitize_text' ); $html = array( 'sanitize_callback' => 'inspry_toolkit_sanitize_html' ); $url = array( 'sanitize_callback' => 'inspry_toolkit_sanitize_url' ); $number = array( 'sanitize_callback' => 'inspry_toolkit_sanitize_number' ); $users = array( 'sanitize_callback' => 'inspry_toolkit_sanitize_user_list' ); $plugins = array( 'sanitize_callback' => 'inspry_toolkit_sanitize_plugin_list' ); $keys = array( 'sanitize_callback' => 'inspry_toolkit_sanitize_key_list' ); $emails = array( 'sanitize_callback' => 'inspry_toolkit_sanitize_email_list' ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_backend_footer_agency_name', $text ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_backend_footer_agency_url', $url ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_backend_footer_agency_email', $text ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_backend_logo_image', $url ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_limit_post_revisions_number', $number ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_limit_admins_list', $users ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_maintenance_mode_heading', $html ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_maintenance_mode_sentence', $html ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_change_email_sender_address', $text ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_change_email_sender_name', $text ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_disable_installs_and_updates_list', $users ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_enable_plugin_update_admin_notifications_list', $users ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_enable_plugin_update_admin_notifications_plugin_list', $plugins ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_change_user_notifications_email_sender', $text ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_checksum_verification_for_wordpress_core_files_email_send', $text ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_remove_site_health_user_list', $users ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_limit_users_modify_admin_email_user_list', $users ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_disable_admin_notice_user_list', $users ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_development_environment_user_list', $users ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_development_environment_host_name', $text ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_manage_development_environment_banner_color', $keys ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_payments_test_mode_check_admin_notifications_list', $users ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_payments_disable_check_admin_notifications_list', $users ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_enable_plugin_activations_admin_notifications_list', $users ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_enable_plugin_activations_admin_notifications_plugin_list', $plugins ); register_setting( 'inspry_toolkit_options_group', 'inspry_toolkit_comment_notification_recipients_list', $emails ); } add_action( 'admin_init', 'inspry_toolkit_options_register' ); // Include media uploader script function function inspry_toolkit_options_page_enqueue( $hook ){ if( $hook == 'toplevel_page_inspry-agency-toolkit' || $hook == 'admin_page_inspry-toolkit-faqs' ){ wp_enqueue_media(); $plugin_version = INSPRY_TOOLKIT_CURRENT_VERSION; $select2_version = '4.1.0-rc.0'; wp_register_script( 'inspry_toolkit_select2_full', plugins_url( 'assets/lib/select2/select2.min.js', __FILE__ ), array( 'jquery' ), $select2_version, false ); wp_enqueue_script( 'inspry_toolkit_select2_full' ); wp_register_script( 'inspry_toolkit_options_page_script', plugins_url( 'assets/js/options-page-script.js', __FILE__ ), array( 'jquery', 'inspry_toolkit_select2_full' ), $plugin_version, false ); wp_enqueue_script( 'inspry_toolkit_options_page_script' ); wp_register_script( 'inspry_toolkit_form_validation_script', plugins_url( 'assets/js/form-validation-script.js', __FILE__ ), array( 'jquery', 'inspry_toolkit_select2_full' ), $plugin_version, true ); wp_enqueue_script( 'inspry_toolkit_form_validation_script' ); wp_register_style( 'inspry_toolkit_select2_min', plugins_url( 'assets/lib/select2/select2.min.css', __FILE__ ), array(), $select2_version ); wp_enqueue_style( 'inspry_toolkit_select2_min' ); wp_register_style( 'inspry_toolkit_options_page_style', plugins_url( 'assets/css/options-page-style.css', __FILE__ ), array( 'inspry_toolkit_select2_min' ), $plugin_version ); wp_enqueue_style( 'inspry_toolkit_options_page_style' ); } } add_action( 'admin_enqueue_scripts', 'inspry_toolkit_options_page_enqueue' ); // Add settings page content function inspry_toolkit_options_page(){ // Check if user is admin if ( is_admin() ) { // Check if limit admins option is enabled and that list is not empty ( to avoid an admin locking themselves out ) if ( get_option( 'inspry_toolkit_limit_admins' ) == 1 && !empty( get_option( 'inspry_toolkit_limit_admins_list' ) ) ) { // Check if this admin is included in the array if ( in_array( get_current_user_id(), get_option( 'inspry_toolkit_limit_admins_list' ) ) ) { // Limit admins enabled and user is in list, display page inspry_toolkit_options_page_html(); } else { // Limit admins enabled and user is not in list, display error inspry_toolkit_options_page_error(); } } else { // Limit admins disabled and this user is an admin, display page inspry_toolkit_options_page_html(); } } else { // User is not an admin role, display error inspry_toolkit_options_page_error(); } } // Options page HTML and JS function inspry_toolkit_options_page_html(){ ?>
You are not authorized to view this page. Please contact the site admin for more information.