init_components(); $this->init_hooks(); } private function init_components() { $this->admin_page = new AdGhost_Admin_Page(); $this->script_injector = new AdGhost_Script_Injector(); $this->placement_engine = new AdGhost_Placement_Engine(); $this->script_generator = new AdGhost_Script_Generator(); $this->insertion_engine = new AdGhost_Insertion_Engine(); $this->debug_mode = AdGhost_Debug_Mode::get_instance($this->insertion_engine); $this->rest_controller = new AdGhost_REST_Controller(); } private function init_hooks() { add_action('admin_menu', [$this->admin_page, 'register_menu']); add_action('admin_enqueue_scripts', [$this->admin_page, 'enqueue_assets']); add_action('admin_init', ['AdGhost_Setup_Guide', 'handle_actions']); add_action('admin_post_adghost_review', ['AdGhost_Admin_Page', 'handle_review_ask']); add_action('admin_post_adghost_export_settings', ['AdGhost_Settings_Transfer', 'handle_export']); add_action('admin_post_adghost_import_settings', ['AdGhost_Settings_Transfer', 'handle_import']); add_action('init', ['AdGhost_Setup_Guide', 'guard'], 1); add_filter('admin_body_class', [$this, 'add_admin_body_class']); add_action('wp_ajax_adghost_save_config', [$this, 'handle_save_config']); add_action('wp_ajax_adghost_implement', [$this, 'handle_implement']); add_action('wp_ajax_adghost_export_csv', ['AdGhost_CSV_Exporter', 'download']); add_action('wp_ajax_adghost_import_csv', [$this, 'handle_import_csv']); add_action('wp_ajax_adghost_import_plugins', [$this, 'handle_import_plugins']); add_action('wp_ajax_adghost_download_engine', [$this, 'handle_download_engine']); add_action('wp_ajax_adghost_settings_mode', [$this, 'handle_settings_mode']); add_action('wp_ajax_adghost_hide_support', [$this, 'handle_hide_support']); $this->rest_controller->init(); /* Portions derived from NoAdCode (https://noadcode.com), GPL v2 or later. */ $this->placement_engine->init(); (new AdGhost_IVT_Manager())->init(); $this->debug_mode->init(); add_action('init', function () { $is_preview = get_option('adghost_debug_mode', false) && current_user_can('manage_options'); if (get_option('adghost_is_active') || $is_preview) { $this->script_injector->init(); } }, 20); add_action('adghost_schedule_boundary', function () { AdGhost_Helper::purge_page_cache(); }); (new AdGhost_Page_Exclusions())->init(); AdGhost_AMP::init(); AdGhost_Ads_Txt::init(); AdGhost_Header_Footer_Code::init(); AdGhost_Consent_Helper::init(); } public function add_admin_body_class($classes) { if (!get_option('adghost_is_active', false)) { $classes .= ' adghost-inactive'; } return $classes; } public function handle_save_config() { check_ajax_referer('adghost_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('Unauthorized', 'adghost-lite')]); } $data = self::sanitize_request(wp_unslash($_POST)); $result = $this->apply_config($data); if (!$result['success']) { wp_send_json_error(['message' => $result['error']]); } $not_runtime = ['action', 'nonce', 'deploy', 'ad_units_sent', 'ads_txt_mode', 'ads_txt_content', 'debug_mode', 'ad_label_type', 'ad_label_custom', 'ad_label_bold', 'ad_label_align', 'device_detection']; $runtime_keys = array_filter(array_diff(array_keys($data), $not_runtime), function ($key) { return strpos($key, 'code_') !== 0; }); $wants_deploy = !empty($data['deploy']) || (get_option('adghost_is_active', false) && !empty($runtime_keys)); if (!$wants_deploy) { wp_send_json_success(['message' => __('Settings saved.', 'adghost-lite'), 'deployed' => false]); } if (!empty($data['ad_units_sent']) && empty(get_option('adghost_ad_units', []))) { update_option('adghost_is_active', false); wp_send_json_success([ 'message' => __('Saved. There are no ad units, so ads are off until you add one.', 'adghost-lite'), 'deployed' => false, ]); } $deploy = $this->activate_runtime(); if (!$deploy['success']) { wp_send_json_error([ /* translators: %s: reason the deploy failed. */ 'message' => sprintf(__('Saved, but not deployed: %s', 'adghost-lite'), $deploy['error']), ]); } wp_send_json_success([ 'message' => __('Saved and deployed.', 'adghost-lite'), 'deployed' => true, ]); } public static function sanitize_request($value, $key = '') { if (is_object($value)) { $value = (array) $value; } if (is_array($value)) { $clean = []; foreach ($value as $name => $item) { $name = is_int($name) ? $name : sanitize_text_field((string) $name); $clean[$name] = self::sanitize_request($item, (string) $name); } return $clean; } if (!is_string($value)) { return is_scalar($value) ? $value : null; } if ($key === 'adblock_message') { return wp_kses_post($value); } if (in_array($key, ['code_head', 'code_body', 'code_footer'], true)) { return current_user_can('unfiltered_html') ? $value : wp_kses_post($value); } if ($key === 'runtime_url') { return esc_url_raw($value, ['https', 'http']); } if (stripos($key, 'url') !== false || stripos($key, 'referrer') !== false) { return wp_strip_all_tags(wp_check_invalid_utf8($value)); } return sanitize_textarea_field($value); } public function apply_config(array $data) { $data = self::sanitize_request($data); $has = function ($key) use ($data) { return array_key_exists($key, $data); }; $bool = function ($key) use ($data) { return (bool) absint($data[$key] ?? 0) || $data[$key] === true || $data[$key] === 'true'; }; if ($has('network_code')) { $network = trim(sanitize_text_field((string) $data['network_code'])); if ($network !== '' && !preg_match('/^\d{4,12}(,\d{4,12})?$/', $network)) { return ['success' => false, 'error' => __('The Google Ad Manager network code is the number from your GAM account, for example 21700000001.', 'adghost-lite')]; } update_option('adghost_network_code', $network); } if ($has('ad_units') || !empty($data['ad_units_sent'])) { update_option('adghost_ad_units', $this->sanitize_ad_units($data['ad_units'] ?? [])); AdGhost_Helper::flush_units_cache(); self::schedule_cache_purges(); update_option('adghost_units_saved', true); } foreach (['auto_incontent', 'enable_anchor', 'enable_cls_prevention', 'debug_mode', 'enable_lazy_loading', 'adapt_slow_network'] as $key) { if ($has($key)) { update_option('adghost_' . $key, $bool($key) ? 1 : 0); } } if ($has('cls_mode') && in_array($data['cls_mode'], ['max', 'min', 'off'], true)) { update_option('adghost_cls_mode', $data['cls_mode']); update_option('adghost_enable_cls_prevention', $data['cls_mode'] !== 'off'); } elseif ($has('enable_cls_prevention')) { update_option('adghost_cls_mode', $bool('enable_cls_prevention') ? 'max' : 'off'); } if ($has('load_after') && in_array($data['load_after'], ['load', 'interactive', 'immediate'], true)) { update_option('adghost_load_after', $data['load_after']); } if ($has('consent_mode')) { $consent = (string) $data['consent_mode']; update_option('adghost_consent_mode', in_array($consent, ['auto', 'npa', 'off'], true) ? $consent : 'auto'); } if ($has('privacy_link')) { update_option('adghost_privacy_link', $bool('privacy_link') ? 1 : 0); } if ($has('adblock_message')) { update_option('adghost_adblock_message', wp_kses_post((string) $data['adblock_message'])); } if ($has('ad_label_color')) { $color = trim((string) $data['ad_label_color']); update_option('adghost_ad_label_color', preg_match('/^#[0-9a-fA-F]{6}$/', $color) ? strtolower($color) : ''); } foreach (['adblock_title', 'adblock_button', 'adblock_dismiss'] as $key) { if ($has($key)) { update_option('adghost_' . $key, sanitize_text_field((string) $data[$key])); } } if ($has('incontent_frequency')) { update_option('adghost_incontent_frequency', max(1, absint($data['incontent_frequency']))); } if ($has('max_ads_per_page')) { update_option('adghost_max_ads_per_page', absint($data['max_ads_per_page'])); } if ($has('min_words_between')) { update_option('adghost_min_words_between', min(2000, absint($data['min_words_between']))); } foreach (array_keys(AdGhost_Paragraph_Rules::DEFAULTS) as $rule_key) { if (!$has($rule_key)) { continue; } $value = $data[$rule_key]; update_option( 'adghost_' . $rule_key, $rule_key === 'paragraph_tags' ? sanitize_text_field((string) $value) : ($rule_key === 'count_inside_blockquote' ? filter_var($value, FILTER_VALIDATE_BOOLEAN) : absint($value)) ); } if ($has('place_before_headings')) { update_option('adghost_place_before_headings', $bool('place_before_headings') ? 1 : 0); } $targeting = get_option('adghost_targeting', []); $targeting = is_array($targeting) ? $targeting : []; $list = function ($key, $pattern) use ($data) { $out = []; foreach (preg_split('/[\r\n,]+/', wp_unslash((string) $data[$key])) as $item) { $item = substr(trim(preg_replace($pattern, '', $item)), 0, 200); if ($item !== '') { $out[] = $item; } } return array_slice(array_values(array_unique($out)), 0, 200); }; foreach (['exclude_urls' => '/[^\w\/\-\.\*\?%~]/u', 'exclude_referrers' => '/[\s<>"\'\\\\]/'] as $key => $pattern) { if ($has($key)) { $targeting[$key] = $list($key, $pattern); } } foreach (['exclude_post_ids', 'include_post_ids'] as $key) { if ($has($key)) { $targeting[$key] = array_values(array_filter(array_map('absint', $list($key, '/\D/')))); } } // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- an array key, not a query argument. foreach (['exclude' => 'exclude_terms', 'include' => 'include_terms'] as $side => $key) { foreach (['category' => '_categories', 'post_tag' => '_tags'] as $taxonomy => $suffix) { if ($has($side . $suffix)) { $targeting[$key] = (array) ($targeting[$key] ?? []); $targeting[$key][$taxonomy] = array_map('sanitize_title', $list($side . $suffix, '/[<>"]/')); } } } if ($has('hide_logged_in')) { $targeting['hide_logged_in'] = in_array($data['hide_logged_in'], AdGhost_Targeting::LOGGED_IN, true) ? $data['hide_logged_in'] : ''; } update_option('adghost_targeting', $targeting); if (class_exists('AdGhost_Key_Values')) { AdGhost_Key_Values::save($data, $has); } if ($has('ivt_mode') && !(defined('ADGHOST_IVT_DISABLED') && ADGHOST_IVT_DISABLED)) { $ivt_mode = (string) $data['ivt_mode']; if ($ivt_mode === 'off') { update_option('adghost_ivt_enabled', false); } else { update_option('adghost_ivt_enabled', true); update_option('adghost_ivt_mode', in_array($ivt_mode, AdGhost_IVT_Manager::available_modes(), true) ? $ivt_mode : 'monitor'); } delete_option('adghost_ivt_notice'); } foreach (['enable_floating', 'enable_sticky_sidebar', 'enable_interstitial', 'enable_auto_refresh', 'enable_adblock_detection', 'enable_video'] as $key) { if ($has($key)) { update_option('adghost_' . $key, $bool($key)); } } if ($has('auto_refresh_interval')) { update_option('adghost_auto_refresh_interval', max(30, absint($data['auto_refresh_interval']))); } if ($has('ad_label_type')) { $label_type = (string) $data['ad_label_type']; update_option('adghost_ad_label_type', in_array($label_type, ['default', 'custom', 'hidden'], true) ? $label_type : 'default'); } if ($has('ad_label_custom')) { update_option('adghost_ad_label_custom', sanitize_text_field((string) $data['ad_label_custom'])); } if ($has('ad_label_bold')) { update_option('adghost_ad_label_bold', !empty($data['ad_label_bold']) && $data['ad_label_bold'] !== '0'); } if ($has('ad_label_align')) { $label_align = (string) $data['ad_label_align']; update_option('adghost_ad_label_align', in_array($label_align, ['left', 'center', 'right'], true) ? $label_align : ''); } if ($has('runtime_source')) { $source = (string) $data['runtime_source'] === 'custom' ? 'custom' : 'site'; $url = trim(esc_url_raw((string) ($data['runtime_url'] ?? ''), ['https', 'http'])); if ($source === 'custom' && $url === '') { return ['success' => false, 'error' => __('Enter the full URL of the AdGhost engine file on your server, starting with https://.', 'adghost-lite')]; } update_option('adghost_runtime_source', $source); update_option('adghost_runtime_url', $url); } if ($has('no_ads_pages')) { $picked = array_filter(array_map('sanitize_key', explode(',', (string) $data['no_ads_pages']))); $rules = []; foreach (array_keys(AdGhost_Page_Exclusions::RULES) as $key) { $rules[$key] = in_array($key, $picked, true); } update_option('adghost_no_ads_pages', $rules); } if ($has('device_detection')) { update_option('adghost_device_detection', (string) $data['device_detection'] === 'server' ? 'server' : 'browser'); } if ($has('ads_txt_mode')) { $managed = ((string) $data['ads_txt_mode'] === 'managed'); update_option('adghost_ads_txt_managed', $managed); if ($managed && $has('ads_txt_content')) { if (!AdGhost_Ads_Txt::write_ads_txt(wp_strip_all_tags((string) $data['ads_txt_content']))) { return ['success' => false, 'error' => __('Could not write ads.txt. Check that the site root is writable.', 'adghost-lite')]; } } } $code_error = AdGhost_Header_Footer_Code::save($data); if ($code_error !== null) { return ['success' => false, 'error' => $code_error]; } do_action('adghost_save_settings', $data); AdGhost_Helper::purge_page_cache(); return ['success' => true]; } public function get_config() { return [ 'network_code' => get_option('adghost_network_code', ''), 'ad_units' => array_values((array) get_option('adghost_ad_units', [])), 'auto_incontent' => (bool) get_option('adghost_auto_incontent', true), 'incontent_frequency' => (int) get_option('adghost_incontent_frequency', 3), 'enable_anchor' => (bool) get_option('adghost_enable_anchor', true), 'enable_lazy_loading' => (bool) get_option('adghost_enable_lazy_loading', true), 'adapt_slow_network' => (bool) get_option('adghost_adapt_slow_network', true), 'consent_mode' => (string) get_option('adghost_consent_mode', 'auto'), 'privacy_link' => (bool) get_option('adghost_privacy_link', false), 'enable_cls_prevention' => (bool) get_option('adghost_enable_cls_prevention', true), 'cls_mode' => AdGhost_Helper::cls_mode(), 'load_after' => get_option('adghost_load_after', 'load'), 'max_ads_per_page' => (int) get_option('adghost_max_ads_per_page', 5), 'debug_mode' => (bool) get_option('adghost_debug_mode', false), 'ivt_mode' => get_option('adghost_ivt_mode', 'monitor'), 'ivt_effective_mode' => AdGhost_IVT_Manager::mode(), 'ivt_signal_count' => AdGhost_Signal_Registry::count(), 'min_paragraphs' => (int) get_option('adghost_min_paragraphs', AdGhost_Paragraph_Rules::DEFAULTS['min_paragraphs']), 'max_paragraphs' => (int) get_option('adghost_max_paragraphs', AdGhost_Paragraph_Rules::DEFAULTS['max_paragraphs']), 'skip_first_paragraphs' => (int) get_option('adghost_skip_first_paragraphs', AdGhost_Paragraph_Rules::DEFAULTS['skip_first_paragraphs']), 'skip_last_paragraphs' => (int) get_option('adghost_skip_last_paragraphs', AdGhost_Paragraph_Rules::DEFAULTS['skip_last_paragraphs']), 'min_words_above' => (int) get_option('adghost_min_words_above', AdGhost_Paragraph_Rules::DEFAULTS['min_words_above']), 'min_paragraph_words' => (int) get_option('adghost_min_paragraph_words', AdGhost_Paragraph_Rules::DEFAULTS['min_paragraph_words']), 'paragraph_tags' => (string) get_option('adghost_paragraph_tags', AdGhost_Paragraph_Rules::DEFAULTS['paragraph_tags']), 'count_inside_blockquote' => (bool) get_option('adghost_count_inside_blockquote', AdGhost_Paragraph_Rules::DEFAULTS['count_inside_blockquote']), 'enable_floating' => (bool) get_option('adghost_enable_floating', false), 'enable_sticky_sidebar' => (bool) get_option('adghost_enable_sticky_sidebar', false), 'enable_interstitial' => (bool) get_option('adghost_enable_interstitial', false), 'enable_auto_refresh' => (bool) get_option('adghost_enable_auto_refresh', false), 'auto_refresh_interval' => (int) get_option('adghost_auto_refresh_interval', 30), 'enable_adblock_detection'=> (bool) get_option('adghost_enable_adblock_detection', false), 'adblock_message' => (string) get_option('adghost_adblock_message', ''), 'ad_label_color' => AdGhost_Helper::ad_label_color(), 'adblock_title' => (string) get_option('adghost_adblock_title', ''), 'adblock_button' => (string) get_option('adghost_adblock_button', ''), 'adblock_dismiss' => (string) get_option('adghost_adblock_dismiss', ''), 'enable_video' => (bool) get_option('adghost_enable_video', false), 'ad_label_type' => get_option('adghost_ad_label_type', 'default'), 'device_detection' => AdGhost_Targeting::device_mode(), 'ad_label_custom' => get_option('adghost_ad_label_custom', 'Advertisement'), 'ad_label_bold' => (bool) get_option('adghost_ad_label_bold', false), 'ad_label_align' => AdGhost_Helper::ad_label_align(), 'ads_txt_managed' => (bool) get_option('adghost_ads_txt_managed', false), 'ads_txt_virtual' => AdGhost_Ads_Txt::is_virtual(), 'ads_txt_content' => AdGhost_Ads_Txt::read_ads_txt(), 'is_active' => (bool) get_option('adghost_is_active', false), 'last_deployed' => get_option('adghost_last_deployed', ''), 'site_id' => get_option('adghost_site_id', ''), 'header_footer_code' => AdGhost_Header_Footer_Code::all(), 'capabilities' => AdGhost_Helper::capabilities(), ]; } public function activate_runtime() { $network_code = get_option('adghost_network_code', ''); if (empty($network_code)) { return ['success' => false, 'error' => 'Network Code is required']; } $ad_units = get_option('adghost_ad_units', []); if (empty($ad_units)) { return ['success' => false, 'error' => 'At least one ad unit is required']; } if ($this->script_generator->generate_local_script() === false) { return ['success' => false, 'error' => 'Failed to generate local runtime']; } $this->script_generator->get_local_script_url(true); delete_option('adghost_script_url'); delete_option('adghost_deploy_warnings'); update_option('adghost_is_active', true); update_option('adghost_last_deployed', current_time('mysql')); add_option('adghost_first_deployed', time(), '', false); AdGhost_Setup_Guide::stamp(); AdGhost_Helper::purge_page_cache(); return ['success' => true]; } public function handle_implement() { check_ajax_referer('adghost_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized']); } $result = $this->activate_runtime(); if ($result['success']) { wp_send_json_success([ 'message' => 'Implementation successful! Ads are now live.', ]); } else { wp_send_json_error(['message' => $result['error']]); } } public function handle_download_engine() { check_ajax_referer('adghost_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die(esc_html__('Unauthorized', 'adghost-lite'), 403); } $script = $this->script_generator->generate_engine_script(); if ($script === false) { wp_die(esc_html__('The engine file is missing from the plugin. Reinstall AdGhost.', 'adghost-lite'), 500); } nocache_headers(); header('Content-Type: application/javascript; charset=utf-8'); header('Content-Disposition: attachment; filename="adghost-engine-' . AdGhost_Script_Generator::engine_build() . '.js"'); header('Content-Length: ' . strlen($script)); echo $script; // phpcs:ignore WordPress.Security.EscapeOutput -- a JavaScript file download. exit; } public function handle_import_csv() { check_ajax_referer('adghost_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized']); } $tmp_name = isset($_FILES['csv_file']['tmp_name']) ? sanitize_text_field(wp_unslash($_FILES['csv_file']['tmp_name'])) : ''; if ($tmp_name === '' || !is_uploaded_file($tmp_name)) { wp_send_json_error(['message' => 'No file uploaded']); } require_once plugin_dir_path(__FILE__) . 'class-csv-importer.php'; $result = AdGhost_CSV_Importer::parse($tmp_name); if (is_wp_error($result)) { wp_send_json_error(['message' => $result->get_error_message()]); } wp_send_json_success(['ad_units' => $result]); } public function handle_import_plugins() { check_ajax_referer('adghost_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized']); } wp_send_json_success(AdGhost_Plugin_Importer::run()); } private static function spacing_value($value) { if ($value === '' || $value === null || !is_numeric($value)) { return ''; } return (int) min(AdGhost_Ad_Container::SPACING_MAX, max(0, (int) $value)); } private static function class_list($value) { $classes = array_map(function ($part) { return preg_replace('/[^a-z0-9_-]/i', '', $part); }, explode(',', wp_unslash((string) $value))); return substr(implode(', ', array_unique(array_filter($classes, 'strlen'))), 0, 200); } public function handle_settings_mode() { check_ajax_referer('adghost_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('You are not allowed to do that.', 'adghost-lite')], 403); } $mode = isset($_POST['mode']) && sanitize_key(wp_unslash($_POST['mode'])) === 'expert' ? 'expert' : 'simple'; update_user_meta(get_current_user_id(), AdGhost_Admin_Page::MODE_META, $mode); wp_send_json_success(['mode' => $mode]); } public function handle_hide_support() { check_ajax_referer('adghost_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error([], 403); } update_user_meta(get_current_user_id(), AdGhost_Admin_Page::SUPPORT_HIDDEN_META, 1); wp_send_json_success(); } public function clean_ad_units($units) { return $this->sanitize_ad_units($units); } private function sanitize_ad_units($units) { if (!is_array($units)) return []; $existing_units = get_option('adghost_ad_units', []); $existing_ids = array_column($existing_units, 'id'); $existing_ids = array_combine($existing_ids, array_fill(0, count($existing_ids), true)); $sanitized = []; $used_ids = []; foreach ($units as $unit) { $name = sanitize_text_field($unit['name'] ?? ''); if (empty($name)) continue; $given = AdGhost_Helper::sanitize_gam_code($unit['id'] ?? ''); $id = $given !== '' ? $given : $this->get_unique_id($name, $used_ids, $existing_ids); $used_ids[$id] = true; $sanitized[] = $this->map_ad_unit_data($unit, $id); } return $sanitized; } private function get_unique_id($name, $used_ids, $existing_ids) { $id = AdGhost_Helper::generate_id($name); $base_id = $id; $counter = 1; while (isset($used_ids[$id]) || isset($existing_ids[$id])) { $id = $base_id . '_' . $counter++; } return $id; } private function map_ad_unit_data($unit, $id) { $flag = function ($key, $default = false) use ($unit) { return array_key_exists($key, $unit) ? filter_var($unit[$key], FILTER_VALIDATE_BOOLEAN) : $default; }; $pick = function ($key, array $allowed, $default) use ($unit) { $value = (string) ($unit[$key] ?? ''); return in_array($value, $allowed, true) ? $value : $default; }; $normal = AdGhost_Helper::normalize_unit([ 'type' => sanitize_text_field($unit['type'] ?? 'standard'), 'position' => sanitize_key($unit['position'] ?? ''), 'feature' => sanitize_key($unit['feature'] ?? 'none'), 'isAnchor' => $flag('isAnchor'), 'floatToCorner' => $flag('floatToCorner'), 'spotSticky' => $flag('spotSticky'), 'devices' => $unit['devices'] ?? (['desktop' => 'desktop', 'mobile' => 'tablet,mobile'][$unit['device'] ?? ''] ?? ''), 'pages' => $unit['pages'] ?? '', 'enabled' => $flag('enabled', true), 'align' => $unit['align'] ?? 'center', ]); $devices = $normal['devices']; $selector = trim(preg_replace('/[<{}\\\\]/', '', wp_unslash((string) ($unit['selector'] ?? '')))); return [ 'id' => $id, 'name' => sanitize_text_field($unit['name'] ?? ''), 'slotName' => sanitize_text_field($unit['slotName'] ?? 'div-gpt-ad-' . $id), 'sizes' => sanitize_text_field($unit['sizes'] ?? '300x250'), 'type' => $normal['type'], 'enabled' => $normal['enabled'], 'isDefault' => $flag('isDefault'), 'isLazy' => true, 'position' => $normal['position'], 'positionChosen' => array_key_exists('position', $unit) ? 1 : 0, 'placementMode' => (($unit['placementMode'] ?? 'auto') === 'manual') ? 'manual' : 'auto', 'insertionRule' => $pick('insertionRule', ['every_n', 'after_n', 'before_n', 'before_last', 'auto'], 'every_n'), 'insertionValue' => max(1, absint($unit['insertionValue'] ?? 3)), 'insertionParagraphs' => AdGhost_Rule_Selector::sanitize_positions((string) ($unit['insertionParagraphs'] ?? '')), 'paragraphText' => substr(sanitize_text_field((string) ($unit['paragraphText'] ?? '')), 0, 200), 'paragraphTextMode' => $pick('paragraphTextMode', ['', 'contain', 'exclude'], ''), 'scheduleStart' => self::date_value($unit['scheduleStart'] ?? ''), 'scheduleEnd' => self::date_value($unit['scheduleEnd'] ?? ''), 'insertionLimit' => absint($unit['insertionLimit'] ?? 0), 'minSpacing' => min(20, absint($unit['minSpacing'] ?? 0)), 'excludeSelectors' => self::class_list($unit['excludeSelectors'] ?? ''), 'label' => sanitize_text_field($unit['label'] ?? ''), 'gamPlacement' => sanitize_text_field($unit['gamPlacement'] ?? ''), 'backfillPath' => preg_match('#^/\d{4,12}/[A-Za-z0-9._\-/ ]{1,120}$#', trim((string) ($unit['backfillPath'] ?? ''))) ? trim((string) $unit['backfillPath']) : '', 'selector' => substr($selector, 0, 200), 'selectorPosition' => $pick('selectorPosition', AdGhost_Zones::SELECTOR_PLACES, 'after'), 'sidebarId' => sanitize_key($unit['sidebarId'] ?? ''), 'anchorPosition' => $pick('anchorPosition', ['bottom', 'top'], 'bottom'), 'corner' => $pick('corner', ['bottom-right', 'bottom-left', 'top-right', 'top-left'], 'bottom-right'), 'slideFrom' => $pick('slideFrom', ['right', 'left'], 'right'), 'dockSide' => $pick('dockSide', ['right', 'left'], 'right'), 'feature' => $normal['feature'], 'stickyOffset' => min(400, absint($unit['stickyOffset'] ?? 16)), 'elevatorAlign' => $pick('elevatorAlign', ['top', 'center'], 'top'), 'travelHeight' => min(3000, absint($unit['travelHeight'] ?? AdGhost_Ad_Container::TRAVEL_DEFAULT)), 'spaceAbove' => self::spacing_value($unit['spaceAbove'] ?? ''), 'spaceBelow' => self::spacing_value($unit['spaceBelow'] ?? ''), 'innerPadding' => self::spacing_value($unit['innerPadding'] ?? ''), 'isAnchor' => $normal['position'] === 'anchor', 'floatToCorner' => $normal['feature'] === 'float', 'spotSticky' => $normal['feature'] === 'sticky', 'refresh' => $flag('refresh'), 'targeting' => class_exists('AdGhost_Key_Values') ? AdGhost_Key_Values::sanitize_pairs($unit['targeting'] ?? []) : [], 'options' => AdGhost_Helper::sanitize_options($unit['options'] ?? []), 'devices' => $devices, 'device' => $devices === '' ? 'both' : ($devices === 'desktop' ? 'desktop' : (strpos($devices, 'desktop') === false ? 'mobile' : 'both')), 'pages' => $normal['pages'], 'align' => $normal['align'], 'context' => sanitize_text_field($unit['context'] ?? 'article'), 'idWarning' => AdGhost_Helper::validate_id($id), 'isImported' => $flag('isImported'), 'importedFrom' => $flag('isImported') ? substr(sanitize_text_field((string) ($unit['importedFrom'] ?? '')), 0, 40) : '', 'gam' => $this->sanitize_gam_snapshot($unit['gam'] ?? null), ]; } private static function date_value($value) { $value = trim((string) $value); $date = DateTime::createFromFormat('!Y-m-d', $value); return ($date && $date->format('Y-m-d') === $value) ? $value : ''; } public static function schedule_cache_purges() { wp_unschedule_hook('adghost_schedule_boundary'); $now = time(); $times = []; foreach ((array) get_option('adghost_ad_units', []) as $unit) { foreach (['scheduleStart' => 0, 'scheduleEnd' => 1] as $key => $days_after) { if (empty($unit[$key])) { continue; } try { $at = (new DateTimeImmutable($unit[$key] . ' 00:00:00', wp_timezone()))->modify('+' . $days_after . ' day')->getTimestamp(); } catch (Exception $e) { continue; } if ($at > $now) { $times[$at] = true; } } } foreach (array_keys($times) as $at) { wp_schedule_single_event($at, 'adghost_schedule_boundary', [$at]); } } private function sanitize_gam_snapshot($gam) { if (!is_array($gam) || AdGhost_Helper::sanitize_gam_code($gam['code'] ?? '') === '') { return null; } return [ 'code' => AdGhost_Helper::sanitize_gam_code($gam['code']), 'sizes' => sanitize_text_field($gam['sizes'] ?? ''), 'placement' => sanitize_text_field($gam['placement'] ?? ''), 'at' => sanitize_text_field($gam['at'] ?? ''), ]; } public function get_admin_page() { return $this->admin_page; } public function get_script_injector() { return $this->script_injector; } public function get_placement_engine() { return $this->placement_engine; } public function get_script_generator() { return $this->script_generator; } }