hooks(); } return self::$instance; } private function __construct() { } /** * Registers all necessary hooks for the admin side of the module. */ public function hooks(): void { add_filter( 'display_post_states', [ $this, 'mark_auto_ads_post_states' ], 10, 2 ); add_action( 'before_delete_post', [ $this, 'handle_auto_ads_deletion' ] ); } /** * Adds an 'AdSense Auto Ad' or 'AdSense Auto Placement' state to the post list table. * * @param array $states * @param WP_Post $post * * @return array */ public function mark_auto_ads_post_states( array $states, WP_Post $post ): array { $ad_id = Settings::get_setting( 'auto_ads_ad_id', 0 ); $placement_id = Settings::get_setting( 'auto_ads_placement_id', 0 ); if ( $post->ID === $ad_id ) { $states['adpresso_adsense_ad'] = __( 'AdSense Auto Ad', 'adpresso' ); } elseif ( $post->ID === $placement_id ) { $states['adpresso_adsense_placement'] = __( 'AdSense Auto Placement', 'adpresso' ); } return $states; } /** * Creates and publishes the necessary Ad and Placement for Auto Ads. * * @return void */ public function create_entities(): void { if ( ! check_ajax_referer( SettingsTab::CREATE_ENTITIES_AJAX_ACTION, '_ajax_nonce', false ) ) { wp_send_json_error( [ 'message' => __( 'Invalid nonce.', 'adpresso' ) ], 403 ); } // Capability check (Admin-only) if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Permission denied.', 'adpresso' ) ], 403 ); } $placement_id = Params::post()->getId( 'placement_id', 0 ); $ad_id = Params::post()->getId( 'ad_id', 0 ); $placement_exists = $placement_id && get_post_status( $placement_id ); $ad_exists = $ad_id && get_post_status( $ad_id ); if ( $placement_exists && $ad_exists ) { RuntimeDebugger::add_server_error_log_entry( 'Auto Ads: Placement and Ad already exist. Skipping creation.' ); wp_update_post( [ 'ID' => $placement_id, 'post_status' => 'publish' ] ); wp_update_post( [ 'ID' => $ad_id, 'post_status' => 'publish' ] ); wp_send_json_error( [ 'message' => __( 'Ad and placement already exist.', 'adpresso' ) ], 403 ); return; } $settings = get_option( 'adpresso_settings_adsense' ); $publisher_id = $settings['adsense_publisher_id'] ?? ''; if ( empty( $publisher_id ) ) { wp_send_json_error( [ 'message' => __( 'Please set your Publisher ID first.', 'adpresso' ) ], 403 ); return; } // Create ad $new_ad_id = $this->create_auto_ads_ad( $publisher_id ); if ( ! $new_ad_id ) { RuntimeDebugger::add_server_error_log_entry( 'Auto Ads: Failed to create Ad. Skipping creation.' ); wp_send_json_error( [ 'message' => __( 'Failed to create Ad. Skipping creation.', 'adpresso' ) ], 403 ); return; } // Create placement $new_placement_id = $this->create_auto_ads_placement(); if ( ! $new_placement_id ) { RuntimeDebugger::add_server_error_log_entry( 'Auto Ads: Failed to create Placement. Skipping creation.' ); wp_send_json_error( [ 'message' => __( 'Failed to create Placement. Skipping creation.', 'adpresso' ) ], 403 ); return; } $this->assign_ad_to_placement( $new_placement_id, $new_ad_id ); RuntimeDebugger::add_server_error_log_entry( 'Auto Ads: Placement and Ad created successfully.' ); $settings['auto_ads_placement_id'] = $new_placement_id; $settings['auto_ads_ad_id'] = $new_ad_id; $settings['adsense_enable_auto_ads'] = true; Settings::update_direct_option( self::OPTION_NAME, $settings ); wp_send_json_success( [ 'message' => __( 'Entities are created successfully!', 'adpresso' ) ] ); } /** * Create the ad for the Auto Ads script. * * @param string $publisher_id * * @return int */ private function create_auto_ads_ad( string $publisher_id ): int { $script_content = $this->get_auto_ads_script_content( $publisher_id ); $ad_id = wp_insert_post( [ 'post_type' => Constants::POST_TYPE_AD, 'post_title' => 'AdSense Auto Ads Script (Auto-Generated)', 'post_content' => $script_content, 'post_status' => 'publish', ] ); RuntimeDebugger::add_server_error_log_entry( 'Auto Ads: Ad ID: ' . $ad_id ); if ( is_wp_error( $ad_id ) ) { RuntimeDebugger::add_server_error_log_entry( 'Auto Ads: Ad creation failed.' ); return 0; } RuntimeDebugger::add_server_error_log_entry( 'Auto Ads: Ad created successfully.' ); update_post_meta( $ad_id, Constants::META_PREFIX . 'ad_type', 'html' ); return $ad_id; } /** * Creates a new header placement for the Auto Ads script. * * @return int */ private function create_auto_ads_placement(): int { $placement_id = wp_insert_post( [ 'post_type' => Constants::POST_TYPE_PLACEMENT, 'post_title' => 'AdSense Auto Ads Placement (Auto-Generated)', 'post_status' => 'publish', ] ); RuntimeDebugger::add_server_error_log_entry( 'Auto Ads: Placement ID: ' . $placement_id ); if ( is_wp_error( $placement_id ) ) { RuntimeDebugger::add_server_error_log_entry( 'Auto Ads: Placement creation failed.' ); return 0; } RuntimeDebugger::add_server_error_log_entry( 'Auto Ads: Placement created successfully.' ); update_post_meta( $placement_id, Constants::META_PREFIX . 'placement_type', HeaderPlacement::ID ); return $placement_id; } /** * Assign ad to placement. * * @param int $placement_id * @param int $ad_id * * @return void */ private function assign_ad_to_placement( int $placement_id, int $ad_id ): void { update_post_meta( $placement_id, Constants::META_PREFIX . 'placement_item_id', $ad_id ); } /** * Creates the Auto Ads script content based on the Publisher ID. * * @param string $publisher_id * * @return string */ private function get_auto_ads_script_content( string $publisher_id ): string { $normalized_id = $this->normalize_adsense_publisher_id( $publisher_id ); return sprintf( // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- don't allow any changes on Google AdSense code. '', esc_attr( $normalized_id ) ); } /** * Checks if a permanently deleted post is part of the Auto Ads setup, * clears the IDs, disables the feature and notifies the user. * * @param int $post_id The ID of the post being deleted. */ public static function handle_auto_ads_deletion( int $post_id ): void { $post_type = get_post_type( $post_id ); if ( ! in_array( $post_type, [ 'adpresso_ad', 'adpresso_placement' ], true ) ) { return; } $option_name = 'adpresso_settings_adsense'; $settings = get_option( $option_name, [] ); if ( empty( $settings ) || ! is_array( $settings ) ) { return; } $is_dirty = false; $deleted_type = ''; if ( isset( $settings['auto_ads_ad_id'] ) && (int) $settings['auto_ads_ad_id'] === $post_id ) { $settings['auto_ads_ad_id'] = 0; $deleted_type = __( 'Auto-Ad', 'adpresso' ); $is_dirty = true; } if ( isset( $settings['auto_ads_placement_id'] ) && (int) $settings['auto_ads_placement_id'] === $post_id ) { $settings['auto_ads_placement_id'] = 0; $deleted_type = __( 'Auto-Ad Placement', 'adpresso' ); $is_dirty = true; } if ( $is_dirty ) { $settings['adsense_enable_auto_ads'] = 0; update_option( $option_name, $settings ); NotificationManager::get()->register( 'adsense-autoad-deleted-' . $post_id, sprintf( /* translators: %s is the type of the deleted entity (Auto-Ad or Placement) */ __( 'Your AdSense %s was permanently deleted. The Auto-Ads feature has been automatically disabled to prevent errors.', 'adpresso' ), $deleted_type ), [ 'type' => 'warning', 'location' => 'wp_notice', 'dismissible' => true, 'context' => 'all', ] ); } } /** * Normalizes the AdSense Publisher ID to a standard format. * * @param string $id * * @return string */ private function normalize_adsense_publisher_id( string $id ): string { $id = strtolower( trim( $id ) ); // Removes common prefixes $id = preg_replace( '/^(ca-)?(pub-)?/', '', $id ); // Only digits are allowed $id = preg_replace( '/\D/', '', $id ); // Add the standard prefix if not present return 'ca-pub-' . $id; } }