__( 'Rooster One Google Ads', 'ai-eshop-optimizer' ), 'description' => __( 'Turn what your shop already knows — the nightly boost ranking, a running competition, an event, a landing page with a form — into a complete Google Ads campaign: ad copy, descriptions and keywords, written from your own catalogue and grounded in your own demand signals. You read the whole ad before anything exists. Rooster then creates it inside your Google Ads account PAUSED, and only you can turn it on.', 'ai-eshop-optimizer' ), 'bullets' => array( __( 'Campaigns composed from the same intelligence that ranks your organic promotion', 'ai-eshop-optimizer' ), __( 'Every headline, description and keyword reviewable before it is created', 'ai-eshop-optimizer' ), __( 'Created PAUSED in your own account — Rooster can never start your spend', 'ai-eshop-optimizer' ), __( 'Sales or lead-form goals, with spend flowing back into the ROI cockpit', 'ai-eshop-optimizer' ), ), ); } public function register_hooks() { add_action( 'wp_ajax_aieo_ads_compose', array( $this, 'ajax_compose' ) ); add_action( 'wp_ajax_aieo_ads_push', array( $this, 'ajax_push' ) ); add_action( 'wp_ajax_aieo_ads_activate', array( $this, 'ajax_activate' ) ); add_action( 'wp_ajax_aieo_ads_archive', array( $this, 'ajax_archive' ) ); } private function guard() { check_ajax_referer( self::NONCE, 'nonce' ); if ( ! current_user_can( self::CAP ) ) { wp_send_json_error( array( 'msg' => 'forbidden' ), 403 ); } } private function engine_ready() { return class_exists( 'AIEO_Ads_Plan' ) && class_exists( 'AIEO_Ads_Composer' ) && class_exists( 'AIEO_Ads_Google_Driver' ); } public function ajax_compose() { $this->guard(); if ( ! $this->engine_ready() ) { wp_send_json_error( array( 'msg' => __( 'The campaign engine is not loaded on this site.', 'ai-eshop-optimizer' ) ) ); } $source_type = isset( $_POST['source_type'] ) ? sanitize_key( wp_unslash( $_POST['source_type'] ) ) : ''; if ( ! in_array( $source_type, self::SOURCES, true ) ) { wp_send_json_error( array( 'msg' => __( 'Choose what the campaign should advertise.', 'ai-eshop-optimizer' ) ) ); } $source_id = isset( $_POST['source_id'] ) ? absint( wp_unslash( $_POST['source_id'] ) ) : 0; if ( 'boost_slate' !== $source_type && $source_id <= 0 ) { wp_send_json_error( array( 'msg' => __( 'Pick which one to advertise.', 'ai-eshop-optimizer' ) ) ); } $raw_budget = isset( $_POST['daily_budget'] ) ? sanitize_text_field( wp_unslash( $_POST['daily_budget'] ) ) : ''; $budget = (float) str_replace( ',', '.', $raw_budget ); if ( $budget <= 0 ) { wp_send_json_error( array( 'msg' => __( 'Set a daily budget above zero.', 'ai-eshop-optimizer' ) ) ); } $objective = isset( $_POST['objective'] ) ? sanitize_key( wp_unslash( $_POST['objective'] ) ) : ''; if ( ! in_array( $objective, array( '', AIEO_Ads_Plan::OBJECTIVE_SALES, AIEO_Ads_Plan::OBJECTIVE_LEADS ), true ) ) { $objective = ''; } $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : ''; $plan = AIEO_Ads_Composer::compose( array( 'source_type' => $source_type, 'source_id' => $source_id, 'daily_budget' => $budget, 'objective' => $objective, 'name' => $name, ) ); if ( is_wp_error( $plan ) ) { wp_send_json_error( array( 'msg' => $plan->get_error_message() ) ); } $plan_id = AIEO_Ads_Plan::save_draft( (array) $plan ); if ( is_wp_error( $plan_id ) ) { wp_send_json_error( array( 'msg' => $plan_id->get_error_message() ) ); } wp_send_json_success( array( 'id' => (int) $plan_id, 'msg' => __( 'Draft written. Read it under Plans before creating it in Google.', 'ai-eshop-optimizer' ), ) ); } public function ajax_push() { $this->guard(); if ( ! $this->engine_ready() ) { wp_send_json_error( array( 'msg' => __( 'The campaign engine is not loaded on this site.', 'ai-eshop-optimizer' ) ) ); } $plan_id = isset( $_POST['id'] ) ? absint( wp_unslash( $_POST['id'] ) ) : 0; if ( $plan_id <= 0 ) { wp_send_json_error( array( 'msg' => __( 'Unknown plan.', 'ai-eshop-optimizer' ) ) ); } $res = AIEO_Ads_Google_Driver::push( $plan_id ); if ( is_wp_error( $res ) ) { wp_send_json_error( array( 'msg' => $res->get_error_message() ) ); } $msg = __( 'Created in Google Ads and PAUSED. It cannot spend until you enable it there yourself.', 'ai-eshop-optimizer' ); if ( ! empty( $res['keyword_warning'] ) ) { $msg .= ' ' . sprintf( __( 'The campaign is fine, but the keywords did not go in: %s', 'ai-eshop-optimizer' ), (string) $res['keyword_warning'] ); } wp_send_json_success( array( 'msg' => $msg ) ); } public function ajax_activate() { $this->guard(); if ( ! $this->engine_ready() ) { wp_send_json_error( array( 'msg' => __( 'The campaign engine is not loaded on this site.', 'ai-eshop-optimizer' ) ) ); } $plan_id = isset( $_POST['id'] ) ? absint( wp_unslash( $_POST['id'] ) ) : 0; if ( $plan_id <= 0 || ! AIEO_Ads_Plan::mark_activated( $plan_id ) ) { wp_send_json_error( array( 'msg' => __( 'Could not record that. Reload the page and try again.', 'ai-eshop-optimizer' ) ) ); } wp_send_json_success( array( 'msg' => __( 'Noted as live.', 'ai-eshop-optimizer' ) ) ); } public function ajax_archive() { $this->guard(); if ( ! $this->engine_ready() ) { wp_send_json_error( array( 'msg' => __( 'The campaign engine is not loaded on this site.', 'ai-eshop-optimizer' ) ) ); } $plan_id = isset( $_POST['id'] ) ? absint( wp_unslash( $_POST['id'] ) ) : 0; if ( $plan_id <= 0 || ! AIEO_Ads_Plan::archive( $plan_id ) ) { wp_send_json_error( array( 'msg' => __( 'Could not archive that plan.', 'ai-eshop-optimizer' ) ) ); } wp_send_json_success( array( 'msg' => __( 'Archived. Anything already in Google Ads is untouched.', 'ai-eshop-optimizer' ) ) ); } public function render_subtab() { if ( ! $this->engine_ready() ) { echo '

' . esc_html__( 'The campaign engine is not loaded on this site.', 'ai-eshop-optimizer' ) . '

'; return; } $nonce = wp_create_nonce( self::NONCE ); $this->render_compose_section(); $this->render_plans_section(); $this->render_safety_section(); $this->print_js( $nonce ); } private function render_connection_hint() { $url = add_query_arg( array( 'page' => AIEO_DMM::MENU_SLUG, 'dmm_tab' => 'ad_connectors' ), admin_url( 'admin.php' ) ); $html = wp_kses_post( sprintf( __( 'Sending a campaign needs a connected Google Ads account. Connect one under %1$sSettings → Ad spend import%2$s, which also pulls your spend back in for ROI reporting.', 'ai-eshop-optimizer' ), '', '' ) ); echo \Rooster\Admin\Guidance::text( wp_strip_all_tags( $html ), 'guidance', array( 'html' => $html ) ); } private function render_compose_section() { $currency = function_exists( 'get_woocommerce_currency' ) ? (string) get_woocommerce_currency() : ''; list( $open, $close ) = AIEO_DMM_UI::card( array( 'anchor' => 'ads-compose-a-campaign', 'icon' => 'pencil-line', 'title' => __( 'Compose a campaign', 'ai-eshop-optimizer' ), 'description' => __( 'Pick something the shop already has and Rooster writes the campaign around it — headlines, descriptions and keywords, in the language of your own catalogue. Composing costs nothing and creates nothing in Google: you get a draft to read first.', 'ai-eshop-optimizer' ), 'footer' => '

' . '

', 'footnote' => __( 'Writing the copy uses AI credits. Google is not contacted at this step.', 'ai-eshop-optimizer' ), ) ); echo $open; echo '
'; $source = ''; echo \Rooster\Admin\Ui::field_row( array( 'id' => 'aieo-ads-source', 'label' => __( 'What to advertise', 'ai-eshop-optimizer' ), 'control' => $source ) ); $this->source_picker( 'competition', __( 'Which competition', 'ai-eshop-optimizer' ), array( 'aieo_competition' ) ); $this->source_picker( 'event', __( 'Which event', 'ai-eshop-optimizer' ), array( 'aieo_event' ) ); $this->source_picker( 'lead_form', __( 'Which page', 'ai-eshop-optimizer' ), array( 'page', 'post' ) ); $budget = \Rooster\Admin\Ui::input( '', '', array( 'type' => 'number', 'id' => 'aieo-ads-budget', 'class' => 'aieo-ads-budget', 'short' => true, 'unit' => $currency, 'step' => '0.01', 'min' => '0.01', 'placeholder' => '10.00', ) ); echo \Rooster\Admin\Ui::field_row( array( 'id' => 'aieo-ads-budget', 'label' => __( 'Daily budget', 'ai-eshop-optimizer' ), 'control' => $budget ) ); $goal = ''; echo \Rooster\Admin\Ui::field_row( array( 'id' => 'aieo-ads-objective', 'label' => __( 'Goal', 'ai-eshop-optimizer' ), 'control' => $goal ) ); $name = \Rooster\Admin\Ui::input( '', '', array( 'id' => 'aieo-ads-name', 'class' => 'aieo-ads-name', 'medium' => true, 'placeholder' => __( 'Rooster names it for you', 'ai-eshop-optimizer' ), ) ); echo \Rooster\Admin\Ui::field_row( array( 'id' => 'aieo-ads-name', 'label' => __( 'Campaign name — optional', 'ai-eshop-optimizer' ), 'control' => $name ) ); echo '
'; echo $close; } private function source_picker( $source, $label, array $post_types ) { $posts = get_posts( array( 'post_type' => $post_types, 'post_status' => 'publish', 'numberposts' => self::SOURCE_LIMIT, 'orderby' => 'title', 'order' => 'ASC', 'suppress_filters' => false, ) ); $id = 'aieo-ads-pick-' . str_replace( '_', '-', $source ); $select = ''; $row = \Rooster\Admin\Ui::field_row( array( 'id' => $id, 'label' => $label, 'control' => $select, 'class' => 'aieo-ads-pick', 'attrs' => array( 'data-src' => $source, 'hidden' => true, ), ) ); echo $row; } private function render_plans_section() { $rows = AIEO_Ads_Plan::all(); list( $open, $close ) = AIEO_DMM_UI::card( array( 'anchor' => 'ads-plans', 'icon' => 'list', 'title' => __( 'Plans', 'ai-eshop-optimizer' ), 'description' => empty( $rows ) ? __( 'The campaign plans you compose appear here, to review and then create in Google Ads, paused.', 'ai-eshop-optimizer' ) : __( 'A draft lives only here. Review the ad Rooster wrote, then create it in Google Ads, where it lands paused and waits for you.', 'ai-eshop-optimizer' ), ) ); echo $open; $this->render_connection_hint(); if ( empty( $rows ) ) { echo \Rooster\Admin\States::state( 'first-use', array( 'title' => __( 'No campaign plans yet — compose one above.', 'ai-eshop-optimizer' ) ) ); echo $close; return; } $table_rows = array(); $statuses = array(); foreach ( $rows as $row ) { $table_rows[] = $this->plan_row( $row ); $st = isset( $row['status'] ) ? (string) $row['status'] : ''; if ( '' !== $st ) { $statuses[ $st ] = $st; } } ksort( $statuses ); echo AIEO_Table::local( 'aieo-ads-plans', array( array( 'key' => 'campaign', 'label' => __( 'Campaign', 'ai-eshop-optimizer' ), 'type' => 'text', 'sort_type' => 'text' ), array( 'key' => 'status', 'label' => __( 'Status', 'ai-eshop-optimizer' ), 'type' => 'status', 'sort_type' => 'text' ), array( 'key' => 'goal', 'label' => __( 'Goal', 'ai-eshop-optimizer' ), 'type' => 'text', 'sort_type' => 'text' ), array( 'key' => 'daily_budget', 'label' => __( 'Daily budget', 'ai-eshop-optimizer' ), 'type' => 'num', 'sort_type' => 'number' ), array( 'key' => 'created', 'label' => __( 'Created', 'ai-eshop-optimizer' ), 'type' => 'date', 'sort_type' => 'date' ), array( 'key' => 'actions', 'label' => __( 'Actions', 'ai-eshop-optimizer' ), 'type' => 'actions' ), ), $table_rows, array( 'label' => __( 'Plans', 'ai-eshop-optimizer' ), 'details' => true, 'search' => array( 'label' => __( 'Search', 'ai-eshop-optimizer' ), 'placeholder' => __( 'Campaigns', 'ai-eshop-optimizer' ) ), 'filters' => array( array( 'key' => 'status', 'label' => __( 'Status', 'ai-eshop-optimizer' ), 'options' => $statuses, 'any' => __( 'Any status', 'ai-eshop-optimizer' ) ), ), ) ); echo $close; } private function plan_row( array $row ) { $id = isset( $row['id'] ) ? (int) $row['id'] : 0; $status = isset( $row['status'] ) ? (string) $row['status'] : ''; $name = isset( $row['name'] ) ? (string) $row['name'] : ''; $plan = isset( $row['plan'] ) && is_array( $row['plan'] ) ? $row['plan'] : array(); $group = isset( $plan['ad_groups'][0] ) && is_array( $plan['ad_groups'][0] ) ? $plan['ad_groups'][0] : array(); $title = '' !== trim( $name ) ? $this->shorten( $name, 80 ) : '#' . $id; $source = ! empty( $row['source_type'] ) ? $this->source_label( (string) $row['source_type'] ) : ''; $goal = $this->objective_label( isset( $row['objective'] ) ? (string) $row['objective'] : '' ); $budget = $this->money( isset( $row['daily_budget'] ) ? $row['daily_budget'] : 0, isset( $row['currency'] ) ? (string) $row['currency'] : '' ); $when = isset( $row['created_at'] ) ? (string) $row['created_at'] : ''; $say = sprintf( __( '%1$s: a daily budget of %2$s.', 'ai-eshop-optimizer' ), $title, $budget ); $btn = static function ( $label, $class, $extra = array(), $says = '' ) use ( $id, $title ) { return \Rooster\Admin\Ui::button( $label, array( 'size' => 'sm', 'class' => $class, 'attrs' => $extra + array( 'data-plan' => $id, 'aria-label' => $label . ', ' . $title ) + \Rooster\Admin\States::confirm_attrs( $class . '-dialog', '' !== $says ? array( 'say' => $says ) : array() ) ) ); }; $buttons = ''; $paused = ''; if ( AIEO_Ads_Plan::STATUS_DRAFT === $status ) { $buttons .= $btn( __( 'Create in Google (paused)', 'ai-eshop-optimizer' ), 'aieo-ads-push', array( 'title' => __( 'Creates the campaign in your Google Ads account with status PAUSED. It cannot show ads or spend anything until you enable it there.', 'ai-eshop-optimizer' ) ), $say ); } if ( AIEO_Ads_Plan::STATUS_CREATED === $status || AIEO_Ads_Plan::STATUS_ACTIVATED === $status ) { $manage = AIEO_Ads_Google_Driver::manage_url( isset( $row['external_ids'] ) && is_array( $row['external_ids'] ) ? $row['external_ids'] : array() ); if ( AIEO_Ads_Plan::STATUS_CREATED === $status ) { $paused = ' ' . \Rooster\Admin\Ui::badge( __( 'Paused in Google', 'ai-eshop-optimizer' ), 'mono', array( 'sm' => true ) ); } if ( '' !== $manage ) { $buttons .= \Rooster\Admin\Ui::button( __( 'Open in Google Ads', 'ai-eshop-optimizer' ), array( 'size' => 'sm', 'href' => $manage, 'external' => true ) ); } if ( AIEO_Ads_Plan::STATUS_CREATED === $status ) { $buttons .= $btn( __( 'I activated it', 'ai-eshop-optimizer' ), 'aieo-ads-activate', array(), $say ); } } $buttons .= $btn( __( 'Archive', 'ai-eshop-optimizer' ), 'aieo-ads-archive', array( 'aria-label' => sprintf( __( 'Archive %s', 'ai-eshop-optimizer' ), $title ) ) ); $detail = ''; $last_error = isset( $row['last_error'] ) ? (string) $row['last_error'] : ''; if ( AIEO_Ads_Plan::STATUS_FAILED === $status && '' !== $last_error ) { $detail .= '' . esc_html__( 'Google refused this:', 'ai-eshop-optimizer' ) . '' . '
' . esc_html( $last_error ) . '
'; } if ( AIEO_Ads_Plan::STATUS_DRAFT === $status && ! empty( $group ) ) { ob_start(); $this->review_panel( $group ); $detail .= ob_get_clean(); } return array( '_id' => (string) $id, '_name' => $title, '_attrs' => array( 'data-plan' => $id ), '_s' => array( 'campaign' => $title, 'status' => $status, 'goal' => $goal, 'daily_budget' => (float) ( isset( $row['daily_budget'] ) ? $row['daily_budget'] : 0 ), 'created' => $when ), '_text' => $title . ' ' . $source, '_f' => array( 'status' => array( $status ) ), '_detail' => $detail, 'campaign' => array( 'title' => $title, 'sub' => $source ), 'status' => array( 'html' => $this->status_pill( $status ) . $paused ), 'goal' => $goal, 'daily_budget' => $budget, 'created' => $this->local_date( $when ), 'actions' => '' . $buttons . '', ); } private function review_panel( array $group ) { $final = isset( $group['final_url'] ) ? (string) $group['final_url'] : ''; if ( '' !== $final ) { echo '

' . esc_html__( 'Landing page:', 'ai-eshop-optimizer' ) . ' ' . '' . esc_html( $this->shorten( $final, 90 ) ) . '

'; } echo '
'; $this->asset_list( __( 'Headlines', 'ai-eshop-optimizer' ), isset( $group['headlines'] ) ? (array) $group['headlines'] : array(), AIEO_Ads_Composer::HEADLINE_MAX ); $this->asset_list( __( 'Descriptions', 'ai-eshop-optimizer' ), isset( $group['descriptions'] ) ? (array) $group['descriptions'] : array(), AIEO_Ads_Composer::DESCRIPTION_MAX ); echo '
'; $keywords = isset( $group['keywords'] ) ? array_filter( array_map( 'strval', (array) $group['keywords'] ) ) : array(); echo '

' . esc_html__( 'Keywords', 'ai-eshop-optimizer' ) . '
'; echo '' . ( $keywords ? esc_html( implode( ' · ', $keywords ) ) : esc_html__( 'None — the ad group will run without keywords until you add some.', 'ai-eshop-optimizer' ) ) . '

'; } private function asset_list( $title, array $items, $max ) { echo '
'; echo '' . esc_html( $title ) . ''; if ( empty( $items ) ) { echo '

' . esc_html__( 'None generated.', 'ai-eshop-optimizer' ) . '

'; return; } echo ''; } private function render_safety_section() { list( $open, $close ) = AIEO_DMM_UI::card( array( 'anchor' => 'ads-nothing-here-can-spend-your-money', 'icon' => 'shield', 'title' => __( 'Nothing here can spend your money', 'ai-eshop-optimizer' ), 'description' => __( 'Composing a plan never contacts Google; creating one puts the campaign, ad and keywords into your account PAUSED, and only you can switch it on in Google Ads.', 'ai-eshop-optimizer' ), 'essential' => true, ) ); echo $open; echo \Rooster\Admin\Guidance::text( __( 'Rooster creates every campaign in Google Ads with the status PAUSED, and it has no way to enable one. A paused campaign shows no ads and spends nothing, however long it sits there.', 'ai-eshop-optimizer' ) ); echo ''; echo $close; } private function status_pill( $status ) { $green = ( AIEO_Ads_Plan::STATUS_CREATED === $status || AIEO_Ads_Plan::STATUS_ACTIVATED === $status ); return \Rooster\Admin\Ui::badge( AIEO_Ads_Plan::status_label( $status ), $green ? 'success' : 'mono', array( 'sm' => true ) ); } private function objective_label( $objective ) { return ( AIEO_Ads_Plan::OBJECTIVE_LEADS === $objective ) ? __( 'Leads', 'ai-eshop-optimizer' ) : __( 'Sales', 'ai-eshop-optimizer' ); } private function source_label( $source_type ) { $map = array( 'boost_slate' => __( 'Top ranked products', 'ai-eshop-optimizer' ), 'competition' => __( 'Competition', 'ai-eshop-optimizer' ), 'event' => __( 'Event', 'ai-eshop-optimizer' ), 'lead_form' => __( 'Landing page', 'ai-eshop-optimizer' ), 'product_set' => __( 'Product set', 'ai-eshop-optimizer' ), ); return isset( $map[ $source_type ] ) ? $map[ $source_type ] : $source_type; } private function money( $amount, $currency ) { $out = number_format_i18n( (float) $amount, 2 ); if ( '' === $currency ) { return $out; } if ( function_exists( 'get_woocommerce_currency_symbol' ) ) { $symbol = get_woocommerce_currency_symbol( $currency ); if ( '' !== $symbol ) { return $out . ' ' . html_entity_decode( $symbol, ENT_QUOTES, 'UTF-8' ); } } return $out . ' ' . $currency; } private function local_date( $gmt ) { if ( '' === $gmt || '0000-00-00 00:00:00' === $gmt ) { return '—'; } return get_date_from_gmt( $gmt, get_option( 'date_format' ) . ' H:i' ); } private function shorten( $text, $len ) { $text = (string) $text; if ( mb_strlen( $text ) <= $len ) { return $text; } return mb_substr( $text, 0, max( 1, $len - 1 ) ) . '…'; } private function print_js( $nonce ) { $ajax = admin_url( 'admin-ajax.php' ); $dialogs = \Rooster\Admin\States::confirm( 'aieo-ads-push-dialog', 'apply', __( 'Create this campaign in Google Ads? It is created PAUSED and cannot spend anything until you enable it there yourself.', 'ai-eshop-optimizer' ), '', array( 'confirm' => __( 'Create in Google (paused)', 'ai-eshop-optimizer' ), 'say' => true ) ) . \Rooster\Admin\States::confirm( 'aieo-ads-activate-dialog', 'apply', __( 'Only confirm this if you have already enabled the campaign inside Google Ads. This records what you did — it does not turn anything on.', 'ai-eshop-optimizer' ), '', array( 'confirm' => __( 'I activated it', 'ai-eshop-optimizer' ), 'say' => true ) ) . \Rooster\Admin\States::confirm( 'aieo-ads-archive-dialog', 'delete', __( 'Archive this plan? It disappears from this list. Anything already in Google Ads is left exactly as it is.', 'ai-eshop-optimizer' ), '', array( 'confirm' => __( 'Archive', 'ai-eshop-optimizer' ) ) ); echo $dialogs; ?>