input( 'action', 'get', 'list' ); switch ( $action ) { case 'edit': self::viewEdit(); break; case 'create': self::viewAdd(); break; default: self::viewList(); break; } } private static function viewList() { $request = static::request(); // phpcs:disable WordPress.Security.NonceVerification.Recommended // These are read-only filter/pagination params for an admin list page. // No state is mutated. Pattern mirrors WordPress core WP_List_Table behaviour. $page = max( 1, (int) $request->input( 'page_no', 'get', 1 ) ); $current_search = sanitize_text_field( wp_unslash( $request->input( 'search', 'get', '' ) ) ); $current_status = sanitize_text_field( wp_unslash( $request->input( 'status', 'get', '' ) ) ); // Base query (excludes deleted). $query = DB::get( TableName::ADFUZ_ADS() )->where( 'status', '<>', 'deleted' ); // Apply search filter. if ( ! empty( $current_search ) ) { $query = $query->where( 'title', 'LIKE', '%' . $current_search . '%' ); } // Apply status filter. if ( ! empty( $current_status ) ) { $query = $query->where( 'status', $current_status ); } $ads = $query->orderBy( 'updated_at', 'desc' )->paginate( 10, $page ); // Status counts (always unfiltered by search/status, but excludes deleted). $status_rows = DB::get( TableName::ADFUZ_ADS() ) ->select( 'status', 'COUNT(*) as count' ) ->where( 'status', '!=', 'deleted' ) ->groupBy( 'status' ) ->get(); $stats = array( 'total' => $ads['total'] ?? 0, 'active' => 0, 'paused' => 0, 'pending' => 0, 'expired' => 0, 'draft' => 0, ); foreach ( $status_rows as $row ) { $key = strtolower( $row['status'] ); if ( isset( $stats[ $key ] ) ) { $stats[ $key ] = (int) $row['count']; } } $ads['stats'] = $stats; $ads['from'] = ( ( $page - 1 ) * 10 ) + 1; $ads['to'] = min( $page * 10, $ads['total'] ); $ads['current_search'] = $current_search; $ads['current_status'] = $current_status; Template::load( 'admin/adz/index', $ads ); } private static function viewEdit() { $ad_id = static::request()->input( 'ad_id', 'get' ); $ads = DB::get( TableName::ADFUZ_ADS() )->find( $ad_id ); Template::load( 'admin/adz/adz', $ads ); } private static function viewAdd() { Template::load( 'admin/adz/adz', array() ); } public static function status( $name = false ) { $status = array( 'active' => 'Active', 'paused' => 'Paused', 'deleted' => 'Deleted', 'draft' => 'Draft', 'pending' => 'Pending', ); return false !== $name ? $status[ $name ] : $status; } /** * After delete hook — removes campaign_ads rows if user confirmed. */ protected static function afterDelete( int $recordId ): void { $force = (bool) self::request()->input( 'force_delete_campaign_ads', 'post', false ); if ( ! $force ) { return; } CampaignAd::deleteByAd( $recordId ); } /** * Atomic Quick Ad Creator * * Creates an Ad, auto-creates/links Campaign & Ad Space, and optionally configures Global Ads. */ public static function quickCreateAd(): void { Common::checkCapability( array( 'manage_options', 'edit_posts' ) ); Common::verifyNonce(); $request = static::request(); $userId = get_current_user_id(); $now = current_time( 'mysql' ); // ── 1. Ad Creative Sanitization ── $title = sanitize_text_field( $request->input( 'title', 'post', '' ) ); $adType = sanitize_key( $request->input( 'ad_type', 'post', 'image' ) ); $targetUrl = esc_url_raw( $request->input( 'target_url', 'post', '' ) ); $cta = sanitize_text_field( $request->input( 'cta', 'post', '' ) ); $linkTarget = sanitize_key( $request->input( 'link_target', 'post', '_blank' ) ); $hideTitle = ! empty( $request->input( 'hide_title', 'post', 0 ) ) ? 1 : 0; $adStatus = sanitize_key( $request->input( 'ad_status', 'post', 'active' ) ); if ( ! in_array( $adStatus, array( 'active', 'draft' ), true ) ) { $adStatus = 'active'; } if ( empty( $title ) ) { wp_send_json_error( array( 'message' => __( 'Ad Title is required.', 'ad-fuz' ) ), 400 ); return; } $mediaId = absint( $request->input( 'media_id', 'post', 0 ) ); $mediaUrl = esc_url_raw( $request->input( 'media_url', 'post', '' ) ); $scriptHtml = wp_unslash( $request->input( 'script_html', 'post', '' ) ); $videoSourceType = sanitize_key( $request->input( 'video_source_type', 'post', 'upload' ) ); $videoUploadUrl = esc_url_raw( $request->input( 'video_url', 'post', '' ) ); $videoExternalUrl = esc_url_raw( $request->input( 'external_video_url', 'post', '' ) ); $videoMediaId = absint( $request->input( 'video_media_id', 'post', 0 ) ); $videoUrl = 'external' === $videoSourceType ? $videoExternalUrl : $videoUploadUrl; if ( 'image' === $adType && empty( $mediaUrl ) && empty( $mediaId ) ) { wp_send_json_error( array( 'message' => __( 'Please select or upload a banner image.', 'ad-fuz' ) ), 400 ); return; } if ( 'script' === $adType && empty( $scriptHtml ) ) { wp_send_json_error( array( 'message' => __( 'Please provide script or HTML content.', 'ad-fuz' ) ), 400 ); return; } if ( 'video' === $adType && empty( $videoUrl ) ) { wp_send_json_error( array( 'message' => __( 'Please provide a video URL or upload a video file.', 'ad-fuz' ) ), 400 ); return; } $baseSlug = sanitize_title( $title ) ?: 'ad'; $adSlug = substr( $baseSlug, 0, 80 ) . '-' . wp_generate_password( 6, false, false ); $insertMediaId = 'image' === $adType ? $mediaId : ( 'video' === $adType ? $videoMediaId : null ); $insertMediaUrl = 'image' === $adType ? $mediaUrl : ( 'video' === $adType ? $videoUrl : null ); $insertAd = array( 'user_id' => $userId, 'ad_type' => $adType, 'title' => $title, 'slug' => $adSlug, 'description' => null, 'cta' => $cta ?: null, 'target_url' => $targetUrl ?: '#', 'media_id' => $insertMediaId ?: null, 'media_url' => $insertMediaUrl ?: null, 'script_html' => 'script' === $adType ? $scriptHtml : null, 'hide_title' => $hideTitle, 'hide_link_name' => 0, 'cta_type' => 'link', 'link_to' => 'link', 'cta_position' => 'left', 'link_target' => $linkTarget, 'status' => $adStatus, 'created_at' => $now, 'updated_at' => $now, ); $adId = DB::get( TableName::ADFUZ_ADS() )->insert( $insertAd ); if ( ! $adId ) { global $wpdb; $dbError = ! empty( $wpdb->last_error ) ? $wpdb->last_error : __( 'Failed to save Ad to database.', 'ad-fuz' ); wp_send_json_error( array( 'message' => $dbError ), 500 ); return; } // Creation source meta DB::get( TableName::ADFUZ_ADS_META() )->insert( array( 'ad_id' => $adId, 'meta_key' => 'creation_source', 'meta_value' => 'quick_studio', 'created_at' => $now, 'updated_at' => $now, ) ); // ── 2. Campaign Setup ── $campaignAction = sanitize_key( $request->input( 'campaign_action', 'post', 'new' ) ); $existingCampaignId = absint( $request->input( 'existing_campaign_id', 'post', 0 ) ); $campaignId = null; $campaignName = ''; if ( 'new' === $campaignAction ) { $campaignName = $title . ' Campaign'; $campaignBaseSlug = sanitize_title( $campaignName ) ?: 'campaign'; $campaignSlug = substr( $campaignBaseSlug, 0, 80 ) . '-' . wp_generate_password( 6, false, false ); $campaignId = DB::get( TableName::ADFUZ_CAMPAIGNS() )->insert( array( 'user_id' => $userId, 'name' => $campaignName, 'slug' => $campaignSlug, 'status' => 'active', 'start_date' => $now, 'hide_link_name' => 0, 'created_at' => $now, 'updated_at' => $now, ) ); } elseif ( 'existing' === $campaignAction && $existingCampaignId > 0 ) { $campaignId = $existingCampaignId; $campRow = DB::get( TableName::ADFUZ_CAMPAIGNS() )->find( $campaignId ); if ( ! empty( $campRow['name'] ) ) { $campaignName = $campRow['name']; } } if ( $campaignId && $adId ) { $exists = DB::get( TableName::ADFUZ_CAMPAIGN_ADS() ) ->where( 'campaign_id', $campaignId ) ->where( 'ad_id', $adId ) ->first(); if ( ! $exists ) { DB::get( TableName::ADFUZ_CAMPAIGN_ADS() )->insert( array( 'campaign_id' => $campaignId, 'ad_id' => $adId, ) ); } } // ── 3. Ad Space Placement & Dimensions ── $adspaceAction = sanitize_key( $request->input( 'adspace_action', 'post', 'new' ) ); $adspaceFormat = sanitize_key( $request->input( 'adspace_format', 'post', 'banner' ) ); $rawDimensions = sanitize_text_field( $request->input( 'adspace_dimensions', 'post', '300x250' ) ); $adSpaceIds = (array) $request->input( 'ad_space_ids', 'post', array() ); $linkedSpaceIds = array(); $primarySpaceId = null; $primaryTitle = ''; // Resolve width & height $width = 300; $height = 250; if ( 'custom' === $rawDimensions ) { $width = max( 50, min( 2000, absint( $request->input( 'custom_width', 'post', 300 ) ) ) ); $height = max( 50, min( 2000, absint( $request->input( 'custom_height', 'post', 250 ) ) ) ); } elseif ( str_contains( $rawDimensions, 'x' ) ) { $parts = explode( 'x', $rawDimensions ); if ( count( $parts ) === 2 ) { $width = absint( $parts[0] ) ?: 300; $height = absint( $parts[1] ) ?: 250; } } if ( 'new' === $adspaceAction ) { $spaceTitle = $title . ' Space'; $spaceBaseSlug = sanitize_title( $spaceTitle ) ?: 'ad-space'; $spaceSlug = substr( $spaceBaseSlug, 0, 80 ) . '-' . wp_generate_password( 6, false, false ); $chosenFormat = in_array( $adspaceFormat, array( 'banner', 'slider' ), true ) ? $adspaceFormat : 'banner'; $newSpaceId = DB::get( TableName::ADFUZ_AD_SPACES() )->insert( array( 'title' => $spaceTitle, 'slug' => $spaceSlug, 'description' => __( 'Created via Quick Ad Studio', 'ad-fuz' ), 'width' => $width, 'height' => $height, 'ad_format' => $chosenFormat, 'ad_sequence' => 'random', 'status' => 'active', 'created_at' => $now, 'updated_at' => $now, ) ); if ( $newSpaceId ) { $linkedSpaceIds[] = $newSpaceId; $primarySpaceId = $newSpaceId; $primaryTitle = $spaceTitle; } } elseif ( 'existing' === $adspaceAction && ! empty( $adSpaceIds ) ) { foreach ( $adSpaceIds as $sid ) { $cleanSid = absint( $sid ); if ( $cleanSid > 0 ) { $linkedSpaceIds[] = $cleanSid; } } if ( ! empty( $linkedSpaceIds[0] ) ) { $primarySpaceId = $linkedSpaceIds[0]; $spaceRow = DB::get( TableName::ADFUZ_AD_SPACES() )->find( $primarySpaceId ); $primaryTitle = $spaceRow['title'] ?? ''; } } // Link Campaign <-> Ad Spaces if ( $campaignId && ! empty( $linkedSpaceIds ) ) { foreach ( $linkedSpaceIds as $sid ) { $exists = DB::get( TableName::ADFUZ_CAMPAIGN_AD_SPACES() ) ->where( 'campaign_id', $campaignId ) ->where( 'ad_space_id', $sid ) ->first(); if ( ! $exists ) { DB::get( TableName::ADFUZ_CAMPAIGN_AD_SPACES() )->insert( array( 'campaign_id' => $campaignId, 'ad_space_id' => $sid, ) ); } } } // ── 4. Global Ads Placement (Optional) ── $enableGlobalAd = ! empty( $request->input( 'enable_global_ad', 'post', 0 ) ); $globalFormat = sanitize_key( $request->input( 'global_format', 'post', 'popup' ) ); $globalScope = sanitize_key( $request->input( 'global_scope', 'post', 'all' ) ); $globalSuccessMsg = ''; if ( $enableGlobalAd && $primarySpaceId ) { $optKey = 'adfuz_global_ads_' . $globalFormat; $savedData = get_option( $optKey, array() ); if ( ! is_array( $savedData ) ) { $savedData = array(); } $savedData['status'] = 'active'; $rules = $savedData['rules'] ?? array(); if ( ! is_array( $rules ) ) { $rules = array(); } $newRule = array( 'title' => $title . ' Rule', 'ad_space_id' => $primarySpaceId, 'priority' => 10, 'status' => 'active', 'scope' => in_array( $globalScope, array( 'all', 'post' ), true ) ? $globalScope : 'all', 'post_ids' => array(), 'post_types' => 'post' === $globalScope ? array( 'post' ) : array(), 'settings' => array(), ); // Multi-spot handling for corner_peel and edge_bar if ( 'corner_peel' === $globalFormat ) { $spot = sanitize_key( $request->input( 'global_spot', 'post', 'bottom_right' ) ); $newRule['positions'] = array( $spot => $primarySpaceId, ); } elseif ( 'edge_bar' === $globalFormat ) { $edgeSpot = sanitize_key( $request->input( 'global_spot_edge', 'post', 'bottom' ) ); $newRule['positions'] = array( $edgeSpot => $primarySpaceId, ); } $rules[] = $newRule; $savedData['rules'] = $rules; update_option( $optKey, $savedData ); /* translators: %s: Global ad format name */ $globalSuccessMsg = sprintf( __( 'Configured and activated for %s.', 'ad-fuz' ), ucfirst( str_replace( '_', ' ', $globalFormat ) ) ); } $shortcode = $primarySpaceId ? sprintf( '[ad-fuz ad_space_id="%d"]', $primarySpaceId ) : ''; wp_send_json_success( array( 'ad_id' => $adId, 'ad_title' => $title, 'space_id' => $primarySpaceId, 'space_name' => $primaryTitle, 'shortcode' => $shortcode, 'global_enabled' => $enableGlobalAd, 'global_message' => $globalSuccessMsg, 'edit_url' => \AdFuz\Helpers\Url::adsEdit( $adId ), ) ); } }