[ 'icon' => 'dashicons-button', 'desc' => 'Styled CTA buttons with icons, animations and hover effects.' ], 'advanced-faq' => [ 'icon' => 'dashicons-editor-help', 'desc' => 'Searchable FAQ accordion with categories and smooth animations.' ], 'form' => [ 'icon' => 'dashicons-feedback', 'desc' => 'Multi-step forms with CAPTCHA, file upload, and conditional logic.' ], 'mega-menu' => [ 'icon' => 'dashicons-menu-alt', 'desc' => 'Responsive mega menus powered by Elementor templates.' ], 'advanced-slider' => [ 'icon' => 'dashicons-images-alt2', 'desc' => 'Full-featured content slider with animations and overlays.' ], 'advanced-tabs' => [ 'icon' => 'dashicons-index-card', 'desc' => 'Horizontal/vertical tabs with template support and URL hash.' ], 'advanced-search' => [ 'icon' => 'dashicons-search', 'desc' => 'Live AJAX search with results dropdown, keyword highlighting, and post-type filtering.' ], 'breadcrumbs' => [ 'icon' => 'dashicons-admin-links', 'desc' => 'Dynamic breadcrumb trail with full hierarchy, custom separator, and typography controls.' ], 'pricing-table' => [ 'icon' => 'dashicons-money-alt', 'desc' => 'Advanced pricing table with billing toggle, popular badge, tooltips, and comparison mode.' ], 'gallery-pro' => [ 'icon' => 'dashicons-format-gallery', 'desc' => 'Pro gallery with category filter, masonry/justified layout, lightbox, and load more.' ], 'post-grid' => [ 'icon' => 'dashicons-grid-view', 'desc' => 'Post grid/list/carousel with query builder, pagination, and rich styling.' ], 'off-canvas' => [ 'icon' => 'dashicons-menu-alt3', 'desc' => 'Slide-in drawer panel from any side with overlay and smooth animations.' ], 'stats-counter' => [ 'icon' => 'dashicons-chart-line', 'desc' => 'Animated number counters with scroll triggers and customizable easing.' ], 'before-after' => [ 'icon' => 'dashicons-image-filter', 'desc' => 'Interactive before/after image comparison with draggable slider.' ], 'logo-grid' => [ 'icon' => 'dashicons-images-alt', 'desc' => 'Client logo showcase in grid or carousel mode with hover effects.' ], 'team-member' => [ 'icon' => 'dashicons-businessperson', 'desc' => 'Showcase team members with photo, role, bio and social links in 4 card styles.' ], 'icon-box' => [ 'icon' => 'dashicons-star-filled', 'desc' => 'Icon + title + description box with top/left/right icon position and full style controls.' ], 'image-box' => [ 'icon' => 'dashicons-format-image', 'desc' => 'Image + title + description box with top/left/right image position, hover effects, and full style controls.' ], 'timeline' => [ 'icon' => 'dashicons-list-view', 'desc' => 'Vertical timeline with repeater items — date, title, description, icon — in left, right, or alternating layout with scroll animation.' ], 'fancy-heading' => [ 'icon' => 'dashicons-editor-textcolor', 'desc' => 'Decorative headings with prefix/suffix text, gradient or highlight fills, and letter-spacing controls.' ], 'image-carousel' => [ 'icon' => 'dashicons-images-alt2', 'desc' => 'Swiper-powered image carousel with lightbox, zoom hover, arrows, dots, and autoplay.' ], 'lottie' => [ 'icon' => 'dashicons-format-video', 'desc' => 'Renders Lottie JSON animations with autoplay, loop, speed, hover-trigger, and scroll-trigger.' ], 'pie-chart' => [ 'icon' => 'dashicons-chart-pie', 'desc' => 'Animated SVG donut chart with scroll-trigger, percentage label, stroke color/width, and typography controls.' ], 'author-box' => [ 'icon' => 'dashicons-admin-users', 'desc' => 'Post author avatar, name, bio, and social links with configurable size, tag, and card style controls.' ], 'related-posts' => [ 'icon' => 'dashicons-admin-post', 'desc' => 'Related posts grid by category or tag with thumbnail, title, date, and read-more link.' ], 'google-map' => [ 'icon' => 'dashicons-location-alt', 'desc' => 'Google Maps iframe embed by address or coordinates with configurable zoom and height.' ], 'woo-products' => [ 'icon' => 'dashicons-cart', 'desc' => 'WooCommerce product grid with category/tag filtering, columns, and full card style controls.' ], 'post-comments' => [ 'icon' => 'dashicons-admin-comments', 'desc' => 'Native WordPress comment list and form for the current post with typography/color controls.' ], 'woo-mini-cart' => [ 'icon' => 'dashicons-cart', 'desc' => 'WooCommerce cart icon with item count badge and flyout mini cart dropdown for headers.' ], 'woo-add-to-cart' => [ 'icon' => 'dashicons-plus-alt', 'desc' => 'Standalone Add to Cart button with quantity input for any product — great for landing pages.' ], 'site-logo' => [ 'icon' => 'dashicons-admin-appearance', 'desc' => 'Site logo from Customizer or custom image, with link, width, opacity, and CSS filter controls.' ], 'nav-menu' => [ 'icon' => 'dashicons-menu', 'desc' => 'WordPress navigation menu — horizontal or vertical, dropdown on hover, hamburger on mobile.' ], 'site-title' => [ 'icon' => 'dashicons-admin-settings', 'desc' => 'Site name (from Settings → General) with home link, HTML tag, and typography controls.' ], 'site-tagline' => [ 'icon' => 'dashicons-editor-textcolor', 'desc' => 'Site tagline (from Settings → General) with HTML tag and typography controls.' ], 'post-title' => [ 'icon' => 'dashicons-heading', 'desc' => 'Dynamic current-post title with HTML tag selector, optional permalink link, and typography controls.' ], 'post-content' => [ 'icon' => 'dashicons-editor-paragraph', 'desc' => 'Outputs the_content() of the current post/page — use inside Theme Builder single templates.' ], 'post-featured-image' => [ 'icon' => 'dashicons-format-image', 'desc' => 'Current post featured image with size picker, link target, border-radius, shadow, and hover filters.' ], 'post-excerpt' => [ 'icon' => 'dashicons-editor-quote', 'desc' => 'Post excerpt with configurable word limit and optional styled Read More link.' ], 'post-meta' => [ 'icon' => 'dashicons-info', 'desc' => 'Repeater of meta items: date, modified, author, categories, tags, comments, reading time, custom key.' ], 'archive-title' => [ 'icon' => 'dashicons-archive', 'desc' => 'Dynamic archive/category/tag/author page title with optional type prefix and typography controls.' ], 'archive-description' => [ 'icon' => 'dashicons-text-page', 'desc' => 'Taxonomy term description (category/tag description) for use in Theme Builder archive templates.' ], 'post-navigation' => [ 'icon' => 'dashicons-arrow-left-alt', 'desc' => 'Previous/Next post links with label, title, arrows, and full typography/color/border style controls.' ], 'animated-heading' => [ 'icon' => 'dashicons-editor-textcolor', 'desc' => 'Typewriter, rotating text, and highlight headline effects with rich typography controls.' ], 'countdown-timer' => [ 'icon' => 'dashicons-backup', 'desc' => 'Fixed-date and evergreen countdown with expiry actions, flip/circle/plain styles.' ], 'flip-box' => [ 'icon' => 'dashicons-image-rotate', 'desc' => '3D flip card with front and back faces, hover-triggered with multiple flip directions.' ], 'hotspot' => [ 'icon' => 'dashicons-location', 'desc' => 'Place interactive hotspot markers on an image with tooltip popups.' ], 'price-list' => [ 'icon' => 'dashicons-editor-ul', 'desc' => 'Restaurant/service style price list with image, title, description, and price.' ], 'progress-bar' => [ 'icon' => 'dashicons-minus', 'desc' => 'Animated horizontal progress bar with percentage label and scroll trigger.' ], 'social-icons' => [ 'icon' => 'dashicons-share', 'desc' => 'Social media icon set with brand colors, hover effects, and multiple shape styles.' ], 'testimonial' => [ 'icon' => 'dashicons-format-quote', 'desc' => 'Customer testimonial/review card with avatar, name, role, rating stars, and quote.' ], 'video' => [ 'icon' => 'dashicons-video-alt3', 'desc' => 'Self-hosted or embed video player with custom poster, play button overlay, and lightbox.' ], ]; private const EXTENSION_META = [ 'dynamic_tags' => [ 'icon' => 'dashicons-tag', 'desc' => 'Connect fields to post data, ACF, or site options dynamically.' ], 'sticky' => [ 'icon' => 'dashicons-sticky', 'desc' => 'Make any Elementor element stick to the viewport on scroll.' ], 'conditions' => [ 'icon' => 'dashicons-visibility', 'desc' => 'Show or hide any element based on user, date, or device rules.' ], 'custom_css' => [ 'icon' => 'dashicons-admin-appearance', 'desc' => 'Apply common style overrides (colors, spacing, border, sizing) to individual elements.' ], ]; private const ALL_EXTENSIONS = [ 'dynamic_tags' => 'Dynamic Tags', 'sticky' => 'Sticky', 'conditions' => 'Conditional Display', 'custom_css' => 'Custom Styles', ]; private const ALL_WIDGETS = [ 'advanced-button' => 'Advanced Button', 'advanced-faq' => 'FAQ Pro', 'form' => 'Advanced Form', 'mega-menu' => 'Mega Menu', 'advanced-slider' => 'Advanced Slider', 'advanced-tabs' => 'Advanced Tabs', 'advanced-search' => 'Advanced Search', 'breadcrumbs' => 'Breadcrumbs', 'pricing-table' => 'Pricing Table', 'gallery-pro' => 'Gallery Pro', 'post-grid' => 'Post Grid', 'off-canvas' => 'Off-Canvas Panel', 'stats-counter' => 'Stats Counter', 'before-after' => 'Before / After Image', 'logo-grid' => 'Logo Grid / Clients', 'team-member' => 'Team Member', 'icon-box' => 'Icon Box', 'image-box' => 'Image Box', 'timeline' => 'Timeline', 'fancy-heading' => 'Fancy Heading', 'image-carousel' => 'Image Carousel', 'lottie' => 'Lottie Animation', 'pie-chart' => 'Pie Chart', 'author-box' => 'Author Box', 'related-posts' => 'Related Posts', 'google-map' => 'Google Map', 'woo-products' => 'WooCommerce Products', 'post-comments' => 'Post Comments', 'woo-mini-cart' => 'WooCommerce Mini Cart', 'woo-add-to-cart' => 'WooCommerce Add to Cart', 'site-logo' => 'Site Logo', 'nav-menu' => 'Nav Menu', 'site-title' => 'Site Title', 'site-tagline' => 'Site Tagline', 'post-title' => 'Post Title', 'post-content' => 'Post Content', 'post-featured-image' => 'Post Featured Image', 'post-excerpt' => 'Post Excerpt', 'post-meta' => 'Post Meta', 'archive-title' => 'Archive Title', 'archive-description' => 'Archive Description', 'post-navigation' => 'Post Navigation', 'animated-heading' => 'Animated Heading', 'countdown-timer' => 'Countdown Timer', 'flip-box' => 'Flip Box', 'hotspot' => 'Hotspot', 'price-list' => 'Price List', 'progress-bar' => 'Progress Bar', 'social-icons' => 'Social Icons', 'testimonial' => 'Testimonial', 'video' => 'Video', ]; public static function get_widget_meta(): array { return self::WIDGET_META; } public static function get_all_widgets(): array { return self::ALL_WIDGETS; } 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' => '', ]; } 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-form-entries', 'eppro-theme-builder', 'eppro-templates' ], 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( self::ALL_WIDGETS ); $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::ALL_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['_version'] = EPPRO_VERSION; return $out; } private static function normalize_import_payload( array $decoded ): array { $out = self::defaults(); $all_widgets = array_keys( self::ALL_WIDGETS ); $all_ext = array_keys( self::ALL_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'] ?? '' ); 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'] ); $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( self::ALL_WIDGETS ); $active_w = $total_w - count( (array) ( $s['disabled_widgets'] ?? [] ) ); $total_e = count( self::ALL_EXTENSIONS ); $active_e = $total_e - count( (array) ( $s['disabled_extensions'] ?? [] ) ); ?>