[ title, description, dashicon ]. Translated, so built at runtime. */ public static function get_extensions(): array { return [ 'dynamic_tags' => [ 'title' => __( 'Dynamic Tags', 'admore-widgets-suite' ), 'icon' => 'dashicons-tag', 'desc' => __( 'Connect fields to post data, ACF, or site options dynamically.', 'admore-widgets-suite' ) ], 'sticky' => [ 'title' => __( 'Sticky', 'admore-widgets-suite' ), 'icon' => 'dashicons-sticky', 'desc' => __( 'Make any Elementor element stick to the viewport on scroll.', 'admore-widgets-suite' ) ], 'conditions' => [ 'title' => __( 'Conditional Display', 'admore-widgets-suite' ), 'icon' => 'dashicons-visibility', 'desc' => __( 'Show or hide any element based on user, date, or device rules.', 'admore-widgets-suite' ) ], 'custom_css' => [ 'title' => __( 'Custom Styles', 'admore-widgets-suite' ), 'icon' => 'dashicons-admin-appearance', 'desc' => __( 'Apply common style overrides (colors, spacing, border, sizing) to individual elements.', 'admore-widgets-suite' ) ], ]; } /** slug => [ icon, desc ] (translated). */ public static function get_widget_meta(): array { return array_map( static fn( $w ) => [ 'icon' => $w['icon'], 'desc' => $w['desc'] ], EPPRO_Widget_Registry::all() ); } /** slug => translated title. */ public static function get_all_widgets(): array { return EPPRO_Widget_Registry::titles(); } public static function init(): void { static $booted = false; if ( $booted ) return; $booted = true; add_action( 'admin_menu', [ __CLASS__, 'add_menu' ] ); add_action( 'admin_init', [ __CLASS__, 'register_settings' ] ); add_action( 'admin_init', [ __CLASS__, 'handle_import' ] ); add_action( 'wp_ajax_eppro_export_settings', [ __CLASS__, 'handle_export' ] ); add_action( 'wp_ajax_eppro_reset_settings', [ __CLASS__, 'ajax_reset_settings' ] ); add_action( 'wp_ajax_eppro_clear_cache', [ __CLASS__, 'ajax_clear_cache' ] ); add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_admin_assets' ] ); add_action( 'admin_enqueue_scripts', [ __CLASS__, 'output_menu_icon_css' ] ); add_action( 'update_option_' . self::OPTION_KEY, [ __CLASS__, 'flush_settings_cache' ] ); } /** Output inline CSS to correctly size the admin menu icon. */ public static function output_menu_icon_css(): void { $css = '#adminmenu #toplevel_page_eppro-settings .wp-menu-image{display:flex!important;align-items:center!important;justify-content:center!important}#adminmenu #toplevel_page_eppro-settings .wp-menu-image img{width:24px!important;height:24px!important;padding:0!important;display:block!important;border-radius:15%!important;background:#fff!important}'; wp_register_style( 'eppro-admin-icon', false, [], EPPRO_VERSION ); wp_enqueue_style( 'eppro-admin-icon' ); wp_add_inline_style( 'eppro-admin-icon', wp_strip_all_tags( $css ) ); } public static function defaults(): array { return [ '_version' => EPPRO_VERSION, 'disabled_widgets' => [], 'disabled_extensions' => [], // CAPTCHA 'captcha_type' => 'none', 'captcha_site_key' => '', 'captcha_secret' => '', // Form integrations (secret — never exported) 'mailchimp_api_key' => '', 'brevo_api_key' => '', ]; } public static function get_settings(): array { if ( null !== self::$settings_cache ) return self::$settings_cache; $saved = get_option( self::OPTION_KEY, [] ); self::$settings_cache = wp_parse_args( $saved, self::defaults() ); return self::$settings_cache; } /** Bust the in-memory cache after saving new settings. */ public static function flush_settings_cache(): void { self::$settings_cache = null; } public static function add_menu(): void { // Use dedicated admin icon SVG — no fixed width/height, only viewBox, // so WordPress renders it correctly as a scaled background image. $icon = EPPRO_URL . 'assets/icon.svg'; add_menu_page( __( 'Essential Pro Pack', 'admore-widgets-suite' ), __( 'Essential Pro Pack', 'admore-widgets-suite' ), 'manage_options', 'eppro-settings', [ __CLASS__, 'render_page' ], $icon, 58 ); add_submenu_page( 'eppro-settings', __( 'Settings', 'admore-widgets-suite' ), __( 'Settings', 'admore-widgets-suite' ), 'manage_options', 'eppro-settings', [ __CLASS__, 'render_page' ] ); } public static function enqueue_admin_assets( string $hook ): void { $eppro_hooks = [ 'toplevel_page_eppro-settings' ]; if ( class_exists( 'EPPRO_Form_Entries' ) && EPPRO_Form_Entries::get_hook() ) { $eppro_hooks[] = EPPRO_Form_Entries::get_hook(); } // Every plugin admin screen shares one design system (eppro-admin.css). $is_epp = in_array( $hook, $eppro_hooks, true ) || ( isset( $_GET['page'] ) && in_array( sanitize_key( wp_unslash( $_GET['page'] ) ), [ 'eppro-settings', 'eppro-popups', 'eppro-popup-analytics', 'eppro-form-entries', 'eppro-theme-builder', 'eppro-templates', 'eppro-template-library' ], true ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only decides which assets to load. if ( ! $is_epp ) return; wp_enqueue_style( 'eppro-admin-ui', EPPRO_URL . 'assets/css/eppro-admin.css', [], eppro_asset_ver( 'assets/css/eppro-admin.css' ) ); wp_enqueue_script( 'eppro-admin-ui', EPPRO_URL . 'assets/js/eppro-admin.js', [ 'jquery' ], eppro_asset_ver( 'assets/js/eppro-admin.js' ), true ); wp_localize_script( 'eppro-admin-ui', 'epproAdmin', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'eppro_admin_nonce' ), 'i18n' => [ 'confirmDuplicate' => __( 'Duplicate this template? A draft copy will be created.', 'admore-widgets-suite' ), 'confirmDisableAllWidgets' => __( 'Disable all visible widgets? You can enable them again before saving.', 'admore-widgets-suite' ), 'confirmUnsavedTabSwitch' => __( 'You have unsaved changes in this tab. Switch anyway?', 'admore-widgets-suite' ), 'unsavedChanges' => __( 'You have unsaved changes. Are you sure you want to leave?', 'admore-widgets-suite' ), 'requestFailed' => __( 'Request failed.', 'admore-widgets-suite' ), 'requestFailedTryAgain' => __( 'Request failed. Please try again.', 'admore-widgets-suite' ), 'saved' => __( 'Saved.', 'admore-widgets-suite' ), 'done' => __( 'Done.', 'admore-widgets-suite' ), 'error' => __( 'Error.', 'admore-widgets-suite' ), 'duplicationFailed' => __( 'Duplication failed.', 'admore-widgets-suite' ), 'enableAtLeastOneWidget' => __( 'Please keep at least one widget enabled.', 'admore-widgets-suite' ), ], ] ); } public static function register_settings(): void { register_setting( 'eppro_group', self::OPTION_KEY, [ __CLASS__, 'sanitize' ] ); } public static function sanitize( array $input ): array { // When called with an already-processed settings array (e.g. from the Setup // Wizard or ajax_reset_settings via update_option/delete_option), the input // has 'disabled_widgets' but no raw form keys like '_context' or 'widget_active'. // Without this guard, the missing 'widget_active' key would be interpreted as // "no widgets active" and overwrite disabled_widgets with the full widget list. if ( ! isset( $input['_context'] ) && ! isset( $input['widget_active'] ) && ! isset( $input['extension_active'] ) ) { return wp_parse_args( $input, self::defaults() ); } $out = self::get_settings(); $context = sanitize_key( $input['_context'] ?? 'all' ); // Widget manager: only rebuild when the widgets form was submitted if ( 'all' === $context || 'widgets' === $context ) { $all_keys = array_keys( EPPRO_Widget_Registry::all() ); $active = array_keys( (array) ( $input['widget_active'] ?? [] ) ); $out['disabled_widgets'] = array_values( array_diff( $all_keys, $active ) ); } // Extension manager: only rebuild when the extensions form was submitted if ( 'all' === $context || 'extensions' === $context ) { $all_ext = array_keys( self::get_extensions() ); $active_e = array_keys( (array) ( $input['extension_active'] ?? [] ) ); $out['disabled_extensions'] = array_values( array_diff( $all_ext, $active_e ) ); } // CAPTCHA — only update when integrations form was submitted if ( 'all' === $context || 'integrations' === $context ) { $allowed_types = [ 'none', 'recaptcha_v2', 'recaptcha_v3', 'hcaptcha' ]; $captcha_type = sanitize_key( $input['captcha_type'] ?? 'none' ); $out['captcha_type'] = in_array( $captcha_type, $allowed_types, true ) ? $captcha_type : 'none'; $out['captcha_site_key'] = sanitize_text_field( $input['captcha_site_key'] ?? '' ); $out['captcha_secret'] = sanitize_text_field( $input['captcha_secret'] ?? '' ); $out['mailchimp_api_key'] = sanitize_text_field( $input['mailchimp_api_key'] ?? '' ); $out['brevo_api_key'] = sanitize_text_field( $input['brevo_api_key'] ?? '' ); } $out['_version'] = EPPRO_VERSION; return $out; } private static function normalize_import_payload( array $decoded ): array { $out = self::defaults(); $all_widgets = array_keys( EPPRO_Widget_Registry::all() ); $all_ext = array_keys( self::get_extensions() ); $disabled_widgets = array_map( 'sanitize_key', (array) ( $decoded['disabled_widgets'] ?? [] ) ); $disabled_ext = array_map( 'sanitize_key', (array) ( $decoded['disabled_extensions'] ?? [] ) ); $out['disabled_widgets'] = array_values( array_intersect( $all_widgets, $disabled_widgets ) ); $out['disabled_extensions'] = array_values( array_intersect( $all_ext, $disabled_ext ) ); $allowed_types = [ 'none', 'recaptcha_v2', 'recaptcha_v3', 'hcaptcha' ]; $import_captcha_type = sanitize_key( $decoded['captcha_type'] ?? 'none' ); $out['captcha_type'] = in_array( $import_captcha_type, $allowed_types, true ) ? $import_captcha_type : 'none'; $out['captcha_site_key'] = sanitize_text_field( $decoded['captcha_site_key'] ?? '' ); // Exports leave the secret out; keep the one already saved on this site. $out['captcha_secret'] = isset( $decoded['captcha_secret'] ) ? sanitize_text_field( $decoded['captcha_secret'] ) : (string) ( self::get_settings()['captcha_secret'] ?? '' ); // API keys are never exported either: keep this site's keys. foreach ( [ 'mailchimp_api_key', 'brevo_api_key' ] as $secret_key ) { $out[ $secret_key ] = isset( $decoded[ $secret_key ] ) ? sanitize_text_field( $decoded[ $secret_key ] ) : (string) ( self::get_settings()[ $secret_key ] ?? '' ); } if ( isset( $decoded['wizard_notify_email'] ) ) { $notify = sanitize_email( $decoded['wizard_notify_email'] ); if ( $notify ) { $out['wizard_notify_email'] = $notify; } } elseif ( ! empty( self::get_settings()['wizard_notify_email'] ) ) { $out['wizard_notify_email'] = sanitize_email( self::get_settings()['wizard_notify_email'] ); } $out['_version'] = EPPRO_VERSION; return $out; } public static function handle_export(): void { if ( ! current_user_can( 'manage_options' ) ) wp_die( -1 ); check_ajax_referer( 'eppro_export_nonce', 'nonce' ); // Never put the CAPTCHA secret key in a file that may be shared; // importing keeps the secret already stored on the target site. $settings = self::get_settings(); unset( $settings['captcha_secret'], $settings['mailchimp_api_key'], $settings['brevo_api_key'] ); $data = (string) wp_json_encode( $settings, JSON_PRETTY_PRINT ); header( 'Content-Type: application/json; charset=UTF-8' ); header( 'Content-Disposition: attachment; filename="eppro-settings-' . gmdate( 'Y-m-d' ) . '.json"' ); header( 'Content-Length: ' . strlen( $data ) ); echo $data; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON file download, not HTML. exit; } public static function ajax_reset_settings(): void { check_ajax_referer( 'eppro_admin_nonce', '_wpnonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'admore-widgets-suite' ) ], 403 ); } delete_option( self::OPTION_KEY ); self::flush_settings_cache(); wp_send_json_success( [ 'message' => __( 'Settings reset to defaults successfully.', 'admore-widgets-suite' ) ] ); } public static function ajax_clear_cache(): void { check_ajax_referer( 'eppro_admin_nonce', '_wpnonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'admore-widgets-suite' ) ], 403 ); } $cleared = []; // Clear Elementor CSS/asset cache if ( class_exists( '\Elementor\Plugin' ) && isset( \Elementor\Plugin::$instance->files_manager ) ) { \Elementor\Plugin::$instance->files_manager->clear_cache(); $cleared[] = __( 'Elementor CSS cache cleared.', 'admore-widgets-suite' ); } // Delete EPP transients (no core API to delete by prefix). global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( 'DELETE FROM %i WHERE option_name LIKE %s OR option_name LIKE %s', $wpdb->options, $wpdb->esc_like( '_transient_eppro_' ) . '%', $wpdb->esc_like( '_transient_timeout_eppro_' ) . '%' ) ); wp_cache_flush(); $cleared[] = __( 'EPP transients cleared.', 'admore-widgets-suite' ); wp_send_json_success( [ 'message' => implode( ' ', $cleared ), ] ); } private static function get_system_info(): string { global $wpdb; $theme = wp_get_theme(); $el_ver = defined( 'ELEMENTOR_VERSION' ) ? ELEMENTOR_VERSION : __( 'Not installed', 'admore-widgets-suite' ); $active_plugins = []; foreach ( get_option( 'active_plugins', [] ) as $plugin_file ) { if ( ! function_exists( 'get_plugin_data' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $d = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file, false, false ); if ( ! empty( $d['Name'] ) ) { $active_plugins[] = ' ' . $d['Name'] . ' ' . $d['Version']; } } $db_version = $wpdb->db_server_info(); $lines = [ '### Essential Pro Pack — System Info ###', '', '-- Plugin --', 'EPP Version : ' . EPPRO_VERSION, '', '-- WordPress --', 'WordPress Version : ' . get_bloginfo( 'version' ), 'Site URL : ' . get_bloginfo( 'url' ), 'Language : ' . get_locale(), 'Multisite : ' . ( is_multisite() ? 'Yes' : 'No' ), 'WP_DEBUG : ' . ( defined( 'WP_DEBUG' ) && WP_DEBUG ? 'Enabled' : 'Disabled' ), '', '-- Elementor --', 'Elementor Version : ' . $el_ver, '', '-- Active Theme --', 'Theme Name : ' . $theme->get( 'Name' ) . ' ' . $theme->get( 'Version' ), 'Theme Author : ' . $theme->get( 'Author' ), '', '-- Server --', 'PHP Version : ' . phpversion(), 'PHP Memory Limit : ' . ini_get( 'memory_limit' ), 'PHP Max Upload : ' . ini_get( 'upload_max_filesize' ), 'PHP Max Post Size : ' . ini_get( 'post_max_size' ), 'PHP Time Limit : ' . ini_get( 'max_execution_time' ) . 's', 'Database Server : ' . ( $db_version ?: 'Unknown' ), 'Server Software : ' . sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown' ) ), '', '-- Active Plugins (' . count( $active_plugins ) . ') --', implode( "\n", $active_plugins ), ]; return implode( "\n", $lines ); } public static function handle_import(): void { if ( ! isset( $_POST['eppro_import_submit'] ) ) return; if ( ! current_user_can( 'manage_options' ) ) return; check_admin_referer( 'eppro_import_nonce', 'eppro_import_nonce_field' ); if ( ! isset( $_FILES['eppro_import_file']['name'], $_FILES['eppro_import_file']['tmp_name'], $_FILES['eppro_import_file']['error'], $_FILES['eppro_import_file']['size'] ) ) { $file = null; } else { $file = [ 'name' => sanitize_file_name( wp_unslash( $_FILES['eppro_import_file']['name'] ) ), 'tmp_name' => sanitize_text_field( $_FILES['eppro_import_file']['tmp_name'] ), 'error' => absint( $_FILES['eppro_import_file']['error'] ), 'size' => absint( $_FILES['eppro_import_file']['size'] ), ]; } if ( empty( $file['tmp_name'] ) || ! is_uploaded_file( $file['tmp_name'] ) ) { add_settings_error( 'eppro_tools', 'no_file', __( 'No file uploaded.', 'admore-widgets-suite' ), 'error' ); return; } $finfo = finfo_open( FILEINFO_MIME_TYPE ); $mime = finfo_file( $finfo, $file['tmp_name'] ); finfo_close( $finfo ); if ( ! in_array( $mime, [ 'application/json', 'text/plain' ], true ) ) { add_settings_error( 'eppro_tools', 'bad_mime', __( 'Invalid file type. Please upload a JSON file.', 'admore-widgets-suite' ), 'error' ); return; } global $wp_filesystem; if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); $content = $wp_filesystem->get_contents( $file['tmp_name'] ); $decoded = json_decode( $content, true ); if ( ! is_array( $decoded ) ) { add_settings_error( 'eppro_tools', 'bad_json', __( 'Invalid JSON file.', 'admore-widgets-suite' ), 'error' ); return; } $allowed_import_keys = [ '_version', 'disabled_widgets', 'disabled_extensions', 'captcha_type', 'captcha_site_key', 'captcha_secret', 'wizard_notify_email', ]; $unknown_keys = array_diff( array_keys( $decoded ), $allowed_import_keys ); update_option( self::OPTION_KEY, self::normalize_import_payload( $decoded ) ); if ( ! empty( $unknown_keys ) ) { add_settings_error( 'eppro_tools', 'import_unknown_keys', sprintf( /* translators: %d: unknown key count in imported JSON */ __( 'Import completed. %d unsupported keys were ignored for safety.', 'admore-widgets-suite' ), count( $unknown_keys ) ), 'warning' ); } add_settings_error( 'eppro_tools', 'imported', __( 'Settings imported successfully.', 'admore-widgets-suite' ), 'updated' ); } public static function render_page(): void { if ( ! current_user_can( 'manage_options' ) ) return; $s = self::get_settings(); $k = self::OPTION_KEY; $total_w = count( EPPRO_Widget_Registry::all() ); $active_w = $total_w - count( (array) ( $s['disabled_widgets'] ?? [] ) ); $total_e = count( self::get_extensions() ); $active_e = $total_e - count( (array) ( $s['disabled_extensions'] ?? [] ) ); ?>

v

[ 'label' => __( 'Core Widgets', 'admore-widgets-suite' ), 'icon' => 'dashicons-screenoptions', 'slugs' => [] ], 'woocommerce' => [ 'label' => __( 'WooCommerce', 'admore-widgets-suite' ), 'icon' => 'dashicons-cart', 'slugs' => [] ], 'theme-builder' => [ 'label' => __( 'Theme Builder', 'admore-widgets-suite' ), 'icon' => 'dashicons-admin-appearance','slugs' => [] ], ]; foreach ( array_keys( EPPRO_Widget_Registry::all() ) as $slug ) { if ( in_array( $slug, $theme_builder_slugs, true ) ) { $groups['theme-builder']['slugs'][] = $slug; } elseif ( in_array( $slug, $woo_slugs, true ) ) { $groups['woocommerce']['slugs'][] = $slug; } else { $groups['core']['slugs'][] = $slug; } } ?> $group ) : ?>
$wslug, 'icon' => 'dashicons-admin-generic', 'desc' => '' ]; $wname = $meta['title']; $enabled = ! in_array( $wslug, (array) ( $s['disabled_widgets'] ?? [] ), true ); ?>

$meta ) : $ename = $meta['title']; $enabled = ! in_array( $eslug, (array) ( $s['disabled_extensions'] ?? [] ), true ); ?>

reCAPTCHA: google.com/recaptcha  ·  hCaptcha: hcaptcha.com', 'admore-widgets-suite' ), [ 'strong' => [], 'a' => [ 'href' => [], 'target' => [] ] ] ), 'https://www.google.com/recaptcha/admin', 'https://www.hcaptcha.com/' ); ?>

$svc_name ) : $row = $int_status[ $svc ] ?? null; ?>
— ()