input( 'prompt', 'post', '' ) ) ); if ( empty( $prompt ) ) { wp_send_json_error( array( 'message' => __( 'Please describe your ad product or offer.', 'ad-fuz' ) ), 200 ); } $payload = array( 'prompt' => $prompt, 'ad_type' => sanitize_key( $request->input( 'ad_type', 'post', 'image' ) ), 'tone' => sanitize_key( $request->input( 'tone', 'post', 'promotional' ) ), 'generate_image' => (bool) $request->input( 'generate_image', 'post', false ), 'image_size' => sanitize_text_field( $request->input( 'image_size', 'post', '300x250' ) ), 'save_direct' => (bool) $request->input( 'save_direct', 'post', false ), 'campaign_action' => sanitize_key( $request->input( 'campaign_action', 'post', 'new' ) ), 'existing_campaign_id' => (int) $request->input( 'existing_campaign_id', 'post', 0 ), 'adspace_action' => sanitize_key( $request->input( 'adspace_action', 'post', 'new' ) ), 'adspace_format' => sanitize_key( $request->input( 'adspace_format', 'post', 'banner' ) ), 'ad_space_ids' => (array) $request->input( 'ad_space_ids', 'post', array() ), 'ad_status' => sanitize_key( $request->input( 'ad_status', 'post', 'active' ) ), 'user_id' => get_current_user_id(), ); if ( ! in_array( $payload['ad_status'], array( 'active', 'draft' ), true ) ) { $payload['ad_status'] = 'active'; } $job = QueueService::createJob( 'ai_generate_ad', $payload ); QueueService::dispatch( $job['job_key'] ); wp_send_json_success( array( 'job_key' => $job['job_key'], 'status' => $job['status'], 'message' => __( 'AI generation queued successfully.', 'ad-fuz' ), ) ); } /** * Get the live progress and status of a queued background job. */ public static function getJobStatus(): void { Common::checkCapability( array( 'manage_options', 'edit_posts' ) ); Common::verifyNonce(); $request = static::request(); $jobKey = sanitize_text_field( $request->input( 'job_key', 'get', $request->input( 'job_key', 'post', '' ) ) ); if ( empty( $jobKey ) ) { wp_send_json_error( array( 'message' => __( 'Missing job_key parameter.', 'ad-fuz' ) ), 200 ); } $job = QueueService::getJob( $jobKey ); if ( ! $job ) { wp_send_json_error( array( 'message' => __( 'Job not found.', 'ad-fuz' ) ), 200 ); } wp_send_json_success( array( 'job_key' => $job['job_key'], 'job_type' => $job['job_type'], 'status' => $job['status'], 'progress_step' => $job['progress_step'] ?? '', 'result' => $job['result'] ?? null, 'error_message' => $job['error_message'] ?? null, ) ); } /** * Asynchronous background worker handler called via loopback HTTP request. */ public static function processJob(): void { if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( 180 ); } $request = static::request(); $jobKey = sanitize_text_field( $request->input( 'job_key', 'post', '' ) ); $token = sanitize_text_field( $request->input( 'token', 'post', '' ) ); if ( empty( $jobKey ) || empty( $token ) || ! QueueService::verifyToken( $jobKey, $token ) ) { wp_die( 'Unauthorized job worker request', 'Unauthorized', array( 'response' => 403 ) ); } $job = QueueService::getJob( $jobKey ); if ( ! $job || 'completed' === $job['status'] ) { wp_send_json_success( array( 'message' => 'Job already completed or missing.' ) ); } $payload = $job['payload'] ?: array(); try { $result = self::executeAdGeneration( $payload, $jobKey ); QueueService::completeJob( $jobKey, $result ); wp_send_json_success( array( 'job_key' => $jobKey, 'status' => 'completed', ) ); } catch ( \Throwable $e ) { $errorCode = method_exists( $e, 'getCodeString' ) ? $e->getCodeString() : 'generation_failed'; QueueService::failJob( $jobKey, $e->getMessage(), $errorCode ); wp_send_json_error( array( 'job_key' => $jobKey, 'error' => $e->getMessage(), ), 200 ); } } /** * Core generation and database persistence engine. * * @throws \Exception When generation or persistence fails. */ private static function executeAdGeneration( array $payload, ?string $jobKey = null ): array { $prompt = $payload['prompt'] ?? ''; $ad_type = $payload['ad_type'] ?? 'image'; $tone = $payload['tone'] ?? 'promotional'; $generate_image = ! empty( $payload['generate_image'] ); $image_size = $payload['image_size'] ?? '300x250'; $save_direct = ! empty( $payload['save_direct'] ); $campaign_action = $payload['campaign_action'] ?? 'new'; $existingCampaignId = (int) ( $payload['existing_campaign_id'] ?? 0 ); $adspace_action = $payload['adspace_action'] ?? 'new'; $adspace_format = $payload['adspace_format'] ?? 'banner'; $ad_space_ids = (array) ( $payload['ad_space_ids'] ?? array() ); $ad_status = $payload['ad_status'] ?? 'active'; $userId = ! empty( $payload['user_id'] ) ? (int) $payload['user_id'] : get_current_user_id(); // Phase 1: Copywriting if ( $jobKey ) { QueueService::updateProgress( $jobKey, __( 'Writing persuasive ad copy & headline...', 'ad-fuz' ) ); } $copyResult = AIService::generateAdCopy( $prompt, array( 'tone' => $tone, 'ad_type' => $ad_type, 'image_size' => $image_size, ) ); if ( is_wp_error( $copyResult ) ) { throw new \Exception( $copyResult->get_error_message() ); } $responseData = array( 'ad_type' => $ad_type, 'title' => $copyResult['title'] ?? '', 'description' => $copyResult['description'] ?? '', 'cta' => $copyResult['cta'] ?? 'Learn More', 'script_html' => $copyResult['script_html'] ?? '', 'video_script' => $copyResult['video_script'] ?? '', 'creation_source' => 'ai', 'ai_prompt' => $prompt, 'media_id' => null, 'media_url' => '', 'space_id' => null, 'space_name' => '', 'space_dimensions' => $image_size . ' px', 'campaign_name' => '', 'shortcode' => '', ); // Phase 2: Image Generation (if applicable) if ( 'image' === $ad_type && $generate_image ) { if ( $jobKey ) { QueueService::updateProgress( $jobKey, __( 'Designing banner graphic & sizing image...', 'ad-fuz' ) ); } $imagePrompt = ! empty( $copyResult['image_prompt'] ) ? $copyResult['image_prompt'] : $prompt; $imageResult = AIService::generateAndSideloadImage( $imagePrompt, $image_size, $copyResult['title'] ); if ( is_wp_error( $imageResult ) ) { $ex = new \Exception( $imageResult->get_error_message() ); throw $ex; } $responseData['media_id'] = $imageResult['attachment_id']; $responseData['media_url'] = $imageResult['media_url']; } // Phase 3: Automated Database Persistence if ( $save_direct || 'none' !== $campaign_action || 'none' !== $adspace_action ) { if ( $jobKey ) { QueueService::updateProgress( $jobKey, __( 'Configuring campaign & placement shortcodes...', 'ad-fuz' ) ); } $now = current_time( 'mysql' ); $adTitle = ! empty( $responseData['title'] ) ? sanitize_text_field( $responseData['title'] ) : 'AI Generated Ad'; $baseSlug = sanitize_title( $adTitle ) ?: 'ad'; $adSlug = $baseSlug . '-' . wp_generate_password( 6, false, false ); $insertAd = array( 'user_id' => $userId, 'ad_type' => $ad_type, 'title' => $adTitle, 'slug' => $adSlug, 'description' => $responseData['description'] ?: null, 'cta' => $responseData['cta'] ?: null, 'target_url' => '#', 'media_id' => $responseData['media_id'] ?: null, 'media_url' => $responseData['media_url'] ?: null, 'script_html' => $responseData['script_html'] ?: null, 'hide_title' => 0, 'hide_link_name' => 0, 'cta_type' => 'link', 'link_to' => 'link', 'cta_position' => 'left', 'status' => $ad_status, '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' ); throw new \Exception( $dbError ); } // Attach AI Meta DB::get( TableName::ADFUZ_ADS_META() )->insert( array( 'ad_id' => $adId, 'meta_key' => 'creation_source', 'meta_value' => 'ai', 'created_at' => $now, 'updated_at' => $now, ) ); DB::get( TableName::ADFUZ_ADS_META() )->insert( array( 'ad_id' => $adId, 'meta_key' => 'ai_prompt', 'meta_value' => $prompt, 'created_at' => $now, 'updated_at' => $now, ) ); $responseData['ad_id'] = $adId; $responseData['slug'] = $adSlug; $responseData['redirect_url'] = Url::adsEdit( $adId ); // Campaign Setup $campaignId = null; if ( 'new' === $campaign_action ) { $campaignName = $adTitle . ' Campaign'; $campaignBaseSlug = sanitize_title( $campaignName ) ?: 'ai-campaign'; $campaignSlug = $campaignBaseSlug . '-' . 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, ) ); $responseData['campaign_name'] = $campaignName; } elseif ( 'existing' === $campaign_action && $existingCampaignId > 0 ) { $campaignId = $existingCampaignId; $campRow = DB::get( TableName::ADFUZ_CAMPAIGNS() )->find( $campaignId ); if ( ! empty( $campRow['name'] ) ) { $responseData['campaign_name'] = $campRow['name']; } } // Link Campaign <-> Ad 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, ) ); } } // Ad Space Setup $linkedSpaceIds = array(); if ( 'new' === $adspace_action ) { $spaceTitle = $adTitle . ' Space'; $dim = AIService::parseDimensions( $image_size ); $spaceBaseSlug = sanitize_title( $adTitle ) . '-space'; if ( empty( $spaceBaseSlug ) || '-' === $spaceBaseSlug ) { $spaceBaseSlug = 'ai-space'; } $spaceSlug = substr( $spaceBaseSlug, 0, 80 ) . '-' . wp_generate_password( 6, false, false ); $validFormats = array_unique( array_merge( array( 'banner', 'slider', 'global' ), array_keys( Common::ad_format() ) ) ); $chosenFormat = in_array( $adspace_format, $validFormats, true ) ? $adspace_format : 'banner'; $newSpaceId = DB::get( TableName::ADFUZ_AD_SPACES() )->insert( array( 'title' => $spaceTitle, 'slug' => $spaceSlug, 'description' => __( 'Created automatically via AI Ad Generator', 'ad-fuz' ), 'width' => $dim['width'] ?: 300, 'height' => $dim['height'] ?: 250, 'ad_format' => $chosenFormat, 'ad_sequence' => 'random', 'status' => 'active', 'created_at' => $now, 'updated_at' => $now, ) ); if ( $newSpaceId ) { $linkedSpaceIds[] = $newSpaceId; $responseData['space_id'] = $newSpaceId; $responseData['space_name'] = $spaceTitle; $responseData['shortcode'] = sprintf( '[ad-fuz ad_space_id="%d"]', $newSpaceId ); } } elseif ( 'existing' === $adspace_action && ! empty( $ad_space_ids ) ) { foreach ( $ad_space_ids as $sid ) { $linkedSpaceIds[] = (int) $sid; } if ( ! empty( $linkedSpaceIds[0] ) ) { $firstSpace = DB::get( TableName::ADFUZ_AD_SPACES() )->find( $linkedSpaceIds[0] ); $responseData['space_id'] = $linkedSpaceIds[0]; $responseData['space_name'] = $firstSpace['title'] ?? ''; $responseData['shortcode'] = sprintf( '[ad-fuz ad_space_id="%d"]', $linkedSpaceIds[0] ); } } // 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, ) ); } } } // If standalone direct save without spaces chosen, link shortcode to ad ID if ( empty( $responseData['shortcode'] ) && $adId ) { $responseData['shortcode'] = sprintf( '[ad-fuz ad_id="%d"]', $adId ); } } $responseData['message'] = __( '🚀 Ad creative and placements created successfully!', 'ad-fuz' ); return $responseData; } /** * Legacy synchronous fallback method. */ public static function generateAd(): void { if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( 120 ); } Common::checkCapability( array( 'manage_options', 'edit_posts' ) ); Common::verifyNonce(); $request = static::request(); $prompt = trim( sanitize_textarea_field( $request->input( 'prompt', 'post', '' ) ) ); if ( empty( $prompt ) ) { wp_send_json_error( array( 'message' => __( 'Please describe your ad product or offer.', 'ad-fuz' ) ), 200 ); } $payload = array( 'prompt' => $prompt, 'ad_type' => sanitize_key( $request->input( 'ad_type', 'post', 'image' ) ), 'tone' => sanitize_key( $request->input( 'tone', 'post', 'promotional' ) ), 'generate_image' => (bool) $request->input( 'generate_image', 'post', false ), 'image_size' => sanitize_text_field( $request->input( 'image_size', 'post', '300x250' ) ), 'save_direct' => (bool) $request->input( 'save_direct', 'post', false ), 'campaign_action' => sanitize_key( $request->input( 'campaign_action', 'post', 'new' ) ), 'existing_campaign_id' => (int) $request->input( 'existing_campaign_id', 'post', 0 ), 'adspace_action' => sanitize_key( $request->input( 'adspace_action', 'post', 'new' ) ), 'adspace_format' => sanitize_key( $request->input( 'adspace_format', 'post', 'banner' ) ), 'ad_space_ids' => (array) $request->input( 'ad_space_ids', 'post', array() ), 'ad_status' => sanitize_key( $request->input( 'ad_status', 'post', 'active' ) ), 'user_id' => get_current_user_id(), ); try { $result = self::executeAdGeneration( $payload ); wp_send_json_success( $result ); } catch ( \Throwable $e ) { wp_send_json_error( array( 'message' => $e->getMessage() ), 200 ); } } }