get_id()); $edit_id = isset($_GET['campaign_id']) ? (int) $_GET['campaign_id'] : 0; if ($edit_id > 0) { $this->render_edit($edit_id, $base_url); } else { $this->render_list($base_url); } } private function card_description() { return __('Auto-created on first sight of a new utm_campaign value. Fresh rows arrive in the “auto_draft” status — promote to live after enriching the metadata.', 'ai-eshop-optimizer'); } private function render_list($base_url) { global $wpdb; $tbl = $wpdb->prefix . 'aieo_dmm_campaigns'; $sess = $wpdb->prefix . 'aieo_sessions_statics'; $status = isset($_GET['status']) ? sanitize_key((string) $_GET['status']) : ''; $allowed_status = array('auto_draft', 'draft', 'live', 'paused', 'archived'); $where = ''; if (in_array($status, $allowed_status, true)) { $where = $wpdb->prepare('WHERE c.status = %s', $status); } $counts = $wpdb->get_results( "SELECT status, COUNT(*) AS n FROM {$tbl} GROUP BY status", ARRAY_A ); $count_map = array(); foreach ((array) $counts as $r) { $count_map[(string) $r['status']] = (int) $r['n']; } $total_all = array_sum($count_map); $tabs = array( '' => sprintf(__('All (%d)', 'ai-eshop-optimizer'), $total_all), 'auto_draft' => sprintf(__('Auto-draft (%d)', 'ai-eshop-optimizer'), (int) ($count_map['auto_draft'] ?? 0)), 'live' => sprintf(__('Live (%d)', 'ai-eshop-optimizer'), (int) ($count_map['live'] ?? 0)), 'paused' => sprintf(__('Paused (%d)', 'ai-eshop-optimizer'), (int) ($count_map['paused'] ?? 0)), 'archived' => sprintf(__('Archived (%d)', 'ai-eshop-optimizer'), (int) ($count_map['archived'] ?? 0)), ); $sessions_present = ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $sess)) === $sess); $rows_sql = "SELECT c.*, sess.sessions AS sessions, sess.last_seen AS last_seen FROM {$tbl} c LEFT JOIN ( SELECT entry_campaign_id, COUNT(*) AS sessions, MAX(updated_at) AS last_seen FROM {$sess} WHERE entry_campaign_id IS NOT NULL GROUP BY entry_campaign_id ) sess ON sess.entry_campaign_id = c.id {$where} ORDER BY (sess.last_seen IS NULL), sess.last_seen DESC, c.id DESC LIMIT 500"; if (!$sessions_present) { $rows_sql = "SELECT c.*, NULL AS sessions, NULL AS last_seen FROM {$tbl} c {$where} ORDER BY c.updated_at DESC LIMIT 500"; } $rows = $wpdb->get_results($rows_sql, ARRAY_A); $aieo_ca_strip = array(); foreach ($tabs as $slug => $label) { $aieo_ca_strip[] = array( 'id' => (string) $slug, 'label' => $label, 'url' => '' === $slug ? $base_url : $base_url . '&status=' . urlencode($slug), ); } if ($total_all > 0) { echo \Rooster\Admin\Ui::tabs($aieo_ca_strip, in_array($status, $allowed_status, true) ? $status : '', array('label' => __('Status', 'ai-eshop-optimizer'))); } list($aieo_ca_open, $aieo_ca_close) = AIEO_DMM_UI::card(array( 'anchor' => 'campaigns-admin-advertising-campaigns', 'icon' => 'banknote', 'title' => __('Campaigns', 'ai-eshop-optimizer'), 'description' => $this->card_description(), 'flush' => !empty($rows), 'footnote' => empty($rows) ? '' : __('Revenue, spend and ROAS are not shown here. They depend on the period you are asking about and on how the campaign name is spelled in the ad platform, so they live in one place only — the ROI Wizard. Use the ROI button on a row to open it filtered to that campaign.', 'ai-eshop-optimizer'), )); echo $aieo_ca_open; if (empty($rows)) { $aieo_ca_none = ('' !== $where && $total_all > 0) ? \Rooster\Admin\States::state('no-results', array('items' => mb_strtolower(__('Campaigns', 'ai-eshop-optimizer')), 'clear' => $base_url)) : \Rooster\Admin\States::state('first-use', array('title' => __('No campaigns yet. They will appear here automatically the first time a visitor lands with a utm_campaign URL parameter.', 'ai-eshop-optimizer'))); echo $aieo_ca_none; echo $aieo_ca_close; return; } $roi_url = admin_url('admin.php?page=' . AIEO_DMM::MENU_SLUG . '&dmm_tab=roi_wizard'); $aieo_ca_rows = array(); foreach ($rows as $r) { $start = substr((string) ($r['start_at'] ?? ''), 0, 10); $end = substr((string) ($r['end_at'] ?? ''), 0, 10); $flight = ($start !== '' || $end !== '') ? (($start !== '' ? $start : '…') . ' → ' . ($end !== '' ? $end : '…')) : ''; $roi_args = array('roi_type' => 'campaign', 'roi_value' => (string) $r['slug']); if ($start !== '' && $end !== '') { $roi_args['roi_from'] = $start; $roi_args['roi_to'] = $end; } $aieo_ca_rows[] = array( 'id' => (int) $r['id'], 'slug' => (string) $r['slug'], 'label' => (string) $r['label'], 'status' => (string) $r['status'], 'objective' => (string) ($r['objective'] ?? ''), 'channel' => (string) ($r['channel_hint'] ?? ''), 'flight' => $flight, 'planned' => (isset($r['budget_planned']) && $r['budget_planned'] !== null && $r['budget_planned'] !== '') ? (float) $r['budget_planned'] : null, 'sessions' => (int) ($r['sessions'] ?? 0), 'last_seen' => (string) ($r['last_seen'] ?? ''), 'edit_url' => $base_url . '&campaign_id=' . (int) $r['id'], 'roi_url' => add_query_arg($roi_args, $roi_url), ); } AIEO_Table::card(array( 'wrap' => false, 'page_id' => $this->get_id(), 'row_key' => 'id', 'rows' => $aieo_ca_rows, 'density' => 'compact', 'columns' => array( array('key' => 'slug', 'label' => __('Slug', 'ai-eshop-optimizer'), 'type' => 'code'), array('key' => 'label', 'label' => __('Label', 'ai-eshop-optimizer')), array( 'key' => 'status', 'label' => __('Status', 'ai-eshop-optimizer'), 'type' => 'badge', 'format' => array( 'map' => array( 'auto_draft' => 'warning', 'draft' => 'neutral', 'live' => 'success', 'paused' => 'danger', 'archived' => 'muted', ), ), ), array('key' => 'objective', 'label' => __('Objective', 'ai-eshop-optimizer')), array('key' => 'channel', 'label' => __('Channel', 'ai-eshop-optimizer')), array('key' => 'flight', 'label' => __('Flight', 'ai-eshop-optimizer'), 'nowrap' => true), array('key' => 'planned', 'label' => __('Budget planned', 'ai-eshop-optimizer'), 'type' => 'money'), array('key' => 'sessions', 'label' => __('Sessions', 'ai-eshop-optimizer'), 'type' => 'integer'), array('key' => 'last_seen', 'label' => __('Last seen', 'ai-eshop-optimizer'), 'nowrap' => true), ), 'actions' => array( array('id' => 'edit', 'label' => __('Edit', 'ai-eshop-optimizer'), 'href' => static function ($r) { return $r['edit_url']; }), array('id' => 'roi', 'label' => __('ROI ↗', 'ai-eshop-optimizer'), 'href' => static function ($r) { return $r['roi_url']; }), ), )); echo $aieo_ca_close; } private function render_edit($id, $base_url) { $row = AIEO_DMM_Campaigns::get($id); $aieo_ca_card = array( 'anchor' => 'campaigns-admin-advertising-campaigns', 'icon' => 'banknote', 'title' => __('Campaigns', 'ai-eshop-optimizer'), 'description' => $this->card_description(), ); if (!$row) { list($aieo_ca_open, $aieo_ca_close) = AIEO_DMM_UI::card($aieo_ca_card); echo $aieo_ca_open; echo \Rooster\Admin\Ui::notice(esc_html__('Campaign not found.', 'ai-eshop-optimizer'), 'warning', array('inline' => true)); echo '

← ' . esc_html__('Back to list', 'ai-eshop-optimizer') . '

'; echo $aieo_ca_close; return; } $aieo_ca_form = 'aieo-card-campaigns-admin-advertising-campaigns-form'; $aieo_ca_card['footer'] = \Rooster\Admin\States::save_button($aieo_ca_form, array('label' => __('Save campaign', 'ai-eshop-optimizer'))); list($aieo_ca_open, $aieo_ca_close) = AIEO_DMM_UI::card($aieo_ca_card); $statuses = array('auto_draft', 'draft', 'live', 'paused', 'archived'); $objectives = array('' => '—', 'acquisition' => 'Acquisition', 'retention' => 'Retention', 'win_back' => 'Win-back', 'brand' => 'Brand'); echo $aieo_ca_open; echo '

← ' . esc_html__('Back to list', 'ai-eshop-optimizer') . '

'; echo rooster_admin_card_section_html((string) $row['label'], array('id' => 'aieo-camp-edit')); echo '

' . esc_html((string) $row['slug']) . '

'; $status_opts = array(); foreach ($statuses as $s) { $status_opts[$s] = $s; } $cur_sym = function_exists('get_woocommerce_currency_symbol') ? html_entity_decode(get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8') : '€'; $rows_html = \Rooster\Admin\Ui::field_row(array('id' => 'aieo-camp-label', 'label' => __('Label', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::input('label', (string) $row['label'], array('id' => 'aieo-camp-label', 'medium' => true)))) . \Rooster\Admin\Ui::field_row(array('id' => 'aieo-camp-status', 'label' => __('Status', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::select('status', $status_opts, (string) $row['status'], array('id' => 'aieo-camp-status')))) . \Rooster\Admin\Ui::field_row(array('id' => 'aieo-camp-objective', 'label' => __('Objective', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::select('objective', $objectives, (string) ($row['objective'] ?? ''), array('id' => 'aieo-camp-objective')))) . \Rooster\Admin\Ui::field_row(array('id' => 'aieo-camp-channel', 'label' => __('Channel hint', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::input('channel_hint', (string) ($row['channel_hint'] ?? ''), array('id' => 'aieo-camp-channel', 'medium' => true, 'code' => true, 'placeholder' => __('e.g. paid_google, paid_facebook, newsletter', 'ai-eshop-optimizer'))))) . \Rooster\Admin\Ui::field_row(array( 'id' => 'aieo-camp-flight', 'label' => __('Flight', 'ai-eshop-optimizer'), 'joiner' => '→', 'parts' => array( array('label' => __('Start date', 'ai-eshop-optimizer'), 'id' => 'aieo-camp-start', 'control' => \Rooster\Admin\Ui::input('start_at', substr((string) ($row['start_at'] ?? ''), 0, 10), array('id' => 'aieo-camp-start', 'type' => 'date'))), array('label' => __('End date', 'ai-eshop-optimizer'), 'id' => 'aieo-camp-end', 'control' => \Rooster\Admin\Ui::input('end_at', substr((string) ($row['end_at'] ?? ''), 0, 10), array('id' => 'aieo-camp-end', 'type' => 'date'))), ), )) . \Rooster\Admin\Ui::field_row(array('id' => 'aieo-camp-bp', 'label' => sprintf(__('Budget planned (%s)', 'ai-eshop-optimizer'), $cur_sym), 'control' => \Rooster\Admin\Ui::input('budget_planned', (string) ($row['budget_planned'] ?? ''), array('id' => 'aieo-camp-bp', 'type' => 'number', 'short' => true, 'step' => 'any', 'min' => '0')))) . \Rooster\Admin\Ui::field_row(array('id' => 'aieo-camp-bs', 'label' => sprintf(__('Budget spent (%s)', 'ai-eshop-optimizer'), $cur_sym), 'help' => __('Your own record of total spend. ROAS is computed in the ROI Wizard from the period-dated ad-spend ledger — enter or import spend there.', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::input('budget_spent', (string) ($row['budget_spent'] ?? ''), array('id' => 'aieo-camp-bs', 'type' => 'number', 'short' => true, 'step' => 'any', 'min' => '0')))) . \Rooster\Admin\Ui::field_row(array('id' => 'aieo-camp-notes', 'label' => __('Notes', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::textarea('notes', (string) ($row['notes'] ?? ''), array('id' => 'aieo-camp-notes', 'rows' => 3)))); $aieo_ca_fields = '
' . $rows_html . '
'; echo AIEO_DMM_UI::card_form( 'aieo_dmm_campaign_save', 'campaigns', $aieo_ca_form, $aieo_ca_fields ); echo $aieo_ca_close; } public function ajax_save() { AIEO_DMM::verify_ajax_request(); $id = isset($_POST['id']) ? (int) $_POST['id'] : 0; if ($id <= 0) { wp_send_json_error(array('message' => __('Missing id.', 'ai-eshop-optimizer'))); } $patch = array(); foreach (array('label', 'status', 'objective', 'channel_hint', 'start_at', 'end_at', 'budget_planned', 'budget_spent', 'notes') as $field) { if (isset($_POST[$field])) { $patch[$field] = wp_unslash($_POST[$field]); } } AIEO_DMM_Campaigns::save($id, $patch); wp_send_json_success(); } }