true, 'unit' => $code]; continue; } if ($position === 'reward') { $formats['rewarded'] = $formats['rewarded'] ?? [ 'enabled' => true, 'unit' => $code, 'prompt' => [ 'headline' => 'Watch a short ad to unlock this content', 'buttonText' => 'Watch ad', 'dismissText' => 'No thanks', ], ]; continue; } if ($position === 'anchor') { if (!isset($formats['anchor']) && get_option('adghost_enable_anchor', true)) { [$mobile, $desktop] = self::split_anchor_sizes($sizes); $formats['anchor'] = [ 'enabled' => true, 'mode' => 'native', 'position' => ($unit['anchorPosition'] ?? 'bottom') === 'top' ? 'top' : 'bottom', 'unit' => $code, 'fallback' => true, 'fallbackSizes' => null, 'closeable' => true, 'sizes' => ['mobile' => $mobile, 'desktop' => $desktop], ]; } continue; } if (in_array($position, AdGhost_Helper::screen_positions(), true)) { continue; } if (isset($seen[$code])) { continue; } $seen[$code] = true; $entry = [ 'id' => $code, 'sizes' => $sizes, ]; if (!empty($unit['targeting']) && is_array($unit['targeting'])) { $entry['targeting'] = $unit['targeting']; } if ($position === 'header') { $entry['priority'] = 'high'; } $format = AdGhost_Ad_Container::behaviour_formats()[AdGhost_Helper::served_feature($unit)] ?? null; if ($format !== null) { $entry['format'] = $format; $entry['formatOptions'] = $format === 'floating' ? ['variant' => 'on-pass', 'corner' => self::corner($unit)] : [ 'mode' => $format === 'elevator' ? 'smooth' : 'css', 'topOffsetPx' => min(400, absint($unit['stickyOffset'] ?? 16)), 'align' => ($unit['elevatorAlign'] ?? 'top') === 'center' ? 'center' : 'top', 'desktopOnly' => ($unit['devices'] ?? '') === 'desktop', ]; if (!in_array($format, ['elevator', 'floating'], true)) { $entry['formatOptions'] = $unit['options']; } } $refresh = !empty($unit['refresh']) || (bool) get_option('adghost_enable_auto_refresh', false); $any_refresh = $any_refresh || $refresh; if (!$refresh) { $entry['refresh'] = false; } if (!empty($unit['backfillPath'])) { $entry['backfillPath'] = (string) $unit['backfillPath']; } $units[] = $entry; } $ivt_mode = AdGhost_IVT_Manager::mode(); $config = [ 'networkCode' => $network_code, 'units' => $units, 'formats' => $formats, 'lazyLoad' => ['enabled' => (bool) get_option('adghost_enable_lazy_loading', true)], 'loadAfter' => in_array(get_option('adghost_load_after', 'load'), ['load', 'interactive', 'immediate'], true) ? get_option('adghost_load_after', 'load') : 'load', 'cls' => ['mode' => AdGhost_Helper::cls_mode()], 'performance' => [ 'maxAds' => 30, 'adaptSlowNetwork' => (bool) get_option('adghost_adapt_slow_network', true), ], 'adblock' => [ 'detect' => $adblock_on, 'title' => $adblock_on ? AdGhost_Helper::adblock_text('title') : null, 'messageHtml' => $adblock_on ? AdGhost_Helper::adblock_text('message') : null, 'buttonLabel' => $adblock_on ? AdGhost_Helper::adblock_text('button') : null, 'dismissLabel' => $adblock_on ? AdGhost_Helper::adblock_text('dismiss') : null, ], 'ivt' => [ 'enabled' => AdGhost_IVT_Manager::enabled(), 'mode' => $ivt_mode !== 'monitor' ? 'no-ads' : 'monitor', ], 'privacy' => self::privacy_config(), 'ivtEnabled' => AdGhost_IVT_Manager::enabled(), 'ivtSample' => AdGhost_IVT_Manager::enabled() ? self::ivt_sample_rate($ivt_mode) : 0, 'ivtEndpoint' => esc_url_raw(rest_url('adghost/v1/ivt/verdict')), ]; if ($any_refresh) { $config['refresh'] = [ 'enabled' => true, 'intervalSeconds' => max(30, (int) get_option('adghost_auto_refresh_interval', 30)), ]; } $config = (array) apply_filters('adghost_runtime_config', $config, AdGhost_Helper::units()); if (empty($config['formats'])) { $config['formats'] = new stdClass(); } return $config; } private static function privacy_config() { $mode = (string) get_option('adghost_consent_mode', 'auto'); if (!in_array($mode, ['auto', 'npa', 'off'], true)) { $mode = 'auto'; } return [ 'auto' => $mode === 'auto', 'nonPersonalizedAds' => $mode === 'npa', ]; } private static function ivt_sample_rate($ivt_mode) { if ($ivt_mode !== 'monitor') { return 1.0; } $rate = (float) apply_filters('adghost_ivt_sample_rate', 0.25); return max(0.0, min(1.0, $rate)); } private static function size_list($sizes) { $out = []; foreach (preg_split('/[;,]/', (string) $sizes) as $size) { $size = strtolower(trim($size)); if ($size === 'fluid' || preg_match('/^\d{1,4}x\d{1,4}$/', $size)) { $out[] = $size; } } return $out ? implode(',', array_unique($out)) : '300x250'; } private static function split_anchor_sizes($sizes) { $mobile = $desktop = []; foreach (explode(',', $sizes) as $size) { if (!preg_match('/^(\d+)x(\d+)$/', $size, $m) || (int) $m[1] <= 1 || (int) $m[2] > 120) { continue; } if ((int) $m[1] >= 728) { $desktop[] = $size; } else { $mobile[] = $size; } } return [ $mobile ? implode(',', $mobile) : '320x50,320x100', $desktop ? implode(',', $desktop) : '728x90', ]; } private static function corner(array $unit) { $corner = (string) ($unit['corner'] ?? 'bottom-right'); return in_array($corner, ['bottom-right', 'bottom-left', 'top-right', 'top-left'], true) ? $corner : 'bottom-right'; } public function format_device_rules() { $family_of = (array) apply_filters('adghost_format_families', [ 'anchor' => 'anchor', 'interstitial' => 'interstitial', 'reward' => 'rewarded', ]); $rules = []; foreach (AdGhost_Helper::units() as $unit) { $family = $family_of[AdGhost_Helper::get_unit_position($unit)] ?? null; if ($family === null || isset($rules[$family]) || $unit['devices'] === '') { continue; } $rules[$family] = explode(',', $unit['devices']); } return $rules; } public static function runtime_source() { return get_option('adghost_runtime_source', 'site') === 'custom' && self::custom_runtime_url() !== '' ? 'custom' : 'site'; } public static function custom_runtime_url() { $url = trim((string) get_option('adghost_runtime_url', '')); return preg_match('#^(https?:)?//#i', $url) ? $url : ''; } public function generate_engine_script() { $template_path = $this->template_path(); if ($template_path === null) { return false; } $build = self::engine_build(); return '/*! AdGhost engine build ' . $build . ' for plugin ' . ADGHOST_VERSION . ". Host this file anywhere; the page supplies the config. */\n" . 'window.__ADGHOST_ENGINE_BUILD__=' . wp_json_encode($build) . ";\n" . str_replace(['{{CONFIG}}', '{{ CONFIG }}'], '(window.__ADGHOST_CONFIG__||{})', (string) file_get_contents($template_path)); } public static function engine_build() { $template = (new self())->template_path(); return $template ? substr(md5_file($template), 0, 10) : ''; } public function generate_local_script() { $config = $this->build_config(); if ($config === null) { return false; } $files = $this->source_files($config); if (!$files) { return false; } $script = implode(";\n", array_map('file_get_contents', $files)); $script = str_replace( ['{{CONFIG}}', '{{ CONFIG }}'], wp_json_encode($config, JSON_HEX_TAG | JSON_HEX_AMP), $script ); return $script; } public function get_local_script_url($force = false) { $config = $this->build_config(); if ($config === null) { return false; } $sources = []; foreach ($this->source_files($config) as $file) { $sources[] = basename($file) . '@' . filemtime($file) . ':' . filesize($file); } $signature = md5(wp_json_encode($config) . '|' . implode('|', $sources)); $stored = get_option('adghost_local_script', []); if (!$force && is_array($stored) && ($stored['signature'] ?? '') === $signature && !empty($stored['path']) && file_exists($stored['path'])) { $uploads = wp_upload_dir(null, false); return empty($uploads['error']) ? trailingslashit($uploads['baseurl']) . 'adghost/' . basename($stored['path']) : (string) $stored['url']; } $lock = 'adghost_building_runtime'; if (get_transient($lock)) { $stored = get_option('adghost_local_script', []); if (is_array($stored) && !empty($stored['path']) && file_exists($stored['path'])) { $uploads = wp_upload_dir(null, false); return empty($uploads['error']) ? trailingslashit($uploads['baseurl']) . 'adghost/' . basename($stored['path']) : (string) $stored['url']; } return false; } set_transient($lock, 1, 30); $script = $this->generate_local_script(); if ($script === false) { delete_transient($lock); return false; } $hash = substr(md5($script), 0, 12); $filename = 'adghost-' . $hash . '.js'; $uploads = wp_upload_dir(); if (!empty($uploads['error'])) { delete_transient($lock); return false; } $dir = trailingslashit($uploads['basedir']) . 'adghost'; $path = $dir . '/' . $filename; $url = trailingslashit($uploads['baseurl']) . 'adghost/' . $filename; if (!file_exists($path)) { if (!wp_mkdir_p($dir)) { delete_transient($lock); return false; } if (!function_exists('WP_Filesystem')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } global $wp_filesystem; if (!WP_Filesystem() || !$wp_filesystem->put_contents($path, $script, FS_CHMOD_FILE)) { delete_transient($lock); return false; } $this->prune_old_scripts($dir, $filename); } update_option('adghost_local_script', [ 'signature' => $signature, 'url' => $url, 'path' => $path, 'bytes' => strlen($script), 'built' => current_time('mysql'), ]); delete_transient($lock); return $url; } const PARTS = ['config', 'detector', 'engine', 'kit', 'floating', 'reporter']; public function parts(array $config) { $format_units = false; $float = false; foreach ((array) ($config['units'] ?? []) as $unit) { if (!empty($unit['format'])) { $format_units = true; $float = $float || $unit['format'] === 'floating'; } } foreach (AdGhost_Helper::units() as $unit) { if (AdGhost_Helper::served_feature($unit) === 'float') { $float = true; } } $ivt = class_exists('AdGhost_IVT_Manager') && AdGhost_IVT_Manager::enabled(); $pro = AdGhost_Helper::pro_active(); $want = [ 'config' => true, 'detector' => $ivt, 'engine' => true, 'kit' => $float || $format_units || $pro, 'floating' => $float, 'reporter' => $ivt, ]; return array_keys(array_filter($want)); } public function source_files(array $config) { $dir = ADGHOST_PLUGIN_DIR . 'cdn-free/parts/'; $files = []; foreach ($this->parts($config) as $part) { $file = $dir . $part . '.js'; if (!is_readable($file)) { $files = null; break; } $files[] = $file; } if ($files) { return $files; } $template = $this->template_path(); return $template ? [$template] : []; } public function template_path() { foreach (['cdn-free/adghost-lite.js', 'cdn/adghost-lite.js'] as $rel) { if (file_exists(ADGHOST_PLUGIN_DIR . $rel)) { return ADGHOST_PLUGIN_DIR . $rel; } } return null; } private function prune_old_scripts($dir, $keep) { $files = glob($dir . '/adghost-*.js'); if (!is_array($files) || count($files) <= 2) { return; } usort($files, function ($a, $b) { return filemtime($b) <=> filemtime($a); }); $kept = 0; foreach ($files as $file) { if (basename($file) === $keep) { continue; } $kept++; if ($kept > 1) { wp_delete_file($file); } } } public static function delete_local_scripts() { $uploads = wp_upload_dir(null, false); if (empty($uploads['error']) && !empty($uploads['basedir'])) { $dir = trailingslashit($uploads['basedir']) . 'adghost'; foreach ((array) glob($dir . '/adghost-*.js') as $file) { if (is_file($file)) { wp_delete_file($file); } } if (is_dir($dir) && count((array) scandir($dir)) === 2) { if (!function_exists('WP_Filesystem')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } global $wp_filesystem; if (WP_Filesystem()) { $wp_filesystem->rmdir($dir); } } } delete_option('adghost_local_script'); } }