cls_file_reset(); } } if ( isset( $_GET['atFileReset'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['atFileReset'] ) ) && $this->should_save_site_ads() ) { $this->site_ads_files_reset(); } } /** * Resets cls files and echoes the results */ public function cls_file_reset() { header( 'Content-Type: application/json' ); try { $output = array(); $error = array(); $output['status'] = 'success'; $output['message'] = 'Saved all files'; $output['version'] = ADTHRIVE_ADS_VERSION; $output['result'] = $this->sync_cls_data(); echo wp_json_encode( $output ); exit(); } catch ( \Exception $e ) { http_response_code( 500 ); $output = array( 'status' => 'error', 'message' => $e->getMessage(), 'errors' => array( $e ), ); echo wp_json_encode( $output ); exit(); } } /** * Resets site ads files and echoes the results */ public function site_ads_files_reset() { header( 'Content-Type: application/json' ); try { $save_result = $this->save_site_ads_files(); $output = array(); $output['status'] = 'success'; $output['message'] = 'Saved all files'; $output['version'] = ADTHRIVE_ADS_VERSION; $output['errors'] = array(); foreach ( $save_result as $k => $k_value ) { if ( is_wp_error( $k_value ) ) { $output['status'] = 'error'; $output['message'] = 'Failed to save one or more files'; $output[ $k ] = $k_value->get_error_message(); $output['errors'][] = $k_value; } elseif ( false === $k_value ) { $output['status'] = 'error'; $output[ $k ] = 'Failed to save file'; $output['message'] = 'Failed to save one or more files'; $output['errors'][] = array( 'message' => 'Failed to save ' . $k ); } else { $output[ $k ] = 'Saved file'; } } if ( 'error' === $output['status'] ) { http_response_code( 500 ); } echo wp_json_encode( $output ); exit(); } catch ( \Exception $e ) { http_response_code( 500 ); $output = array( 'status' => 'error', 'message' => $e->getMessage(), 'errors' => array( $e ), ); echo wp_json_encode( $output ); exit(); } } /** * Returns if publisher needs site js saved */ public function should_save_site_ads() { $cls_optimization = \AdThrive_Ads\Options::get( 'cls_optimization' ); $amp_stories = \AdThrive_Ads\Options::get( 'amp_stories' ); return 'on' === $cls_optimization || 'on' === $amp_stories; } /** * Get deployment json from remote location */ public function get_deployment_json() { $remote = 'https://ads.adthrive.com/api/v1/core/cms/experiments?ts=' . strval( time() ); return $this->get_remote_file( $remote ); } /** * Update Deployment json and cls files */ public function sync_cls_data() { $cls_optimization = \AdThrive_Ads\Options::get( 'cls_optimization' ); $status = array(); $deployment_str = $this->get_deployment_json(); if ( $deployment_str && strlen( $deployment_str ) > 1 ) { do_action( 'adthrive_plugin_experiments_retrieved', $deployment_str ); try { $deployment = $this->parse_deployment_json( $deployment_str ); if ( isset( $deployment['stable'] ) ) { // Delete old CLS files before syncing (only when CLS optimization is on). // Must run before saving cls-deployments, since delete_cls_files removes that option. if ( 'on' === $cls_optimization ) { $this->delete_cls_files( false ); } // Always save deployment info so parse_cls_deployment works for all scripts. $deployment['updated_at'] = time(); \AdThrive_Ads\Options::save_to_option( 'cls-deployments', $deployment ); // Sync files - test branch first if exists, then stable. if ( isset( $deployment['test'] ) ) { $status[ $deployment['test'] ] = $this->get_cls_files( $deployment['test'], false ); } $status[ $deployment['stable'] ] = $this->get_cls_files( $deployment['stable'], true ); $status['deployment'] = $deployment; } } catch ( \Exception $e ) { $status['error'] = $e->getMessage(); } } else { $status['error'] = 'Failed to retrieve data from deployment json endpoint.'; } if ( isset( $status['error'] ) ) { $status['error_at'] = time(); \AdThrive_Ads\Options::save_to_option( 'cls-error', $status ); } return $status; } /** * Parse deployment json and return object */ public function parse_deployment_json( $deployment_str ) { $site_id = \AdThrive_Ads\Options::get( 'site_id' ); $deployment = array(); $feature_hashes = array(); $deploy_json = json_decode( $deployment_str ); if ( isset( $deploy_json->values ) ) { foreach ( $deploy_json->values as $feature_branch ) { $feature_hashes[] = $feature_branch->test; foreach ( $feature_branch->sites as $site ) { if ( $site === $site_id ) { $deployment['test'] = $feature_branch->test; break; } } } $deployment['stable'] = $deploy_json->stable; } $deployment['feature_hashes'] = $feature_hashes; return $deployment; } /** * Get CLS insertion files for given hash. * CLS-specific files only sync when CLS optimization is enabled. * Ad blocker detection keeps its CLS download when recovery is enabled. * * @param string $hash The build hash to sync. * @param bool $is_stable Whether this is the stable build. * @return array Status of each file save. */ public function get_cls_files( $hash, $is_stable ) { $cls_optimization = \AdThrive_Ads\Options::get( 'cls_optimization' ); $adblock_recovery = \AdThrive_Ads\Options::get( 'adblock_recovery' ); $base_url = 'https://ads.adthrive.com/builds/core/' . $hash . '/js/cls/'; $suffix = $is_stable ? 'stable' : $hash; $status = array(); // CLS-specific files - only sync when CLS optimization is enabled. if ( 'on' === $cls_optimization ) { $status['insertion'] = \AdThrive_Ads\Options::save_to_option( 'cls-insertion.' . $suffix, $this->get_remote_file( $base_url . 'cls-insertion.min.js' ) ); $status['header-insertion'] = \AdThrive_Ads\Options::save_to_option( 'cls-header-insertion.' . $suffix, $this->get_remote_file( $base_url . 'cls-header-insertion.min.js' ) ); $status['disable-ads'] = \AdThrive_Ads\Options::save_to_option( 'cls-disable-ads.' . $suffix, $this->get_remote_file( $base_url . 'cls-disable-ads.min.js' ) ); $status['comscore-loader'] = \AdThrive_Ads\Options::save_to_option( 'comscore-loader.' . $suffix, $this->get_remote_file( $base_url . 'comscore-loader.min.js' ) ); } // Detection keeps its existing CLS URL; recovery is updated separately. if ( 'off' !== $adblock_recovery ) { $status['adblock-detection'] = \AdThrive_Ads\Options::save_to_option( 'adblock-detection.' . $suffix, $this->get_remote_file( $base_url . 'adblock-detection.min.js' ) ); } return $status; } /** * Make request to remote location and return content */ public function get_remote_file( $remote ) { $response = wp_remote_get( $remote ); if ( is_wp_error( $response ) ) { return ''; } return wp_remote_retrieve_body( $response ); } /** * Delete existing cls files, if delete_all is true, it will delete all fines under insertion/min, otherwise keep stable files. * Note: adblock scripts are preserved as they operate independently of CLS optimization. */ public function delete_cls_files( $delete_all ) { require_once ABSPATH . 'wp-admin/includes/file.php'; if ( \WP_Filesystem() ) { global $wp_filesystem; if ( $wp_filesystem->is_file( 'cls-deployments.js' ) ) { $wp_filesystem->delete( 'cls-deployments.js' ); } if ( $wp_filesystem->is_file( 'cls-error.js' ) ) { $wp_filesystem->delete( 'cls-error.js' ); } $cls_files = list_files( ADTHRIVE_ADS_PATH . 'js/insertion/min' ); foreach ( $cls_files as $cls_file ) { if ( $wp_filesystem->is_file( $cls_file ) && ( ! preg_match( '/stable.js$/', $cls_file ) || $delete_all ) ) { $wp_filesystem->delete( $cls_file ); } } } \AdThrive_Ads\Options::remove_option( 'cls-deployments' ); \AdThrive_Ads\Options::remove_option( 'cls-error' ); $options = get_option( 'adthrive_options' ); foreach ( $options as $k => $option ) { // Skip adblock options - they operate independently of CLS optimization. if ( false !== strpos( $k, 'adblock-' ) ) { continue; } if ( false !== strpos( $k, 'cls' ) || false !== strpos( $k, 'comscore' ) ) { if ( ! preg_match( '/stable$/', $k ) || $delete_all ) { \AdThrive_Ads\Options::remove_option( $k ); } } } } /** * Save site js if publisher should */ public function sync_site_ads_data() { if ( $this->should_save_site_ads() ) { $this->save_site_ads_files(); } } /** * Get and save the latest site js and css file */ private function save_site_ads_files() { require_once ABSPATH . 'wp-admin/includes/file.php'; global $wp; $site_id = \AdThrive_Ads\Options::get( 'site_id' ); $remote = "https://ads.adthrive.com/api/v1/siteAds/$site_id?ts=" . strval( time() ); $successful_saves = array(); $response = wp_remote_get( $remote ); if ( is_wp_error( $response ) ) { $successful_saves['site_ads_json'] = $response; } else { $response_text = wp_remote_retrieve_body( $response ); if ( is_array( $response ) ) { $successful_saves['site_ads_json'] = \AdThrive_Ads\Options::save_to_option( 'site_js', $response_text ); } } $css_response = wp_remote_get( 'https://ads.adthrive.com/sites/' . $site_id . '/ads.min.css?ts=' . strval( time() ) ); if ( is_wp_error( $css_response ) ) { $successful_saves['css_content'] = $response; } else { $css_response_text = wp_remote_retrieve_body( $css_response ); $successful_saves['css_content'] = \AdThrive_Ads\Options::save_to_option( 'site_css', $css_response_text ); } return $successful_saves; } /** * Delete the site js and css file */ private function delete_site_ads_files() { require_once ABSPATH . 'wp-admin/includes/file.php'; if ( \WP_Filesystem() ) { global $wp_filesystem; $filename = ADTHRIVE_ADS_PATH . 'site.js'; $css_filename = ADTHRIVE_ADS_PATH . 'site.css'; if ( $wp_filesystem->is_file( $filename ) ) { $wp_filesystem->delete( $filename ); } if ( $wp_filesystem->is_file( $css_filename ) ) { $wp_filesystem->delete( $css_filename ); } } \AdThrive_Ads\Options::remove_option( 'site_js' ); \AdThrive_Ads\Options::remove_option( 'site_css' ); } /** * Called when the adthrive_ads option is updated */ public function options_updated( $old_value, $value ) { if ( isset( $value['cls_optimization'] ) && 'on' === $value['cls_optimization'] ) { $this->sync_cls_data(); $this->sync_site_ads_data(); } else { $this->delete_cls_files( true ); $this->delete_site_ads_files(); } } /** * Deployment json and cls files need to be updated when plugin is upgraded */ public function plugin_upgraded( $upgrader_object, $options ) { $current_plugin_path_name = plugin_basename( ADTHRIVE_ADS_FILE ); if ( 'update' === $options['action'] && 'plugin' === $options['type'] && isset( $options['plugins'] ) ) { foreach ( $options['plugins'] as $each_plugin ) { if ( $each_plugin === $current_plugin_path_name ) { $cls_optimization = \AdThrive_Ads\Options::get( 'cls_optimization' ); if ( 'on' === $cls_optimization ) { $this->sync_cls_data(); } $this->sync_site_ads_data(); } } } } }