'Version']); define('ADMIN_HERO_VERSION', $plugin_data['Version']); define('ADMIN_HERO_DIR', plugin_dir_path(__FILE__)); define('ADMIN_HERO_URL', plugin_dir_url(__FILE__)); // Dynamically find the latest feature loader file $feature_loader_files = glob(ADMIN_HERO_DIR . 'includes/class-feature-loader*.php'); $feature_loader_file = !empty($feature_loader_files) ? $feature_loader_files[0] : ADMIN_HERO_DIR . 'includes/class-feature-loader.php'; require_once $feature_loader_file; class Admin_Hero { private $feature_loader; public function __construct() { $this->feature_loader = new Admin_Hero_Feature_Loader(); $this->setup_hooks(); } private function setup_hooks() { add_action('plugins_loaded', [$this, 'load_features']); add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']); add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']); add_action('admin_footer', [$this, 'render_modal']); add_action('wp_footer', [$this, 'render_modal']); add_action('wp_ajax_admin_hero_save_note', [$this, 'save_note']); add_action('wp_ajax_admin_hero_refresh_nonce', [$this, 'refresh_nonce']); add_action('wp_ajax_admin_hero_save_settings', [$this, 'save_settings']); } public function load_features() { $this->feature_loader->load_features(ADMIN_HERO_DIR . 'features/'); } public function enqueue_assets() { if (!current_user_can('edit_posts')) { return; } // Dynamically find the latest CSS file $css_files = glob(ADMIN_HERO_DIR . 'assets/css/admin-hero*.css'); $css_file = !empty($css_files) ? basename($css_files[0]) : 'admin-hero.css'; wp_enqueue_style('admin-hero-css', ADMIN_HERO_URL . 'assets/css/' . $css_file, [], ADMIN_HERO_VERSION); // Dynamically find the latest JS file $js_files = glob(ADMIN_HERO_DIR . 'assets/js/admin-hero*.js'); $js_file = !empty($js_files) ? basename($js_files[0]) : 'admin-hero.js'; wp_enqueue_script('admin-hero-js', ADMIN_HERO_URL . 'assets/js/' . $js_file, ['jquery'], ADMIN_HERO_VERSION, true); // Font Awesome wp_enqueue_style('font-awesome', ADMIN_HERO_URL . 'assets/fontawesome/css/all.min.css', [], '6.7.2'); // Quill Editor (local assets) wp_enqueue_style('quill-css', ADMIN_HERO_URL . 'assets/quilleditor/quill.snow.css', [], '2.0.3'); wp_enqueue_script('quill-js', ADMIN_HERO_URL . 'assets/quilleditor/quill.min.js', [], '2.0.3', true); wp_localize_script('admin-hero-js', 'AdminHero', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('admin_hero_action'), 'version' => ADMIN_HERO_VERSION, 'is_admin' => is_admin(), 'features' => $this->feature_loader->get_features(), ]); } public function render_modal() { if (!current_user_can('edit_posts')) { return; } $user = wp_get_current_user(); $note = get_user_meta($user->ID, 'admin_hero_text', true); // admin-hero.php, in render_modal() $last_saved = get_user_meta( $user->ID, 'admin_hero_last_saved', true ); $timestamp = 'Last saved: ' . ( $last_saved ? date_i18n( ADMIN_HERO_TIMESTAMP_FORMAT, strtotime( $last_saved ) ) : 'Never saved' ); $nonce = wp_create_nonce('admin_hero_action'); // Allow Pro to hook before modal render do_action('admin_hero_render_modal_before', $user, $note, $timestamp, $nonce); // Let Pro override the modal template file $modal_files = glob(ADMIN_HERO_DIR . 'templates/modal*.php'); $modal_file = !empty($modal_files) ? $modal_files[0] : ADMIN_HERO_DIR . 'templates/modal.php'; $modal_file = apply_filters('admin_hero_modal_template', $modal_file); if (file_exists($modal_file)) { include $modal_file; } // Allow Pro to hook after modal render do_action('admin_hero_render_modal_after'); } public function save_note() { check_ajax_referer('admin_hero_action', 'nonce'); if (!current_user_can('edit_posts')) { wp_send_json_error(['message' => 'Unauthorized']); } $user_id = get_current_user_id(); $note = wp_kses_post(wp_unslash($_POST['note'] ?? '')); update_user_meta($user_id, 'admin_hero_text', $note); update_user_meta($user_id, 'admin_hero_last_saved', current_time('mysql')); wp_send_json_success([ 'message' => 'Note saved', 'timestamp' => date_i18n( ADMIN_HERO_TIMESTAMP_FORMAT, current_time( 'timestamp' ) ), ]); } public function refresh_nonce() { if (!current_user_can('edit_posts')) { wp_send_json_error(['message' => 'Unauthorized']); } wp_send_json_success(['nonce' => wp_create_nonce('admin_hero_action')]); } public function save_settings() { check_ajax_referer('admin_hero_action', 'nonce'); if ( ! current_user_can('edit_posts') ) { wp_send_json_error( ['message' => 'Unauthorized'] ); } // 1) Pull settings from POST via filter_input (avoids direct $_POST usage) $raw_settings = filter_input( INPUT_POST, 'settings', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ); if ( ! is_array( $raw_settings ) ) { $raw_settings = []; } // 2) Reverse slashes $raw_settings = wp_unslash( $raw_settings ); // 3) Sanitize keys & values $settings = []; foreach ( $raw_settings as $feature_id => $value ) { $feature_id = sanitize_key( $feature_id ); if ( is_array( $value ) ) { $value = array_map( 'sanitize_text_field', $value ); } else { $value = sanitize_text_field( $value ); } $settings[ $feature_id ] = $value; } $user_id = get_current_user_id(); // 4) Apply any feature‑specific sanitizers and save foreach ( $settings as $feature_id => $value ) { $feature = $this->feature_loader->get_feature( $feature_id ); if ( $feature && isset( $feature['sanitize_callback'] ) ) { $value = call_user_func( $feature['sanitize_callback'], $value ); } update_user_meta( $user_id, "admin_hero_feature_{$feature_id}", $value ); } wp_send_json_success( ['message' => 'Settings saved'] ); } } new Admin_Hero();