query("DO SLEEP(0)"); usleep(250000); $wpdb->query("UNLOCK TABLES"); return true; } catch (Exception $e) { return true; } } private function aieo_log($message, array $context = array()) { $entry = 'AIEO: ' . $message; if (!empty($context)) { $encoded = wp_json_encode($context); if ($encoded !== false) { $entry .= ' ' . $encoded; } } aieo_debug_log($entry); } private function aieo_get_uuid_bin_expression() { if ($this->uuid_bin_expression !== null) { return $this->uuid_bin_expression; } global $wpdb; $previous_suppression = $wpdb->suppress_errors(true); $previous_show_errors = property_exists($wpdb, 'show_errors') ? $wpdb->show_errors : null; if (method_exists($wpdb, 'hide_errors') && $previous_show_errors !== null) { $wpdb->hide_errors(); } $wpdb->get_var('SELECT UUID_TO_BIN(UUID())'); $wpdb->suppress_errors($previous_suppression); if (method_exists($wpdb, 'show_errors') && $previous_show_errors) { $wpdb->show_errors(); } if (empty($wpdb->last_error)) { $this->uuid_bin_expression = 'UUID_TO_BIN(UUID())'; $wpdb->last_error = ''; $this->aieo_log('Using native UUID_TO_BIN() for binary UUID generation.'); return $this->uuid_bin_expression; } $this->aieo_log( 'UUID_TO_BIN() not available on this database server, falling back to UNHEX(REPLACE(UUID(), \'-\', \'\')).', array('db_error' => $wpdb->last_error) ); $wpdb->last_error = ''; $this->uuid_bin_expression = "UNHEX(REPLACE(UUID(), '-', ''))"; return $this->uuid_bin_expression; } private function aieo_table_exists($table_name) { global $wpdb; $result = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_name)); return ($result === $table_name); } private function aieo_uuid_empty_clause($column, $alias = '') { $prefix = $alias ? "{$alias}." : ''; return "({$prefix}{$column} IS NULL OR LENGTH({$prefix}{$column}) = 0)"; } private function aieo_ensure_temp_orders_schema($table_name) { global $wpdb; if (!$this->aieo_table_exists($table_name)) { $this->aieo_log('Temp orders table missing; recreating.', array('table' => $table_name)); aieo_create_temp_orders_table($table_name); return; } $columns = array( 'OrderIdGraphUUID' => 'BINARY(16) NULL DEFAULT NULL', 'OrderItemIdGraphUUID' => 'BINARY(16) NULL DEFAULT NULL', 'PPIdGraphUUID' => 'BINARY(16) NULL DEFAULT NULL', 'ProdIdGraphUUID' => 'BINARY(16) NULL DEFAULT NULL', 'EpoIdGraphUUID' => 'BINARY(16) NULL DEFAULT NULL', 'O_PP_GraphUUID' => 'BINARY(16) NULL DEFAULT NULL', 'O_PP_Freq' => 'INT NULL DEFAULT NULL', 'O_Prod_GraphUUID' => 'BINARY(16) NULL DEFAULT NULL', 'O_Prod_Freq' => 'INT NULL DEFAULT NULL', 'O_Epo_GraphUUID' => 'BINARY(16) NULL DEFAULT NULL', 'O_Epo_Freq' => 'INT NULL DEFAULT NULL', 'PP_Epo_GraphUUID' => 'BINARY(16) NULL DEFAULT NULL', 'PP_Epo_Freq' => 'INT NULL DEFAULT NULL', 'Prod_Epo_GraphUUID' => 'BINARY(16) NULL DEFAULT NULL', 'Prod_Epo_Freq' => 'INT NULL DEFAULT NULL', ); foreach ($columns as $column => $definition) { $column_info = $wpdb->get_row( $wpdb->prepare( "SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = %s", $table_name, $column ), ARRAY_A ); if (!$column_info) { $wpdb->query("ALTER TABLE `{$table_name}` ADD COLUMN `{$column}` {$definition}"); if ($wpdb->last_error) { $this->aieo_log('Failed adding missing column to temp orders table.', array( 'table' => $table_name, 'column' => $column, 'error' => $wpdb->last_error, )); } continue; } $expects_binary = stripos($definition, 'BINARY(16)') !== false; if ($expects_binary && stripos($column_info['COLUMN_TYPE'], 'binary(16)') === false) { $wpdb->query("ALTER TABLE `{$table_name}` MODIFY `{$column}` BINARY(16) NULL DEFAULT NULL"); if ($wpdb->last_error) { $this->aieo_log('Failed converting column to BINARY(16).', array( 'table' => $table_name, 'column' => $column, 'current_type' => $column_info['COLUMN_TYPE'], 'error' => $wpdb->last_error, )); } } } } public function aieo_wpse_edit_text($content) { $page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; if ('' === $page || (0 !== strpos($page, 'ai-eshop') && 0 !== strpos($page, 'aieo-'))) { return $content; } return sprintf( __('Thank you for choosing %1$s by %2$s', 'ai-eshop-optimizer'), 'Rooster One', 'Oxford Metadata Ltd' ); } public function aieo_normalize_path($csv_path) { if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $csv_path = str_replace('\\', '/', $csv_path); } return $csv_path; } public function aieo_enqueue_styles_scripts() { $aieo_panel_css = AIEO_DIR . 'assets/css/aieo-admin-panel.css'; wp_enqueue_style( 'ai-eshop-optimizer-admin', AIEO_URI . 'assets/css/aieo-admin-panel.css', array(), file_exists($aieo_panel_css) ? (string) filemtime($aieo_panel_css) : AIEO_VERSION ); wp_enqueue_script( 'ai-eshop-optimizer-form-validation', AIEO_URI . 'assets/js/form-validation.js', array('jquery'), AIEO_VERSION, true ); wp_enqueue_script( 'aieo-export-progress', AIEO_URI . 'assets/js/export-progress.js', array('jquery'), AIEO_VERSION . '.' . time(), true ); wp_localize_script( 'aieo-export-progress', 'aieoExport', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aieo_export_nonce'), 'i18nNoOptionsSelected' => __('No export options selected. Please check at least one option.', 'ai-eshop-optimizer'), 'i18nStagesSelected' => __('Selected %d stage(s) to process', 'ai-eshop-optimizer'), 'i18nExportInitSuccess' => __('Export initialized successfully', 'ai-eshop-optimizer'), 'i18nExportInitFailed' => __('Failed to initialize export: ', 'ai-eshop-optimizer'), 'i18nAjaxErrorInit' => __('AJAX error during initialization', 'ai-eshop-optimizer'), 'i18nConfirmExport' => __('Start the data analysis process? This may take several minutes.', 'ai-eshop-optimizer'), 'i18nStartingStage' => __('Starting: ', 'ai-eshop-optimizer'), 'i18nStageProgress' => __('Stage %1$d/%2$d: %3$s', 'ai-eshop-optimizer'), 'i18nStageComplete' => __('✓ %1$s: %2$s', 'ai-eshop-optimizer'), 'i18nStageError' => __('Error in %1$s: %2$s', 'ai-eshop-optimizer'), 'i18nTimeoutWarning' => __('⚠ Timeout in %s. The operation may still be running on the server.', 'ai-eshop-optimizer'), 'i18nTimeoutRefresh' => __('Please wait a few moments, then refresh the page and check if the operation completed.', 'ai-eshop-optimizer'), 'i18nTimeoutTabs' => __('If you have multiple browser tabs open, close them to avoid conflicts.', 'ai-eshop-optimizer'), 'i18nAjaxErrorStage' => __('AJAX error in %1$s: %2$s', 'ai-eshop-optimizer'), 'i18nExportCompleted' => __('Export completed successfully!', 'ai-eshop-optimizer'), 'i18nExportSuccess' => __('🎉 Export process completed successfully!', 'ai-eshop-optimizer'), 'i18nExportFailed' => __('❌ Export process failed. Please check the errors above.', 'ai-eshop-optimizer'), 'i18nNoExportRequested' => __('✓ Process complete. No export file was requested.', 'ai-eshop-optimizer'), 'i18nLoadingDownload' => __('Loading download section...', 'ai-eshop-optimizer'), 'i18nDownloadLoaded' => __('✓ Download section loaded! Scroll down to download your file.', 'ai-eshop-optimizer'), 'i18nRetrying' => __('File not ready yet, retrying... (%1$d/%2$d)', 'ai-eshop-optimizer'), 'i18nDownloadFailed' => __('⚠ Could not load download section after %d attempts.', 'ai-eshop-optimizer'), 'i18nRefreshPage' => __('Please refresh the page to see the download link.', 'ai-eshop-optimizer'), 'i18nErrorRetrying' => __('Error loading, retrying... (%1$d/%2$d)', 'ai-eshop-optimizer'), 'i18nErrorDownloadFailed' => __('⚠ Error loading download section after %d attempts.', 'ai-eshop-optimizer'), 'i18nErrorDetails' => __('Error details: ', 'ai-eshop-optimizer'), 'i18nCheckingStats' => __('Checking for statistics...', 'ai-eshop-optimizer'), 'i18nStatsLoaded' => __('✓ Statistics section loaded!', 'ai-eshop-optimizer'), 'i18nStatsRetrying' => __('Statistics not ready yet, retrying... (%1$d/%2$d)', 'ai-eshop-optimizer'), 'i18nStatsNotAvailable' => __('⚠ Statistics not available: ', 'ai-eshop-optimizer'), 'i18nRefreshLater' => __('You may need to refresh the page later.', 'ai-eshop-optimizer'), 'i18nStatsErrorRetrying' => __('Error loading statistics, retrying... (%1$d/%2$d)', 'ai-eshop-optimizer'), 'i18nStatsLoadError' => __('⚠ Could not load statistics section: ', 'ai-eshop-optimizer'), 'i18nServerResponse' => __('Server response: ', 'ai-eshop-optimizer'), 'i18nCheckConsole' => __('Check browser console for full error details', 'ai-eshop-optimizer'), 'i18nUnknownError' => __('Unknown error', 'ai-eshop-optimizer'), 'i18nUpsellConfirm' => __('Start exporting current upsell/cross-sell assignments? This may take a few minutes.', 'ai-eshop-optimizer'), 'i18nUpsellComplete' => __('✓ Upsell export completed successfully!', 'ai-eshop-optimizer'), 'i18nUpsellFailed' => __('❌ Upsell export failed. Please review the log and try again.', 'ai-eshop-optimizer'), 'i18nUpsellExportError' => __('Unexpected error during upsell export.', 'ai-eshop-optimizer'), 'i18nAjaxErrorUpsellExport' => __('AJAX error in upsell export: ', 'ai-eshop-optimizer'), 'i18nUpsellExportInitFailed' => __('Failed to initialize upsell export.', 'ai-eshop-optimizer'), 'i18nUpsellExportComplete' => __('Upsell export complete.', 'ai-eshop-optimizer'), 'i18nUpsellExportStarted' => __('Upsell export started.', 'ai-eshop-optimizer'), 'i18nStartingExport' => __('Starting export...', 'ai-eshop-optimizer'), 'i18nAjaxErrorUpsellInit' => __('AJAX error while initializing upsell export: ', 'ai-eshop-optimizer'), 'i18nUpsellImportConfirm' => __('Start importing the uploaded upsell and cross-sell recommendations?', 'ai-eshop-optimizer'), 'i18nUpsellImportComplete' => __('✓ Upsell import completed successfully!', 'ai-eshop-optimizer'), 'i18nUpsellImportFailed' => __('❌ Upsell import failed. Please review the log and try again.', 'ai-eshop-optimizer'), 'i18nUpsellImportSelect' => __('Please choose a CSV file before starting the import.', 'ai-eshop-optimizer'), 'i18nUpsellImportError' => __('Unexpected error during upsell import.', 'ai-eshop-optimizer'), 'i18nAjaxErrorUpsellImport' => __('AJAX error in upsell import: ', 'ai-eshop-optimizer'), 'i18nUpsellImportInitFailed' => __('Failed to initialize upsell import.', 'ai-eshop-optimizer'), 'i18nUpsellImportCompleted' => __('Upsell import completed.', 'ai-eshop-optimizer'), 'i18nUpsellImportStarted' => __('Upsell import started.', 'ai-eshop-optimizer'), 'i18nStartingImport' => __('Starting import...', 'ai-eshop-optimizer'), 'i18nAjaxErrorUpsellImportInit' => __('AJAX error while initializing upsell import: ', 'ai-eshop-optimizer'), 'i18nInitializing' => __('Initializing...', 'ai-eshop-optimizer'), 'i18nProcessingBatch' => __('Processing batch...', 'ai-eshop-optimizer'), 'i18nDownloadCSV' => __('Download CSV', 'ai-eshop-optimizer'), 'i18nOptimizing' => __('Optimizing...', 'ai-eshop-optimizer'), 'i18nPleaseWaitOptimizing' => __('Please wait, optimizing database indexes...', 'ai-eshop-optimizer'), 'i18nSuccess' => __('✓ Success!', 'ai-eshop-optimizer'), 'i18nOptimizationComplete' => __('✓ Optimization Complete', 'ai-eshop-optimizer'), 'i18nError' => __('✗ Error:', 'ai-eshop-optimizer'), 'i18nRetryOptimization' => __('Retry Optimization', 'ai-eshop-optimizer'), 'i18nServerCommError' => __('Failed to communicate with server', 'ai-eshop-optimizer'), 'i18nLoading' => __('Loading...', 'ai-eshop-optimizer'), 'i18nRefreshIndexes' => __('Refresh Indexes', 'ai-eshop-optimizer'), 'i18nNoIndexesFound' => __('No indexes found or error loading data.', 'ai-eshop-optimizer'), 'i18nRetry' => __('Retry', 'ai-eshop-optimizer'), 'i18nErrorLoadingIndexes' => __('Error loading index information.', 'ai-eshop-optimizer'), 'i18nFixingCollations' => __('Fixing Collations...', 'ai-eshop-optimizer'), 'i18nPleaseWaitCollations' => __('Please wait, standardizing collations...', 'ai-eshop-optimizer'), 'i18nCollationFixComplete' => __('✓ Collation Fix Complete', 'ai-eshop-optimizer'), 'i18nRetryCollationFix' => __('Retry Collation Fix', 'ai-eshop-optimizer'), 'i18nConfirmCollationFix' => __('This will standardize all table collations to utf8mb4_unicode_ci. Continue?', 'ai-eshop-optimizer'), 'stageNames' => array( 'create_attributes' => __('Creating product core attributes and categories', 'ai-eshop-optimizer'), 'create_needs' => __('Populating product needs', 'ai-eshop-optimizer'), 'create_catalogue' => __('Creating full product catalogue', 'ai-eshop-optimizer'), 'process_eshop_orders' => __('Processing eshop orders', 'ai-eshop-optimizer'), 'process_instore_orders' => __('Processing in-store orders', 'ai-eshop-optimizer'), 'merge_orders' => __('Merging orders', 'ai-eshop-optimizer'), 'include_unsold' => __('Including unsold products', 'ai-eshop-optimizer'), 'aggregate_product_stats' => __('Aggregating product statistics', 'ai-eshop-optimizer'), 'aggregate_category_brand_stats' => __('Aggregating category/brand popularity', 'ai-eshop-optimizer'), 'aggregate_customer_std_stats' => __('Aggregating customer standard statistics', 'ai-eshop-optimizer'), 'aggregate_customer_adv_stats' => __('Aggregating customer advanced statistics', 'ai-eshop-optimizer'), 'create_uuids' => __('Creating UUIDs', 'ai-eshop-optimizer'), 'create_need_suggestions' => __('Generating need-based suggestions', 'ai-eshop-optimizer'), 'create_export' => __('Creating export file', 'ai-eshop-optimizer') ) ) ); } public function aieo_add_admin_menu() { $custom_icon = AIEO_URI . 'assets/images/rooster-head-fit-82.svg'; $hook_suffix = add_menu_page( __('Rooster One', 'ai-eshop-optimizer'), $this->aieo_brand_wordmark_html(), AIEO_CAPABILITY, 'ai-eshop-optimizer', array($this, 'render_eshop_optimizer_admin_page'), $custom_icon, 26 ); add_submenu_page( 'ai-eshop-optimizer', __('Data Prep', 'ai-eshop-optimizer'), __('⚙️ Data Prep', 'ai-eshop-optimizer'), AIEO_CAPABILITY, 'ai-eshop-optimizer', array($this, 'render_eshop_optimizer_admin_page') ); $this->aieo_add_group_submenus(); add_action('admin_enqueue_scripts', function ($hook) use ($hook_suffix) { if ($hook == $hook_suffix) { $this->aieo_enqueue_styles_scripts(); } }); } public function aieo_filter_admin_title($admin_title, $title) { $admin_title = (string) $admin_title; $title = (string) $title; if ('' === $title || 0 !== strpos($admin_title, $title)) { return $admin_title; } $page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; if ('' === $page || !$this->aieo_owns_admin_page($page)) { return $admin_title; } $segments = array(); if (class_exists('AIEO_DMM') && AIEO_DMM::MENU_SLUG === $page) { $dmm = AIEO_DMM::instance(); if (is_object($dmm) && method_exists($dmm, 'get_active_subtab_label')) { $segments[] = (string) $dmm->get_active_subtab_label(); } } $segments[] = $title; $segments[] = __('Rooster One', 'ai-eshop-optimizer'); $head = array(); foreach ($segments as $segment) { $segment = trim(wp_strip_all_tags((string) $segment)); if ('' === $segment) { continue; } if (!empty($head) && 0 === strcasecmp($segment, (string) end($head))) { continue; } $head[] = $segment; } if (empty($head)) { return $admin_title; } return implode(' ‹ ', $head) . substr($admin_title, strlen($title)); } private function aieo_owns_admin_page($page) { if (0 === strpos($page, 'aieo-') || 0 === strpos($page, 'ai-eshop-')) { return true; } $submenu = isset($GLOBALS['submenu']['ai-eshop-optimizer']) ? (array) $GLOBALS['submenu']['ai-eshop-optimizer'] : array(); foreach ($submenu as $item) { if (isset($item[2]) && $page === (string) $item[2]) { return true; } } return false; } private function aieo_add_group_submenus() { if (!class_exists('AIEO_DMM')) { return; } $chrome = class_exists('AIEO_Chrome') && AIEO_Chrome::on(); $base = 'admin.php?page=' . AIEO_DMM::MENU_SLUG; if (!$chrome) { add_submenu_page( 'ai-eshop-optimizer', __('All Features', 'ai-eshop-optimizer'), __('All Features', 'ai-eshop-optimizer'), AIEO_CAPABILITY, $base ); } $dmm = AIEO_DMM::instance(); if (!method_exists($dmm, 'get_group_definitions')) { return; } $counts = array(); foreach ($dmm->get_subtab_tree('') as $entry) { if (isset($entry['kind']) && 'group' === $entry['kind']) { $counts[(string) $entry['id']] = count((array) $entry['children']); } } foreach ($dmm->get_group_definitions() as $group) { $gid = isset($group['id']) ? (string) $group['id'] : ''; if ($gid === '' || empty($counts[$gid])) { continue; } if ('home' === $gid) { continue; } add_submenu_page( 'ai-eshop-optimizer', wp_strip_all_tags((string) $group['label']), wp_strip_all_tags((string) $group['label']), AIEO_CAPABILITY, $chrome ? 'admin.php?page=' . AIEO_Chrome::HOME_SLUG . '#rooster-admin-home-' . $gid : add_query_arg('aieo_group', $gid, $base) ); } } public function aieo_reorder_admin_submenu() { global $submenu; $parent = 'ai-eshop-optimizer'; if (empty($submenu[$parent]) || !is_array($submenu[$parent])) { return; } remove_submenu_page($parent, 'ai-eshop-optimizer'); remove_submenu_page($parent, 'aieo-iapi'); remove_submenu_page($parent, 'aieo-recommendations-cache'); $group_rank = array(); if (class_exists('AIEO_DMM')) { $rank = 10; foreach (AIEO_DMM::instance()->get_group_definitions() as $g) { $group_rank['aieo_group=' . $g['id']] = $rank; $group_rank['#rooster-admin-home-' . $g['id']] = $rank; $rank++; } } $rank_of = static function ($slug) use ($group_rank) { $slug = (string) $slug; if ($slug !== '' && strpos($slug, 'aieo_group=') === false && strpos($slug, 'dmm_tab=welcome') !== false) { return 0; } if (class_exists('AIEO_Chrome') && AIEO_Chrome::HOME_SLUG === $slug) { return 0; } foreach ($group_rank as $frag => $r) { if (strpos($slug, $frag) !== false) { return $r; } } return 900; }; foreach ($submenu[$parent] as $i => $row) { $slug = isset($row[2]) ? (string) $row[2] : ''; $cls = ''; if (strpos($slug, 'aieo_group=') !== false || strpos($slug, '#rooster-admin-home-') !== false) { foreach (array_keys($group_rank) as $frag) { if (strpos($slug, $frag) !== false) { $cls = 'aieo-grp-' . (strpos($frag, 'aieo_group=') === 0 ? substr($frag, strlen('aieo_group=')) : substr($frag, strlen('#rooster-admin-home-'))); break; } } } elseif (strpos($slug, 'dmm_tab=welcome') !== false || (class_exists('AIEO_Chrome') && AIEO_Chrome::HOME_SLUG === $slug)) { $cls = 'aieo-grp-all'; } if ($cls !== '') { $submenu[$parent][$i][4] = trim((isset($row[4]) ? $row[4] . ' ' : '') . $cls); } } usort($submenu[$parent], static function ($a, $b) use ($rank_of) { $ra = $rank_of(isset($a[2]) ? $a[2] : ''); $rb = $rank_of(isset($b[2]) ? $b[2] : ''); return ($ra === $rb) ? 0 : (($ra < $rb) ? -1 : 1); }); } public function aieo_add_admin_bar_menu($wp_admin_bar) { if (!current_user_can(AIEO_CAPABILITY)) { return; } $chrome = class_exists('AIEO_Chrome') && AIEO_Chrome::on(); $icon_url = AIEO_URI . 'assets/images/rooster-mark-light.svg'; $icon_img = ''; $wp_admin_bar->add_node(array( 'id' => 'aieo-toolbar', 'title' => $icon_img . $this->aieo_brand_wordmark_html(), 'href' => $chrome ? AIEO_Chrome::home_url() : admin_url('admin.php?page=ai-eshop-optimizer'), 'meta' => array('class' => 'aieo-toolbar-node'), )); $dmm = class_exists('AIEO_DMM') ? AIEO_DMM::instance() : null; $base = $dmm ? ($chrome ? AIEO_Chrome::home_url() : admin_url('admin.php?page=' . AIEO_DMM::MENU_SLUG)) : ''; if ($dmm && $base !== '') { $wp_admin_bar->add_node(array( 'id' => 'aieo-toolbar-all', 'parent' => 'aieo-toolbar', 'title' => $chrome ? __('Home', 'ai-eshop-optimizer') : __('All Features', 'ai-eshop-optimizer'), 'href' => $base, 'meta' => array('class' => 'aieo-grp-all'), )); $counts = array(); foreach ($dmm->get_subtab_tree('') as $entry) { if (isset($entry['kind']) && 'group' === $entry['kind']) { $counts[(string) $entry['id']] = count((array) $entry['children']); } } foreach ($dmm->get_group_definitions() as $group) { $gid = isset($group['id']) ? (string) $group['id'] : ''; if ($gid === '' || empty($counts[$gid])) { continue; } if ('home' === $gid) { continue; } $wp_admin_bar->add_node(array( 'id' => 'aieo-toolbar-grp-' . $gid, 'parent' => 'aieo-toolbar', 'title' => wp_strip_all_tags((string) $group['label']), 'href' => $chrome ? $base . '#rooster-admin-home-' . $gid : add_query_arg('aieo_group', $gid, $base), 'meta' => array('class' => 'aieo-grp-' . $gid), )); } } $wp_admin_bar->add_node(array( 'id' => 'aieo-toolbar-ai-chat', 'parent' => 'aieo-toolbar', 'title' => __('AI Chat', 'ai-eshop-optimizer'), 'href' => admin_url('admin.php?page=ai-eshop-chat'), )); } private function aieo_brand_wordmark_html() { $brand = 'Rooster One'; $label = __('Rooster One', 'ai-eshop-optimizer'); if ($label !== $brand) { return esc_html($label); } return '' . 'Rooster One' . ''; } public function aieo_admin_bar_css() { if (!is_admin_bar_showing() || !current_user_can(AIEO_CAPABILITY)) { return; } echo ''; } public function aieo_display_all_admin_notices() { if ($products_impacted = get_transient('aieo_products_updated_count')) { $message = sprintf( _n('%d product has been updated in the catalogue.', '%d products have been updated in the catalogue.', $products_impacted, 'ai-eshop-optimizer'), esc_html($products_impacted) ); echo '

' . esc_html($message) . '

'; delete_transient('aieo_products_updated_count'); } global $pagenow; if ($pagenow == 'admin.php' && isset($_GET['page']) && $_GET['page'] == 'ai-eshop-optimizer') { if (isset($_GET['exported_orders']) && $_GET['exported_orders'] == '1') { $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : ''; if ($nonce && wp_verify_nonce($nonce, 'exported_orders_action')) { $message = get_option('aieo_export_orders_message', false); if ($message) { echo '

' . esc_html($message) . '

'; delete_option('aieo_export_orders_message'); } } else { wp_die(esc_html__('Security check failed.', 'ai-eshop-optimizer')); } } } if ($notice = get_transient('aieo_upload_notice')) { echo '

' . esc_html($notice) . '

'; delete_transient('aieo_upload_notice'); } if ($error = get_transient('aieo_upload_error')) { echo '

' . esc_html($error) . '

'; delete_transient('aieo_upload_error'); } $account_message = get_option('aieo_account_message', false); if ($account_message) { $class = strpos($account_message, 'Error') !== false ? 'notice-error' : 'notice-success'; echo '

' . wp_kses_post($account_message) . '

'; delete_option('aieo_account_message'); } $remote_connection_message = get_option('aieo_remote_connection_message', false); if ($remote_connection_message) { $class = strpos($remote_connection_message, 'Error') !== false ? 'notice-error' : 'notice-success'; echo '

' . esc_html($remote_connection_message) . '

'; delete_option('aieo_remote_connection_message'); } $file_deletion_message = get_option('aieo_file_deletion_message', false); if ($file_deletion_message) { $class = strpos($file_deletion_message, 'Error') !== false ? 'notice-error' : 'notice-success'; echo '

' . esc_html($file_deletion_message) . '

'; delete_option('aieo_file_deletion_message'); } } public function aieo_handle_display_references_setting() { if ( isset($_POST['eshop_optimizer_nonce_field_1']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['eshop_optimizer_nonce_field_1'])), 'eshop_optimizer_update_display_preferences') ) { if (current_user_can(AIEO_CAPABILITY)) { update_option('aieo_display_upsells', isset($_POST['aieo_display_upsells']) ? '1' : '0'); update_option('aieo_display_cross_sells', isset($_POST['aieo_display_cross_sells']) ? '1' : '0'); update_option('aieo_display_related', isset($_POST['aieo_display_related']) ? '1' : '0'); update_option('aieo_display_recently_viewed_products', isset($_POST['aieo_display_recently_viewed_products']) ? '1' : '0'); update_option('aieo_display_same_needs', isset($_POST['aieo_display_same_needs']) ? '1' : '0'); update_option('aieo_display_categories_by_sales', isset($_POST['aieo_display_categories_by_sales']) ? '1' : '0'); update_option('aieo_category_sales_period', isset($_POST['aieo_category_sales_period']) ? sanitize_text_field(wp_unslash($_POST['aieo_category_sales_period'])) : 'all_time'); update_option('aieo_recommendations_display_order', isset($_POST['aieo_recommendations_display_order']) ? '1' : '0'); } } else { wp_die(esc_html__('Invalid nonce!', 'ai-eshop-optimizer')); } wp_safe_redirect(esc_url(admin_url('admin.php?page=ai-eshop-optimizer'))); exit; } public function aieo_handle_utm_stats_setting() { if ( isset($_POST['eshop_optimizer_utm_stats_nonce_2']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['eshop_optimizer_utm_stats_nonce_2'])), 'eshop_optimizer_utm_stats_action') ) { if (current_user_can(AIEO_CAPABILITY)) { if (isset($_POST['aieo_activate_utm_stats'])) { $ga4tag = isset($_POST['aieo_google_ga4']) ? sanitize_text_field(wp_unslash($_POST['aieo_google_ga4'])) : ''; update_option('aieo_activate_utm_stats', 1); update_option('aieo_google_ga4', $ga4tag); } else { delete_option('aieo_activate_utm_stats'); delete_option('aieo_google_ga4'); } } wp_safe_redirect(esc_url(admin_url('admin.php?page=ai-eshop-optimizer'))); exit; } } public function aieo_handle_session_tracker_setting() { if ( isset($_POST['eshop_optimizer_session_tracker_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['eshop_optimizer_session_tracker_nonce'])), 'eshop_optimizer_session_tracker_action') ) { if (current_user_can(AIEO_CAPABILITY)) { if (isset($_POST['aieo_session_tracking_enabled'])) { update_option('aieo_session_tracking_enabled', 1); } else { update_option('aieo_session_tracking_enabled', 0); } if (isset($_POST['aieo_track_scroll_depth'])) { update_option('aieo_track_scroll_depth', 1); } else { update_option('aieo_track_scroll_depth', 0); } if (isset($_POST['aieo_track_clicks'])) { update_option('aieo_track_clicks', 1); } else { update_option('aieo_track_clicks', 0); } if (isset($_POST['aieo_throttle_clicks'])) { update_option('aieo_throttle_clicks', 1); } else { update_option('aieo_throttle_clicks', 0); } if (isset($_POST['aieo_session_bypass_consent'])) { update_option('aieo_session_bypass_consent', 1); } else { update_option('aieo_session_bypass_consent', 0); } if (isset($_POST['aieo_custom_image_click_rules'])) { update_option('aieo_custom_image_click_rules', sanitize_textarea_field(wp_unslash($_POST['aieo_custom_image_click_rules']))); } if (isset($_POST['aieo_session_retention_days'])) { $retention_days = absint(wp_unslash($_POST['aieo_session_retention_days'])); $retention_days = max(7, min(90, $retention_days)); update_option('aieo_session_retention_days', $retention_days); } if (isset($_POST['aieo_session_archive_frequency'])) { $frequency = sanitize_text_field(wp_unslash($_POST['aieo_session_archive_frequency'])); $allowed_frequencies = array('daily', 'weekly', 'monthly'); if (in_array($frequency, $allowed_frequencies)) { update_option('aieo_session_archive_frequency', $frequency); $tracker = AIEO_Session_Tracker::get_instance(); $tracker->schedule_archive_cron(); } } if (isset($_POST['aieo_session_archive_hour'])) { $hour = absint(wp_unslash($_POST['aieo_session_archive_hour'])); $hour = max(0, min(23, $hour)); update_option('aieo_session_archive_hour', $hour); $tracker = AIEO_Session_Tracker::get_instance(); $tracker->schedule_archive_cron(); } $this->generate_custom_tracker_js(); $base = time(); $random_suffix = wp_rand(1, 999999999); $new_version = $base . $random_suffix; $new_version = substr($new_version, 0, wp_rand(8, strlen($new_version))); update_option('aieo_session_tracker_version', $new_version); } wp_safe_redirect(esc_url(admin_url('admin.php?page=ai-eshop-dmm&dmm_tab=session_tracker'))); exit; } } private function generate_custom_tracker_js() { $custom_rules_raw = get_option('aieo_custom_image_click_rules', ''); $rules_js_code = ''; if (!empty($custom_rules_raw)) { $lines = array_filter(array_map('trim', explode("\n", $custom_rules_raw))); $rules = array(); foreach ($lines as $line) { if (strpos($line, '|') !== false) { list($event_name, $selector) = array_map('trim', explode('|', $line, 2)); if (!empty($event_name) && !empty($selector)) { $event_name_safe = sanitize_key($event_name); $selector_safe = addslashes($selector); $rules[] = " // Rule: {$event_name_safe}\n" . " if (\$target.is('{$selector_safe}') || \$target.closest('{$selector_safe}').length > 0 || \$target.find('{$selector_safe}').length > 0) {\n" . " self.clickCount++; // Count custom rule click\n" . " self.trackCustomClick(\$target, '{$event_name_safe}');\n" . " return true; // Prevent double tracking\n" . " }"; } } } if (!empty($rules)) { $rules_js_code = "\n" . implode("\n\n", $rules) . "\n return false; // No custom rule matched"; } } update_option('aieo_custom_image_click_rules_js', $rules_js_code); } public function render_eshop_optimizer_admin_page() { wp_cache_delete('aieo_create_attributes', 'options'); wp_cache_delete('aieo_create_catalogue', 'options'); wp_cache_delete('aieo_create_orders', 'options'); wp_cache_delete('aieo_erp_consolidation', 'options'); wp_cache_delete('aieo_erp_merge_orders', 'options'); wp_cache_delete('aieo_include_unsold', 'options'); wp_cache_delete('aieo_include_aggregate_stats', 'options'); wp_cache_delete('aieo_include_aggregate_cust_std_stats', 'options'); wp_cache_delete('aieo_include_aggregate_cust_adv_stats', 'options'); wp_cache_delete('aieo_create_uuids', 'options'); wp_cache_delete('aieo_create_export', 'options'); wp_cache_delete('aieo_include_text', 'options'); wp_cache_delete('aieo_include_attributes', 'options'); wp_cache_delete('aieo_include_full_text', 'options'); wp_cache_delete('aieo_include_price', 'options'); wp_cache_delete('aieo_variations_choice', 'options'); wp_cache_delete('aieo_include_eponymous', 'options'); wp_cache_delete('aieo_include_reciprocity', 'options'); wp_cache_delete('aieo_include_seasonality', 'options'); wp_cache_delete('aieo_include_restricted_seasonality', 'options'); wp_cache_delete('aieo_instore_anonymous', 'options'); wp_cache_delete('aieo_season_days_past', 'options'); wp_cache_delete('aieo_season_days_after', 'options'); require_once AIEO_DIR . 'assets/pages/ai-eshop-optimizer.php'; } public function aieo_export_upsells_cross_sells_to_csv() { wp_die(esc_html__('This export now runs asynchronously. Please use the Export button on the settings page to generate the file.', 'ai-eshop-optimizer')); } public function aieo_init_upsell_export() { check_ajax_referer('aieo_export_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions.', 'ai-eshop-optimizer'))); return; } global $wpdb; $statuses = apply_filters('aieo_upsell_export_statuses', array('publish', 'private', 'draft', 'pending', 'future')); $placeholders = implode(',', array_fill(0, count($statuses), '%s')); $total_products = (int) $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status IN ({$placeholders})", array_merge(array('product'), $statuses) )); $uploads_dir = wp_upload_dir(); $parsed_url = wp_parse_url(home_url()); $domain = isset($parsed_url['host']) ? $parsed_url['host'] : 'site'; $timestamp = gmdate('Ymd_His'); $export_id = uniqid('upsell_export_', true); $filename = sprintf('%s_upsells_crossells_%s_%s.csv', $domain, $timestamp, substr(md5($export_id), 0, 8)); $file_path = trailingslashit($uploads_dir['basedir']) . $filename; $file_url = trailingslashit($uploads_dir['baseurl']) . $filename; if (!wp_mkdir_p(dirname($file_path))) { wp_send_json_error(array('message' => __('Unable to prepare export directory.', 'ai-eshop-optimizer'))); return; } $state = array( 'file_path' => $this->aieo_normalize_path($file_path), 'file_url' => $file_url, 'file_name' => $filename, 'header_written' => false, 'last_id' => 0, 'processed' => 0, 'total' => $total_products, 'batch_size' => 3000, 'statuses' => $statuses, ); set_transient('aieo_upsell_export_state_' . $export_id, $state, 2 * HOUR_IN_SECONDS); if ($total_products === 0) { $handle = fopen($state['file_path'], 'w'); if ($handle) { fputcsv($handle, array('original_product_id', 'related_upsells_list', 'related_crossells_list', 'aieo_neo4j_params')); fclose($handle); } update_option('aieo_last_upsell_export', array( 'filename' => $filename, 'path' => $state['file_path'], 'url' => $file_url, 'generated_at' => current_time('mysql', true), )); delete_transient('aieo_upsell_export_state_' . $export_id); wp_send_json_success(array( 'complete' => true, 'message' => __('No products found. Generated empty export file.', 'ai-eshop-optimizer'), 'download_url' => $file_url, 'filename' => $filename, )); return; } wp_send_json_success(array( 'message' => sprintf(__('Initializing upsell & cross-sell export for %d products...', 'ai-eshop-optimizer'), $total_products), 'export_id' => $export_id, 'total' => $total_products, )); } public function aieo_process_upsell_batch() { check_ajax_referer('aieo_export_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions.', 'ai-eshop-optimizer'))); return; } $export_id = isset($_POST['export_id']) ? sanitize_text_field(wp_unslash($_POST['export_id'])) : ''; if (!$export_id) { wp_send_json_error(array('message' => __('Missing export identifier.', 'ai-eshop-optimizer'))); return; } $state_key = 'aieo_upsell_export_state_' . $export_id; $state = get_transient($state_key); if (!$state) { wp_send_json_error(array('message' => __('Export session expired. Please restart the export.', 'ai-eshop-optimizer'))); return; } global $wpdb; $statuses = $state['statuses']; $placeholders = implode(',', array_fill(0, count($statuses), '%s')); $batch_size = (int) $state['batch_size']; $last_id = (int) $state['last_id']; $product_ids = $wpdb->get_col($wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status IN ({$placeholders}) AND ID > %d ORDER BY ID ASC LIMIT %d", array_merge(array('product'), $statuses, array($last_id, $batch_size)) )); if (empty($product_ids)) { update_option('aieo_last_upsell_export', array( 'filename' => $state['file_name'], 'path' => $state['file_path'], 'url' => $state['file_url'], 'generated_at' => current_time('mysql', true), )); delete_transient($state_key); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ Upsell & cross-sell export completed: %d products processed.', 'ai-eshop-optimizer'), $state['processed']), 'download_url' => $state['file_url'], 'filename' => $state['file_name'], 'current' => $state['processed'], 'total' => $state['total'], )); return; } set_time_limit(0); $handle = fopen($state['file_path'], $state['header_written'] ? 'a' : 'w'); if (!$handle) { delete_transient($state_key); wp_send_json_error(array('message' => __('Unable to write to export file.', 'ai-eshop-optimizer'))); return; } if (!$state['header_written']) { fputcsv($handle, array('original_product_id', 'related_upsells_list', 'related_crossells_list', 'aieo_neo4j_params')); $state['header_written'] = true; } update_postmeta_cache($product_ids); foreach ($product_ids as $product_id) { $upsells = get_post_meta($product_id, '_upsell_ids', true); $upsells = is_array($upsells) ? array_map('intval', $upsells) : array(); $upsells = array_filter($upsells); $crossells = get_post_meta($product_id, '_crosssell_ids', true); $crossells = is_array($crossells) ? array_map('intval', $crossells) : array(); $crossells = array_filter($crossells); $params = get_post_meta($product_id, 'aieo_neo4j_params', true); if (is_array($params) || is_object($params)) { $params = wp_json_encode($params); } $params = is_scalar($params) ? (string) $params : ''; fputcsv($handle, array( $product_id, implode(',', $upsells), implode(',', $crossells), $params, )); } fclose($handle); $state['processed'] += count($product_ids); $state['last_id'] = (int) end($product_ids); set_transient($state_key, $state, 2 * HOUR_IN_SECONDS); update_option('aieo_upsell_import_state_store_' . $import_id, $state, false); $percent = $state['total'] > 0 ? round(($state['processed'] / $state['total']) * 100) : 100; wp_send_json_success(array( 'complete' => false, 'message' => sprintf( __('Processing products %1$d-%2$d of %3$d (%4$s%% complete)...', 'ai-eshop-optimizer'), max(1, $state['processed'] - count($product_ids) + 1), $state['processed'], $state['total'], $percent ), 'current' => $state['processed'], 'total' => $state['total'], 'progress' => $percent, )); } public function aieo_init_upsell_import() { check_ajax_referer('aieo_export_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions.', 'ai-eshop-optimizer'))); return; } if (!isset($_POST['eshop_optimizer_process_params_nonce_6']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['eshop_optimizer_process_params_nonce_6'])), 'eshop_optimizer_handle_upload_action')) { wp_send_json_error(array('message' => __('Security check failed. Please reload the page and try again.', 'ai-eshop-optimizer'))); return; } if (empty($_FILES['csv_file']['tmp_name'])) { wp_send_json_error(array('message' => __('Please select a CSV file to upload.', 'ai-eshop-optimizer'))); return; } global $wpdb; $monitor = isset($_POST['aieo_monitor_performance']) && sanitize_text_field(wp_unslash($_POST['aieo_monitor_performance'])) === '1'; $batch_size = isset($_POST['aieo_batch_size']) ? max(1, intval(sanitize_text_field(wp_unslash($_POST['aieo_batch_size'])))) : 300; $batch_sleep = isset($_POST['aieo_batch_sleep']) ? max(0, intval(sanitize_text_field(wp_unslash($_POST['aieo_batch_sleep'])))) : 0; update_option('aieo_monitor_performance', $monitor ? '1' : '0'); update_option('aieo_batch_size', $batch_size); update_option('aieo_batch_sleep', $batch_sleep); $table_name = $wpdb->prefix . 'aieo_' . gmdate('Y_m') . '_new_recommendations'; if (!preg_match('/^[a-zA-Z0-9_]+$/', $table_name)) { wp_send_json_error(array('message' => __('Invalid temporary table name.', 'ai-eshop-optimizer'))); return; } $wpdb->query("DROP TABLE IF EXISTS `{$table_name}`"); $wpdb->query("CREATE TABLE `{$table_name}` ( original_product_id BIGINT(20) UNSIGNED NOT NULL, related_upsells_list TEXT, related_crossells_list TEXT, aieo_neo4j_params TEXT, PRIMARY KEY (original_product_id) )"); if ($monitor) { $performance_table = $wpdb->prefix . 'aieo_performance_tuning'; $wpdb->query("DROP TABLE IF EXISTS {$performance_table}"); $wpdb->query("CREATE TABLE {$performance_table} ( id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, batch_size INT(11) UNSIGNED NOT NULL, processing_time FLOAT NOT NULL, total_processing_time FLOAT NOT NULL, date_recorded TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) )"); } if (!function_exists('wp_handle_upload')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } $uploaded_file = $_FILES['csv_file']; $uploaded_file['name'] = sanitize_file_name($uploaded_file['name']); $movefile = wp_handle_upload($uploaded_file, array('test_form' => false)); if (!$movefile || isset($movefile['error'])) { wp_send_json_error(array('message' => isset($movefile['error']) ? $movefile['error'] : __('Upload failed.', 'ai-eshop-optimizer'))); return; } $file_path = $movefile['file']; $handle = fopen($file_path, 'r'); if (!$handle) { wp_delete_file($file_path); wp_send_json_error(array('message' => __('Unable to open uploaded file.', 'ai-eshop-optimizer'))); return; } set_time_limit(0); $row_number = 0; while (($row = fgetcsv($handle)) !== false) { if ($row_number === 0) { $row_number++; continue; } if (empty($row)) { continue; } $product_id = isset($row[0]) ? intval($row[0]) : 0; if ($product_id <= 0) { continue; } $upsells = isset($row[1]) ? sanitize_text_field($row[1]) : ''; $crossells = isset($row[2]) ? sanitize_text_field($row[2]) : ''; $params = isset($row[3]) ? sanitize_text_field($row[3]) : ''; $wpdb->replace( $table_name, array( 'original_product_id' => $product_id, 'related_upsells_list' => $upsells, 'related_crossells_list' => $crossells, 'aieo_neo4j_params' => $params, ), array('%d', '%s', '%s', '%s') ); } fclose($handle); $total_rows = (int) $wpdb->get_var("SELECT COUNT(*) FROM `{$table_name}` WHERE related_upsells_list <> '' OR related_crossells_list <> ''"); if ($total_rows === 0) { wp_delete_file($file_path); $wpdb->query("DROP TABLE IF EXISTS `{$table_name}`"); wp_send_json_success(array( 'complete' => true, 'message' => __('No records found in the uploaded file. Nothing to import.', 'ai-eshop-optimizer'), 'total' => 0, )); return; } $import_id = uniqid('upsell_import_', true); $state = array( 'table' => $table_name, 'file' => $file_path, 'processed' => 0, 'impacted' => 0, 'total' => $total_rows, 'last_id' => 0, 'batch_size' => $batch_size, 'batch_sleep' => $batch_sleep, 'monitor' => $monitor, ); set_transient('aieo_upsell_import_state_' . $import_id, $state, 2 * HOUR_IN_SECONDS); update_option('aieo_upsell_import_state_store_' . $import_id, $state, false); wp_send_json_success(array( 'message' => sprintf(__('Initializing upsell import for %d records...', 'ai-eshop-optimizer'), $total_rows), 'import_id' => $import_id, 'total' => $total_rows, )); } public function aieo_process_upsell_import_batch() { check_ajax_referer('aieo_export_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions.', 'ai-eshop-optimizer'))); return; } $import_id = isset($_POST['import_id']) ? sanitize_text_field(wp_unslash($_POST['import_id'])) : ''; if (!$import_id) { wp_send_json_error(array('message' => __('Missing import identifier.', 'ai-eshop-optimizer'))); return; } $state_key = 'aieo_upsell_import_state_' . $import_id; $state = get_transient($state_key); if (!$state || !is_array($state)) { $state = get_option('aieo_upsell_import_state_store_' . $import_id, false); } if (!$state || !is_array($state)) { delete_option('aieo_upsell_import_state_store_' . $import_id); wp_send_json_error(array('message' => __('Import session expired. Please restart the import.', 'ai-eshop-optimizer'))); return; } global $wpdb; $table_name = $state['table']; if (!preg_match('/^[a-zA-Z0-9_]+$/', $table_name)) { delete_transient($state_key); wp_send_json_error(array('message' => __('Invalid temporary table detected.', 'ai-eshop-optimizer'))); return; } $batch_size = max(1, (int) $state['batch_size']); $batch_sleep = max(0, (int) $state['batch_sleep']); $last_id = (int) $state['last_id']; set_time_limit(0); $rows = $wpdb->get_results($wpdb->prepare( "SELECT * FROM `{$table_name}` WHERE (related_upsells_list <> '' OR related_crossells_list <> '') AND original_product_id > %d ORDER BY original_product_id ASC LIMIT %d", $last_id, $batch_size ), ARRAY_A); if (empty($rows)) { wp_delete_file($state['file']); $wpdb->query("DROP TABLE IF EXISTS `{$table_name}`"); delete_transient($state_key); delete_option('aieo_upsell_import_state_store_' . $import_id); update_option('aieo_last_upsell_import', array( 'processed' => $state['processed'], 'impacted' => $state['impacted'], 'completed_at' => current_time('mysql', true), )); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ Upsell import completed: %d records processed.', 'ai-eshop-optimizer'), $state['processed']), 'summary' => sprintf(__('Total products updated: %d', 'ai-eshop-optimizer'), $state['impacted']), 'current' => $state['processed'], 'total' => $state['total'], )); return; } $start_time = microtime(true); $impacted = 0; foreach ($rows as $row) { $product_id = intval($row['original_product_id']); $upsells = array_filter(array_map('intval', array_map('trim', explode(',', $row['related_upsells_list'])))); $crossells = array_filter(array_map('intval', array_map('trim', explode(',', $row['related_crossells_list'])))); $params = $row['aieo_neo4j_params']; $impacted += $this->assign_upsells_cross_sells($product_id, $upsells, $crossells, $params); } $elapsed = microtime(true) - $start_time; if (!empty($state['monitor'])) { $performance_table = $wpdb->prefix . 'aieo_performance_tuning'; $wpdb->insert( $performance_table, array( 'batch_size' => count($rows), 'processing_time' => $elapsed, 'total_processing_time' => ($state['processed'] + count($rows)) > 0 ? ($elapsed * ($state['total'] / max(1, count($rows)))) : 0, ), array('%d', '%f', '%f') ); } $state['processed'] += count($rows); $state['impacted'] += $impacted; $last_row = end($rows); $state['last_id'] = $last_row ? (int) $last_row['original_product_id'] : $state['last_id']; set_transient($state_key, $state, 2 * HOUR_IN_SECONDS); if ($batch_sleep > 0) { usleep($batch_sleep * 1000000); } $percent = $state['total'] > 0 ? round(($state['processed'] / $state['total']) * 100) : 100; wp_send_json_success(array( 'complete' => false, 'message' => sprintf( __('Processed %1$d of %2$d records (%3$s%% complete).', 'ai-eshop-optimizer'), $state['processed'], $state['total'], $percent ), 'current' => $state['processed'], 'total' => $state['total'], 'progress' => $percent, )); } public function aieo_handle_export_orders() { if ( isset($_POST['eshop_optimizer_handle_export_orders_nonce_3']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['eshop_optimizer_handle_export_orders_nonce_3'])), 'eshop_optimizer_handle_export_orders_action') ) { global $wpdb; $original_time_limit = ini_get('max_execution_time'); $product_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish'"); $order_item_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_order_items"); $timeout = ($product_count + $order_item_count) / 50; set_time_limit($timeout); register_shutdown_function(function () use ($original_time_limit) { set_time_limit($original_time_limit); }); $site_prefix = $wpdb->prefix; $orders_table_name = $wpdb->prefix . "aieo_" . gmdate('Y_m') . "_orders"; $orders_table_name = sanitize_text_field($orders_table_name); if (!preg_match('/^[a-zA-Z0-9_]+$/', $orders_table_name)) { return false; } $drop_orders_table_query = $wpdb->prepare("DROP TABLE IF EXISTS {$orders_table_name};"); $order_id = 0; if (isset($_POST['export_orders'])) { if (isset($_POST['aieo_order_id']) && !empty($_POST['aieo_order_id'])) { $order_id = absint($_POST['aieo_order_id']); $max_records = isset($_POST['aieo_max_records']) ? absint($_POST['aieo_max_records']) : 0; } if (current_user_can(AIEO_CAPABILITY)) { update_option('aieo_create_orders', (isset($_POST['aieo_create_orders']) && $_POST['aieo_create_orders'] == '1') ? '1' : '0'); update_option('aieo_activate_account', (isset($_POST['aieo_activate_account']) && $_POST['aieo_activate_account'] == '1') ? '1' : '0'); update_option('aieo_erp_consolidation', (isset($_POST['aieo_erp_consolidation']) && $_POST['aieo_erp_consolidation'] == '1') ? '1' : '0'); update_option('aieo_erp_merge_orders', (isset($_POST['aieo_erp_merge_orders']) && $_POST['aieo_erp_merge_orders'] == '1') ? '1' : '0'); update_option('aieo_erp_anonymous_consolidation', (isset($_POST['aieo_erp_anonymous_consolidation']) && $_POST['aieo_erp_anonymous_consolidation'] == '1') ? '1' : '0'); update_option('aieo_erp_stats_per_channel', (isset($_POST['aieo_erp_stats_per_channel']) && $_POST['aieo_erp_stats_per_channel'] == '1') ? '1' : '0'); update_option('aieo_include_eponymous', (isset($_POST['aieo_include_eponymous']) && $_POST['aieo_include_eponymous'] == '1') ? '1' : '0'); update_option('aieo_include_reciprocity', (isset($_POST['aieo_include_reciprocity']) && $_POST['aieo_include_reciprocity'] == '1') ? '1' : '0'); update_option('aieo_include_seasonality', (isset($_POST['aieo_include_seasonality']) && $_POST['aieo_include_seasonality'] == '1') ? '1' : '0'); update_option('aieo_include_restricted_seasonality', (isset($_POST['aieo_include_restricted_seasonality']) && $_POST['aieo_include_restricted_seasonality'] == '1') ? '1' : '0'); update_option('aieo_instore_anonymous', (isset($_POST['aieo_instore_anonymous']) && $_POST['aieo_instore_anonymous'] == '1') ? '1' : '0'); update_option('aieo_include_price', (isset($_POST['aieo_include_price']) && $_POST['aieo_include_price'] == '1') ? '1' : '0'); update_option('aieo_variations_choice', (isset($_POST['aieo_variations_choice']) && $_POST['aieo_variations_choice'] == '1') ? '1' : '0'); update_option('aieo_include_aggregate_stats', (isset($_POST['aieo_include_aggregate_stats']) && $_POST['aieo_include_aggregate_stats'] == '1') ? '1' : '0'); update_option('aieo_include_aggregate_category_brand_stats', (isset($_POST['aieo_include_aggregate_category_brand_stats']) && $_POST['aieo_include_aggregate_category_brand_stats'] == '1') ? '1' : '0'); if (isset($_POST['aieo_category_sales_period'])) { $aieo_period_in = sanitize_text_field(wp_unslash($_POST['aieo_category_sales_period'])); if (in_array($aieo_period_in, array('all_time', 'last_30_days', 'last_3_months', 'last_6_months', 'last_12_months'), true)) { update_option('aieo_category_sales_period', $aieo_period_in); } } update_option('aieo_include_aggregate_cust_std_stats', (isset($_POST['aieo_include_aggregate_cust_std_stats']) && $_POST['aieo_include_aggregate_cust_std_stats'] == '1') ? '1' : '0'); update_option('aieo_include_aggregate_cust_adv_stats', (isset($_POST['aieo_include_aggregate_cust_adv_stats']) && $_POST['aieo_include_aggregate_cust_adv_stats'] == '1') ? '1' : '0'); update_option('aieo_include_text', (isset($_POST['aieo_include_text']) && $_POST['aieo_include_text'] == '1') ? '1' : '0'); update_option('aieo_include_attributes', (isset($_POST['aieo_include_attributes']) && $_POST['aieo_include_attributes'] == '1') ? '1' : '0'); update_option('aieo_include_full_text', (isset($_POST['aieo_include_full_text']) && $_POST['aieo_include_full_text'] == '1') ? '1' : '0'); update_option('aieo_create_attributes', (isset($_POST['aieo_create_attributes']) && $_POST['aieo_create_attributes'] == '1') ? '1' : '0'); update_option('aieo_create_needs', (isset($_POST['aieo_create_needs']) && $_POST['aieo_create_needs'] == '1') ? '1' : '0'); update_option('aieo_create_need_suggestions', (isset($_POST['aieo_create_need_suggestions']) && $_POST['aieo_create_need_suggestions'] == '1') ? '1' : '0'); update_option('aieo_create_catalogue', (isset($_POST['aieo_create_catalogue']) && $_POST['aieo_create_catalogue'] == '1') ? '1' : '0'); update_option('aieo_include_unsold', (isset($_POST['aieo_include_unsold']) && $_POST['aieo_include_unsold'] == '1') ? '1' : '0'); update_option('aieo_create_uuids', (isset($_POST['aieo_create_uuids']) && $_POST['aieo_create_uuids'] == '1') ? '1' : '0'); update_option('aieo_create_export', (isset($_POST['aieo_create_export']) && $_POST['aieo_create_export'] == '1') ? '1' : '0'); if (isset($_POST['aieo_order_id'])) { update_option('aieo_order_id', sanitize_text_field(wp_unslash($_POST['aieo_order_id']))); } if (isset($_POST['aieo_max_records'])) { update_option('aieo_max_records', sanitize_text_field(wp_unslash($_POST['aieo_max_records']))); } if (isset($_POST['aieo_core_attribute_vendor'])) { update_option('aieo_core_attribute_vendor', sanitize_text_field(wp_unslash($_POST['aieo_core_attribute_vendor']))); } if (isset($_POST['aieo_core_attribute_brands'])) { update_option('aieo_core_attribute_brands', sanitize_text_field(wp_unslash($_POST['aieo_core_attribute_brands']))); } if (isset($_POST['aieo_core_attribute_size'])) { update_option('aieo_core_attribute_size', sanitize_text_field(wp_unslash($_POST['aieo_core_attribute_size']))); } if (isset($_POST['aieo_core_attribute_color'])) { update_option('aieo_core_attribute_color', sanitize_text_field(wp_unslash($_POST['aieo_core_attribute_color']))); } if (isset($_POST['aieo_core_attribute_need'])) { update_option('aieo_core_attribute_need', sanitize_text_field(wp_unslash($_POST['aieo_core_attribute_need']))); } if (isset($_POST['aieo_season_days_past'])) { update_option('aieo_season_days_past', sanitize_text_field(wp_unslash($_POST['aieo_season_days_past']))); } if (isset($_POST['aieo_season_days_after'])) { update_option('aieo_season_days_after', sanitize_text_field(wp_unslash($_POST['aieo_season_days_after']))); } if (isset($_POST['aieo_erp_store_anonymous_invoice_users'])) { update_option('aieo_erp_store_anonymous_invoice_users', sanitize_text_field(wp_unslash($_POST['aieo_erp_store_anonymous_invoice_users']))); } if (isset($_POST['aieo_erp_series_to_ignore'])) { update_option('aieo_erp_series_to_ignore', sanitize_text_field(wp_unslash($_POST['aieo_erp_series_to_ignore']))); } if (isset($_POST['aieo_erp_products_to_ignore'])) { update_option('aieo_erp_products_to_ignore', sanitize_text_field(wp_unslash($_POST['aieo_erp_products_to_ignore']))); } if (isset($_POST['aieo_erp_product_price_to_ignore'])) { update_option('aieo_erp_product_price_to_ignore', sanitize_text_field(wp_unslash($_POST['aieo_erp_product_price_to_ignore']))); } } $create_orders = isset($_POST['aieo_create_orders']) && $_POST['aieo_create_orders'] === '1'; $orders_sql_choice = $create_orders ? 1 : 0; $erp_orders = isset($_POST['aieo_erp_consolidation']) && $_POST['aieo_erp_consolidation'] === '1'; $erp_sql_choice = $erp_orders ? 1 : 0; $erp_merge_orders = isset($_POST['aieo_erp_merge_orders']) && $_POST['aieo_erp_merge_orders'] === '1'; $erp_merge_orders_sql_choice = $erp_merge_orders ? 1 : 0; $erp_anonymous_orders = isset($_POST['aieo_erp_anonymous_consolidation']) && $_POST['aieo_erp_anonymous_consolidation'] === '1'; $erp_anonymous_sql_choice = $erp_anonymous_orders ? 1 : 0; $erp_stats_per_channel = isset($_POST['aieo_erp_stats_per_channel']) && $_POST['aieo_erp_stats_per_channel'] === '1'; $erp_stats_per_channel_sql_choice = $erp_stats_per_channel ? 1 : 0; $include_text = isset($_POST['aieo_include_text']) && $_POST['aieo_include_text'] === '1'; $text_sql_choice = $include_text ? 1 : 0; $include_attributes = isset($_POST['aieo_include_attributes']) && $_POST['aieo_include_attributes'] === '1'; $attributes_sql_choice = $include_attributes ? 1 : 0; $include_full_text = isset($_POST['aieo_include_full_text']) && $_POST['aieo_include_full_text'] === '1'; $full_text_sql_choice = $include_full_text ? 1 : 0; $include_price = isset($_POST['aieo_include_price']) && $_POST['aieo_include_price'] === '1'; $price_sql_choice = $include_price ? 1 : 0; $season_days_past = sanitize_text_field(wp_unslash($_POST['aieo_season_days_past'])); $season_days_after = sanitize_text_field(wp_unslash($_POST['aieo_season_days_after'])); $variations_choice = isset($_POST['aieo_variations_choice']) && $_POST['aieo_variations_choice'] === '1'; $variations_sql_choice = $variations_choice ? 1 : 0; $include_aggregate_stats = isset($_POST['aieo_include_aggregate_stats']) && $_POST['aieo_include_aggregate_stats'] === '1'; $aggregate_stats_sql_choice = $include_aggregate_stats ? 1 : 0; $create_attributes = isset($_POST['aieo_create_attributes']) && $_POST['aieo_create_attributes'] === '1'; $attributes_sql_choice = $create_attributes ? 1 : 0; $create_needs = isset($_POST['aieo_create_needs']) && $_POST['aieo_create_needs'] === '1'; $needs_sql_choice = $create_needs ? 1 : 0; $create_catalogue = isset($_POST['aieo_create_catalogue']) && $_POST['aieo_create_catalogue'] === '1'; $catalogue_sql_choice = $create_catalogue ? 1 : 0; $create_uuids = isset($_POST['aieo_create_uuids']) && $_POST['aieo_create_uuids'] === '1'; $uuids_sql_choice = $create_uuids ? 1 : 0; $create_export = isset($_POST['aieo_create_export']) && $_POST['aieo_create_export'] === '1'; $export_sql_choice = $create_export ? 1 : 0; $include_unsold = isset($_POST['aieo_include_unsold']) && $_POST['aieo_include_unsold'] === '1'; $unsold_sql_choice = $include_unsold ? 1 : 0; $include_seasonality = isset($_POST['aieo_include_seasonality']) && $_POST['aieo_include_seasonality'] === '1'; $include_restricted_seasonality = isset($_POST['aieo_include_restricted_seasonality']) && $_POST['aieo_include_restricted_seasonality'] === '1'; if ($include_seasonality && $include_restricted_seasonality) { $seasonality_sql_choice = 2; } elseif ($include_seasonality) { $seasonality_sql_choice = 1; } elseif ($include_restricted_seasonality) { $seasonality_sql_choice = 0; } else { $seasonality_sql_choice = 0; } $include_instore_anonymous = isset($_POST['aieo_instore_anonymous']) && $_POST['aieo_instore_anonymous'] === '1'; $instore_anonymous_sql_choice = $include_instore_anonymous ? 1 : 0; $instore_anonymous_list = get_option('aieo_erp_store_anonymous_invoice_users'); $instore_anonymous_list = sanitize_text_field($instore_anonymous_list); $instore_invoices_list = get_option('aieo_erp_series_to_ignore'); $instore_invoices_list = sanitize_text_field($instore_invoices_list); $instore_products_list = get_option('aieo_erp_products_to_ignore'); $instore_products_list = sanitize_text_field($instore_products_list); $instore_product_threshold = get_option('aieo_erp_product_price_to_ignore'); $instore_product_threshold = sanitize_text_field($instore_product_threshold); $instore_product_threshold_sql_choice = !empty($instore_product_threshold) ? 1 : 0; $instore_invoices_list_sql_choice = !empty($instore_invoices_list) ? 1 : 0; $include_reciprocicty = isset($_POST['aieo_include_reciprocity']) && $_POST['aieo_include_reciprocity'] === '1'; $reciprocity_sql_choice = $include_reciprocicty ? 1 : 0; $include_eponymous = isset($_POST['aieo_include_eponymous']) && $_POST['aieo_include_eponymous'] === '1'; $eponymous_sql_choice = $include_eponymous ? 1 : 0; $temp_orders_table_name = get_option('aieo_site_orders_table'); $temp_orders_table_name = sanitize_text_field($temp_orders_table_name); if (!preg_match('/^[a-zA-Z0-9_]+$/', $temp_orders_table_name)) { return false; } $temp_products_table_name = get_option('aieo_site_products_table'); $eponymousStatus = ($eponymous_sql_choice == '1') ? ', and user ids provided' : ''; $seasonalityStatus = ($seasonality_sql_choice !== '0') ? ', and seasonality active' : ''; $script_version = 'Progress Report: '; if ((isset($_POST['aieo_create_attributes']) && $_POST['aieo_create_attributes'] == '1') || get_option('aieo_create_attributes', '1') == '1') { $vendor_attribute = get_option('aieo_core_attribute_vendor'); $brand_attribute = get_option('aieo_core_attribute_brands'); $size_attributes = get_option('aieo_core_attribute_size'); $color_attributes = get_option('aieo_core_attribute_color'); $aieo_clean_attributes = function ($attributes) { return implode(", ", array_map(function ($attr) { return "'" . trim($attr) . "'"; }, explode(",", $attributes))); }; $cleaned_size_attributes = $aieo_clean_attributes($size_attributes); $cleaned_color_attributes = $aieo_clean_attributes($color_attributes); $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_InsertCoreProductAttributes` (%s, %s, %s, %s)", $vendor_attribute, $brand_attribute, $cleaned_size_attributes, $cleaned_color_attributes )); $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_InsertProductCategoryHierarchy` ();" )); delete_option('aieo_chat_cached_categories'); $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_InsertProductVitals` ();" )); $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_InsertVariationVitals` ();" )); $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_CombineVariationVitalsInProductVitals` ();" )); if (!class_exists('AI_eShop_Product_Needs')) { require_once plugin_dir_path(__FILE__) . 'class-product-needs.php'; } AI_eShop_Product_Needs::get_instance()->populate_product_needs(); delete_transient('aieo_chat_sellable_breakdown'); delete_transient('aieo_chat_count_sellable_units_0'); delete_transient('aieo_chat_count_sellable_units_1'); $script_version = $script_version . 'Created the product vital attributes and product category hierarchy. '; } if ((isset($_POST['aieo_create_needs']) && $_POST['aieo_create_needs'] == '1') || get_option('aieo_create_needs', '1') == '1') { require_once plugin_dir_path(__FILE__) . 'class-product-needs.php'; $needs_handler = AI_eShop_Product_Needs::get_instance(); if ($needs_handler->populate_product_needs()) { $script_version = $script_version . 'Successfully populated product needs data. '; } else { $script_version = $script_version . 'Warning: Failed to populate product needs data. '; } } if ((isset($_POST['aieo_create_catalogue']) && $_POST['aieo_create_catalogue'] == '1') || get_option('aieo_create_catalogue', '1') == '1') { $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_Create_Product_Catalogue_HPOS` (%d, %d, %d)", $price_sql_choice, $text_sql_choice, $full_text_sql_choice )); $script_version = $script_version . 'A fresh product catalogue has been created. '; } if ((isset($_POST['aieo_create_orders']) && $_POST['aieo_create_orders'] == '1') || get_option('aieo_create_orders', '1') == '1') { if (class_exists('Automattic\WooCommerce\Utilities\OrderUtil') && function_exists('WC')) { if (Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled()) { $script_version = 'HPOS via SP for ' . $max_records . ' records starting at Order_Id ' . $order_id . $eponymousStatus . $seasonalityStatus; $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_Export_Orders_HPOS` (%d, %d, %d, %d, %d, %d, %d)", $price_sql_choice, $text_sql_choice, $full_text_sql_choice, $eponymous_sql_choice, $seasonality_sql_choice, $order_id, $max_records )); } else { $script_version = $script_version . 'Non-HPOS via SP for ' . $max_records . ' records starting at Order_Id ' . $order_id . $eponymousStatus . $seasonalityStatus; $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_Export_Orders_non_HPOS` (%d, %d, %d, %d, %d, %d, %d)", $price_sql_choice, $text_sql_choice, $full_text_sql_choice, $eponymous_sql_choice, $seasonality_sql_choice, $order_id, $max_records )); } } } else { $script_version = $script_version . 'The eshop orders were not processed this time!'; } if ((isset($_POST['aieo_erp_consolidation']) && $_POST['aieo_erp_consolidation'] == '1') || get_option('aieo_erp_consolidation', '1') == '1') { $include_instore_anonymous = isset($_POST['aieo_instore_anonymous']) && $_POST['aieo_instore_anonymous'] === '1'; $instore_anonymous_sql_choice = $include_instore_anonymous ? 1 : 0; $instore_anonymous_list = get_option('aieo_erp_store_anonymous_invoice_users'); $instore_anonymous_list = sanitize_text_field($instore_anonymous_list); $instore_invoices_list = get_option('aieo_erp_series_to_ignore'); $instore_invoices_list = sanitize_text_field($instore_invoices_list); $instore_product_threshold = get_option('aieo_erp_product_price_to_ignore'); $instore_product_threshold = sanitize_text_field($instore_product_threshold); $instore_invoices_list_sql_choice = !empty($instore_invoices_list) ? 1 : 0; $aieo_clean_list_values = function ($list_values) { return implode(", ", array_map(function ($attr) { return "'" . trim($attr) . "'"; }, explode(",", $list_values))); }; $cleaned_instore_invoices_list = $aieo_clean_list_values($instore_invoices_list); $cleaned_instore_anonymous_list = $aieo_clean_list_values($instore_anonymous_list); $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_Export_Store_Orders` (%d, %d, %d, %d, %d, %d, %s, %d, %s)", $price_sql_choice, $eponymous_sql_choice, $seasonality_sql_choice, $season_days_past, $season_days_after, $instore_invoices_list_sql_choice, $instore_invoices_list, $instore_anonymous_sql_choice, $instore_anonymous_list )); $script_version = $script_version . 'Integrated the in-store orders. '; } if ((isset($_POST['aieo_erp_merge_orders']) && $_POST['aieo_erp_merge_orders'] == '1') || get_option('aieo_erp_merge_orders', '1') == '1') { $store_orders_table_name = get_option('aieo_store_orders_table'); $store_orders_table_name = sanitize_text_field($store_orders_table_name); $analytics_eponymous_id_field = get_option('aieo_analytics_eponymous_id_field'); $analytics_eponymous_id_field = sanitize_text_field($analytics_eponymous_id_field); $instore_product_threshold = get_option('aieo_erp_product_price_to_ignore'); $instore_product_threshold = sanitize_text_field($instore_product_threshold); $instore_product_threshold_sql_choice = !empty($instore_product_threshold) ? 1 : 0; $instore_products_list = get_option('aieo_erp_products_to_ignore'); $instore_products_list = sanitize_text_field($instore_products_list); $instore_products_list_sql_choice = !empty($instore_products_list) ? 1 : 0; $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_Merge_Store_Orders` (%s, %s, %s, %d, %s, %d, %s)", $store_orders_table_name, $temp_orders_table_name, $analytics_eponymous_id_field, $instore_product_threshold_sql_choice, $instore_product_threshold, $instore_products_list_sql_choice, $instore_products_list )); $script_version = $script_version . 'Integrated the in-store orders. '; } if ((isset($_POST['aieo_include_unsold']) && $_POST['aieo_include_unsold'] == '1') || get_option('aieo_include_unsold', '1') == '1') { if (preg_match('/^[a-zA-Z0-9_]+$/', $site_prefix)) { $wpdb->query("SET @output_message = ''"); $wpdb->query($wpdb->prepare("CALL `{$site_prefix}AIEO_UpdateOrdersWithUnsold`(@output_message)")); $output_message_from_sp = $wpdb->get_var("SELECT @output_message AS output_message"); } else { } if (strpos($output_message_from_sp, 'Warning') !== false) { $message = 'The operation did not complete! Regenerate the necessary tables! ' . $output_message_from_sp; update_option('aieo_export_orders_message', $message, false); $redirect_url = add_query_arg( array( 'page' => 'ai-eshop-optimizer', 'exported_orders' => '1', '_wpnonce' => wp_create_nonce('exported_orders_action') ), admin_url('admin.php') ); wp_safe_redirect($redirect_url); exit; } else { $script_version = $script_version . $output_message_from_sp; } } if ((isset($_POST['aieo_include_aggregate_stats']) && $_POST['aieo_include_aggregate_stats'] == '1') || get_option('aieo_include_aggregate_stats', '0') == '1') { $result = $wpdb->query($wpdb->prepare("CALL `{$site_prefix}AIEO_UpdateProductCentricStats` ('wp_aieo_temp_orders', 0);")); $script_version = $script_version . ', and the Product stats'; } if ((isset($_POST['aieo_include_aggregate_cust_std_stats']) && $_POST['aieo_include_aggregate_cust_std_stats'] == '1') || get_option('aieo_include_aggregate_cust_std_stats', '0') == '1') { $result = $wpdb->query($wpdb->prepare("CALL `{$site_prefix}AIEO_UpdateCustomerCentricStdStats` ('$temp_orders_table_name', 0);")); $script_version = $script_version . ', and the Customer Standard Statistics were computed'; } if ((isset($_POST['aieo_include_aggregate_cust_adv_stats']) && $_POST['aieo_include_aggregate_cust_adv_stats'] == '1') || get_option('aieo_include_aggregate_cust_adv_stats', '0') == '1') { @set_time_limit(300); $result = $wpdb->query($wpdb->prepare("CALL `{$site_prefix}AIEO_UpdateCustomerCentricAdvStats` ('$temp_orders_table_name', 0);")); $script_version = $script_version . ', and the Customer Adv Statistics were computed'; } if ((isset($_POST['aieo_create_uuids']) && $_POST['aieo_create_uuids'] == '1') || get_option('aieo_create_uuids', '0') == '1') { $result = $wpdb->query($wpdb->prepare("CALL `{$site_prefix}AIEO_GenerateGraphDBUUIDFreqs` ('$temp_orders_table_name');")); $script_version = $script_version . ', and generated the UUIDs'; } if ((isset($_POST['create_need_suggestions']) && $_POST['create_need_suggestions'] == '1') || get_option('aieo_create_need_suggestions', '0') == '1') { require_once plugin_dir_path(__FILE__) . 'class-need-suggestions.php'; $need_suggestions = AI_eShop_Need_Suggestions::get_instance(); $suggestions_result = $need_suggestions->generate_all_suggestions(); if ($suggestions_result['processed'] > 0) { $script_version = $script_version . sprintf( ', and generated need suggestions for %d products (%d suggestions)', $suggestions_result['processed'], $suggestions_result['suggestions_created'] ); } } } if ((isset($_POST['aieo_include_full_text']) && $_POST['aieo_include_full_text'] == '1') || get_option('aieo_include_full_text', '0') == '1') { $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_FindDifferences` (%s)", $temp_orders_table_name )); $script_version = $script_version . ', and found text diffrences'; $wpdb->query( $wpdb->prepare( "CALL `{$site_prefix}AIEO_UpdateContentOutgoingLinks` (%s)", $temp_orders_table_name ) ); $script_version = $script_version . ', and counted the links in the text'; $wpdb->query( $wpdb->prepare( "CALL `{$site_prefix}AIEO_UpdateContentIntroPlain` (%s)", $temp_orders_table_name ) ); $script_version = $script_version . ', and created the plain text descriptions'; $wpdb->query( $wpdb->prepare( "CALL `{$site_prefix}AIEO_UpdateContentWordCount` (%s)", $temp_orders_table_name ) ); $script_version = $script_version . ', and having counted the words in the description'; } if ((isset($_POST['aieo_create_export']) && $_POST['aieo_create_export'] == '1') || get_option('aieo_create_export', '0') == '1') { global $wpdb; require_once(ABSPATH . 'wp-admin/includes/file.php'); WP_Filesystem(); global $wp_filesystem; $wpdb->query( $wpdb->prepare( "CALL `{$site_prefix}AIEO_RenameTable` (%s, %s);", $temp_orders_table_name, $orders_table_name ) ); aieo_create_temp_orders_table($temp_orders_table_name); $table_columns = $wpdb->get_results("SHOW COLUMNS FROM {$orders_table_name}"); $export_columns = array_map(function ($column) { return $column->Field; }, $table_columns); $uuid_columns = array( 'OrderIdGraphUUID', 'OrderItemIdGraphUUID', 'PPIdGraphUUID', 'ProdIdGraphUUID', 'EpoIdGraphUUID', 'O_PP_GraphUUID', 'O_Prod_GraphUUID', 'O_Epo_GraphUUID', 'PP_Epo_GraphUUID', 'Prod_Epo_GraphUUID' ); $select_columns = array(); foreach ($export_columns as $col) { if (in_array($col, $uuid_columns)) { $select_columns[] = "BIN_TO_UUID({$col}) AS {$col}"; } else { $select_columns[] = $col; } } $results = $wpdb->get_results("SELECT " . implode(', ', $select_columns) . " FROM {$orders_table_name}", ARRAY_A); $file_extension = 'csv'; $uploads_dir = wp_upload_dir(); $parsed_url = wp_parse_url(home_url()); $domain = $parsed_url['host']; $file_path = $uploads_dir['basedir'] . '/' . $domain . '_exported_orders_hpos.csv'; $file_path = $this->aieo_normalize_path($file_path); $csv_content = ''; $csv_content .= implode(',', $export_columns) . "\r\n"; foreach ($results as $result) { $row_data = []; foreach ($export_columns as $col_name) { $row_data[] = $result[$col_name] ?? ''; } $csv_content .= implode(',', $row_data) . "\r\n"; } $wp_filesystem->put_contents($file_path, $csv_content, FS_CHMOD_FILE); $message = $script_version . ': The orders table/file have been created and are ready for transfer/upload!'; update_option('aieo_export_orders_message', $message, false); $redirect_url = add_query_arg( array( 'page' => 'ai-eshop-optimizer', 'exported_orders' => '1', '_wpnonce' => wp_create_nonce('exported_orders_action') ), admin_url('admin.php') ); wp_safe_redirect($redirect_url); exit; } else { $message = $script_version . ': The orders have been created but you have to confirm that you need to create the export file!'; update_option('aieo_export_orders_message', $message, false); $redirect_url = add_query_arg( array( 'page' => 'ai-eshop-optimizer', 'exported_orders' => '1', '_wpnonce' => wp_create_nonce('exported_orders_action') ), admin_url('admin.php') ); wp_safe_redirect($redirect_url); exit; } } else { wp_die(esc_html__('Security check 2 failed.', 'ai-eshop-optimizer')); } } private function aieo_create_csv_from_orders_table($orders_table_name, $file_path) { global $wpdb; aieo_debug_log('AIEO: Starting CSV creation from table: ' . $orders_table_name); try { if (!function_exists('WP_Filesystem')) { require_once(ABSPATH . 'wp-admin/includes/file.php'); } WP_Filesystem(); global $wp_filesystem; if (!$wp_filesystem) { aieo_debug_log('AIEO: WARNING - WP_Filesystem not initialized; falling back to direct file write.'); } $columns_result = $wpdb->get_results("SHOW COLUMNS FROM {$orders_table_name}", ARRAY_A); if (!$columns_result) { aieo_debug_log('AIEO: ERROR - Could not get table structure for: ' . $orders_table_name); return false; } $export_columns = array(); $uuid_columns = array(); $select_columns = array(); foreach ($columns_result as $column) { $col_name = $column['Field']; $col_type = $column['Type']; $export_columns[] = $col_name; if (stripos($col_type, 'binary(16)') !== false || stripos($col_name, 'UUID') !== false) { $uuid_columns[] = $col_name; $select_columns[] = "CONCAT(SUBSTR(HEX({$col_name}), 1, 8), '-', SUBSTR(HEX({$col_name}), 9, 4), '-', SUBSTR(HEX({$col_name}), 13, 4), '-', SUBSTR(HEX({$col_name}), 17, 4), '-', SUBSTR(HEX({$col_name}), 21)) AS {$col_name}"; } else { $select_columns[] = $col_name; } } aieo_debug_log('AIEO: Found ' . count($export_columns) . ' columns in table, ' . count($uuid_columns) . ' UUID columns'); $select_query = "SELECT " . implode(', ', $select_columns) . " FROM {$orders_table_name}"; aieo_debug_log('AIEO: Executing query to fetch data from table'); $file_path = $this->aieo_normalize_path($file_path); aieo_debug_log('AIEO: Target file path: ' . $file_path); if (!wp_mkdir_p(dirname($file_path))) { aieo_debug_log('AIEO: ERROR - Failed to create directory for CSV: ' . dirname($file_path)); return false; } $handle = @fopen($file_path, 'w'); if (!$handle) { aieo_debug_log('AIEO: ERROR - Unable to open file handle for CSV: ' . $file_path); return false; } fputcsv($handle, $export_columns); $batch_size = 1000; $offset = 0; $total_written = 0; while (true) { $rows = $wpdb->get_results( $select_query . $wpdb->prepare(" LIMIT %d OFFSET %d", $batch_size, $offset), ARRAY_A ); if ($wpdb->last_error) { aieo_debug_log('AIEO: ERROR - Database query failed: ' . $wpdb->last_error); fclose($handle); wp_delete_file($file_path); return false; } if (empty($rows)) { break; } foreach ($rows as $row) { $line = array(); foreach ($export_columns as $col_name) { $line[] = $row[$col_name] ?? ''; } fputcsv($handle, $line); } $total_written += count($rows); $offset += $batch_size; } fclose($handle); aieo_debug_log('AIEO: CSV file created successfully at: ' . $file_path . ' with ' . $total_written . ' rows'); return $file_path; } catch (Exception $e) { aieo_debug_log('AIEO: EXCEPTION in CSV creation: ' . $e->getMessage()); return false; } } public function aieo_delete_exported_file() { if (!current_user_can(AIEO_CAPABILITY)) { wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'ai-eshop-optimizer')); } if (!isset($_GET['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'delete_exported_file')) { wp_die(esc_html__('Security check failed.', 'ai-eshop-optimizer')); } $csv_info = get_option('aieo_last_export_csv', array()); $file_path = isset($csv_info['path']) ? $this->aieo_normalize_path($csv_info['path']) : ''; if ($file_path && file_exists($file_path)) { wp_delete_file($file_path); delete_option('aieo_last_export_csv'); update_option('aieo_file_deletion_message', __('Export file has been successfully deleted from your server.', 'ai-eshop-optimizer')); } else { update_option('aieo_file_deletion_message', 'Error: ' . __('Export file not found.', 'ai-eshop-optimizer')); } wp_safe_redirect(admin_url('admin.php?page=ai-eshop-optimizer')); exit; } public function aieo_handle_recommendations_upload() { if ( isset($_POST['eshop_optimizer_process_params_nonce_6']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['eshop_optimizer_process_params_nonce_6'])), 'eshop_optimizer_handle_upload_action') ) { global $wpdb; $monitor_performance = isset($_POST['aieo_monitor_performance']) && $_POST['aieo_monitor_performance'] === '1'; if (current_user_can(AIEO_CAPABILITY)) { update_option('aieo_monitor_performance', (isset($_POST['aieo_monitor_performance']) && $_POST['aieo_monitor_performance'] == '1') ? '1' : '0'); update_option('aieo_batch_size', (isset($_POST['aieo_batch_size']) && $_POST['aieo_batch_size'] == '1') ? '1' : '0'); update_option('aieo_batch_sleep', (isset($_POST['aieo_batch_sleep']) && $_POST['aieo_batch_sleep'] == '1') ? '1' : '0'); if (isset($_POST['aieo_monitor_performance'])) { update_option('aieo_monitor_performance', sanitize_text_field(wp_unslash($_POST['aieo_monitor_performance']))); } if (isset($_POST['aieo_batch_size'])) { update_option('aieo_batch_size', sanitize_text_field(wp_unslash($_POST['aieo_batch_size']))); } if (isset($_POST['aieo_batch_sleep'])) { update_option('aieo_batch_sleep', sanitize_text_field(wp_unslash($_POST['aieo_batch_sleep']))); } } $new_recommendations_table_name = $wpdb->prefix . "aieo_" . gmdate('Y_m') . "_new_recommendations"; $wpdb->query("DROP TABLE IF EXISTS `{$new_recommendations_table_name}`"); $wpdb->query("CREATE TABLE `{$new_recommendations_table_name}` ( original_product_id INT NOT NULL, related_upsells_list TEXT, related_crossells_list TEXT, aieo_neo4j_params TEXT, PRIMARY KEY (original_product_id) )"); if (isset($_FILES['csv_file'])) { $uploaded_file = $_FILES['csv_file']; if (!function_exists('wp_handle_upload')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } $uploaded_file['name'] = sanitize_file_name($uploaded_file['name']); $movefile = wp_handle_upload($uploaded_file, array('test_form' => false)); if ($movefile && !isset($movefile['error'])) { $csv_data = array_map('str_getcsv', file($movefile['file'])); array_shift($csv_data); if (!empty($csv_data)) { foreach ($csv_data as $row) { $exists = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM `{$new_recommendations_table_name}` WHERE `original_product_id` = %d", intval($row[0]) )); if ($exists == 0) { $wpdb->insert( $new_recommendations_table_name, array( 'original_product_id' => intval($row[0]), 'related_upsells_list' => sanitize_text_field($row[1]), 'related_crossells_list' => sanitize_text_field($row[2]), 'aieo_neo4j_params' => sanitize_text_field($row[3]) ), array('%d', '%s', '%s', '%s') ); } } } $total_products_impacted = $this->assign_upsells_cross_sells_from_db($new_recommendations_table_name); set_transient('aieo_products_updated_count', $total_products_impacted, 60); $redirect_url = admin_url('admin.php?page=ai-eshop-optimizer'); } else { $redirect_url = add_query_arg('upsell_cross_sell_error', 1, admin_url('admin.php?page=ai-eshop-optimizer')); } } else { wp_die(esc_html__('Security check 3 failed.', 'ai-eshop-optimizer')); } wp_safe_redirect(esc_url_raw($redirect_url)); exit; } else { } } public function assign_upsells_cross_sells_from_db($new_recommendations_table_name) { global $wpdb; $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}aieo_performance_tuning"); $wpdb->query("CREATE TABLE {$wpdb->prefix}aieo_performance_tuning ( id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, batch_size INT(11) UNSIGNED NOT NULL, processing_time FLOAT NOT NULL, total_processing_time FLOAT NOT NULL, date_recorded TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) )"); $offset = 0; $batch_size = get_option('aieo_batch_size', 300); $batch_sleep = get_option('aieo_batch_sleep', 0); $products_impacted_count = 0; $batch_times = []; while (true) { $products_data = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$new_recommendations_table_name}` WHERE (related_upsells_list <> '' OR related_crossells_list <> '') LIMIT %d OFFSET %d", $batch_size, $offset ), ARRAY_A ); if (empty($products_data)) { break; } $start_time = microtime(true); foreach ($products_data as $data) { $original_product_id = intval($data['original_product_id']); $related_upsells_list = array_filter(explode(',', $data['related_upsells_list'])); $related_crossells_list = array_filter(explode(',', $data['related_crossells_list'])); $aieo_neo4j_params = $data['aieo_neo4j_params']; $this->assign_upsells_cross_sells($original_product_id, $related_upsells_list, $related_crossells_list, $aieo_neo4j_params); } $end_time = microtime(true); $time_taken = $end_time - $start_time; $batch_times[] = $time_taken; if (count($batch_times) == 4) { $avg_first_two = ($batch_times[0] + $batch_times[1]) / 2; $avg_last_two = ($batch_times[2] + $batch_times[3]) / 2; if ($avg_last_two > 2 * $avg_first_two) { $batch_size = intdiv($batch_size, 2); } $batch_times = []; } $products_impacted_count += count($products_data); $offset += $batch_size; sleep($batch_sleep); } return $products_impacted_count; } public function assign_upsells_cross_sells($original_product_id, $related_upsells_list, $related_crossells_list, $aieo_neo4j_params) { global $wpdb; $parsed_url = wp_parse_url(home_url()); $domain = str_replace(['.', '-'], '', $parsed_url['host']); $current_user = wp_get_current_user(); $tablesuffix = esc_html($current_user->user_login) . '_' . esc_html($current_user->ID) . '_' . esc_html($domain); $start_time = microtime(true); $original_product = wc_get_product($original_product_id); if (!$original_product) { return 0; } $upsell_ids = array_slice($related_upsells_list, 0, 10); $cross_sell_ids = array_slice($related_crossells_list, 0, 10); if (!empty($upsell_ids)) { $original_product->set_upsell_ids($upsell_ids); } if (!empty($cross_sell_ids)) { $original_product->set_cross_sell_ids($cross_sell_ids); } $original_product->save(); if ($aieo_neo4j_params === '0' || empty($aieo_neo4j_params)) { delete_post_meta($original_product_id, 'aieo_neo4j_params'); } else { update_post_meta($original_product_id, 'aieo_neo4j_params', $aieo_neo4j_params); } $end_time = microtime(true); $time_taken = $end_time - $start_time; $current_total = $wpdb->get_var("SELECT SUM(processing_time) FROM {$wpdb->prefix}aieo_performance_tuning"); $new_total = $current_total + $time_taken; $monitor_performance = get_option('aieo_monitor_performance'); if ($monitor_performance) { $wpdb->insert($wpdb->prefix . 'aieo_performance_tuning', [ 'batch_size' => count($related_upsells_list) + count($related_crossells_list), 'processing_time' => $time_taken, 'total_processing_time' => $new_total ]); } return 1; } public function aieo_handle_eshop_optimizer_account() { if ( isset($_POST['eshop_optimizer_account_nonce_7']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['eshop_optimizer_account_nonce_7'])), 'eshop_optimizer_account_action') ) { $username = isset($_POST['aieo_account_username']) ? sanitize_text_field(wp_unslash($_POST['aieo_account_username'])) : ''; $password = isset($_POST['aieo_account_password']) ? sanitize_text_field(wp_unslash($_POST['aieo_account_password'])) : ''; $erp_store_products_table = isset($_POST['aieo_erp_store_products_table']) ? sanitize_text_field(wp_unslash($_POST['aieo_erp_store_products_table'])) : ''; $erp_store_orders_table = isset($_POST['aieo_erp_store_orders_table']) ? sanitize_text_field(wp_unslash($_POST['aieo_erp_store_orders_table'])) : ''; $erp_store_customers_table = isset($_POST['aieo_erp_store_customers_table']) ? sanitize_text_field(wp_unslash($_POST['aieo_erp_store_customers_table'])) : ''; $analytics_eponymous_id_field = isset($_POST['aieo_analytics_eponymous_id_field']) ? sanitize_text_field(wp_unslash($_POST['aieo_analytics_eponymous_id_field'])) : ''; $erp_anonymous_invoice_users = isset($_POST['aieo_erp_store_anonymous_invoice_users']) ? sanitize_text_field(wp_unslash($_POST['aieo_erp_store_anonymous_invoice_users'])) : ''; $erp_series_to_ignore = isset($_POST['aieo_erp_series_to_ignore']) ? sanitize_text_field(wp_unslash($_POST['aieo_erp_series_to_ignore'])) : ''; $erp_products_to_ignore = isset($_POST['aieo_erp_products_to_ignore']) ? sanitize_text_field(wp_unslash($_POST['aieo_erp_products_to_ignore'])) : ''; $erp_product_price_to_ignore = isset($_POST['aieo_erp_product_price_to_ignore']) ? sanitize_text_field(wp_unslash($_POST['aieo_erp_product_price_to_ignore'])) : ''; $google_user_journey_table = isset($_POST['aieo_google_user_journey_table']) ? sanitize_text_field(wp_unslash($_POST['aieo_google_user_journey_table'])) : ''; update_option('aieo_erp_store_products_table', $erp_store_products_table); update_option('aieo_erp_store_orders_table', $erp_store_orders_table); update_option('aieo_erp_store_customers_table', $erp_store_customers_table); update_option('aieo_analytics_eponymous_id_field', $analytics_eponymous_id_field); update_option('aieo_erp_store_anonymous_invoice_users', $erp_anonymous_invoice_users); update_option('aieo_erp_series_to_ignore', $erp_series_to_ignore); update_option('aieo_erp_products_to_ignore', $erp_products_to_ignore); update_option('aieo_erp_product_price_to_ignore', $erp_product_price_to_ignore); update_option('aieo_google_user_journey_table', $google_user_journey_table); update_option('aieo_activate_account', isset($_POST['aieo_activate_account']) ? 1 : 0); update_option('aieo_erp_consolidation', isset($_POST['aieo_erp_consolidation']) ? 1 : 0); update_option('aieo_erp_anonymous_consolidation', isset($_POST['aieo_erp_anonymous_consolidation']) ? 1 : 0); update_option('aieo_erp_stats_per_channel', isset($_POST['aieo_erp_stats_per_channel']) ? 1 : 0); $remember_auth = isset($_POST['aieo_remember_auth']) ? 1 : 0; update_option('aieo_remember_auth', $remember_auth); $table = sanitize_text_field(wp_unslash($_POST['aieo_account_table'] ?? '')); $result = $this->remote_user_authenticate_and_check_membership($username, $password); if ($result === 'You have been successfully authenticated as a Pro User' || $result === 'You have been successfully authenticated as a Free User') { update_option('aieo_account_username', $username); $method = 'aes-256-ctr'; $key = AUTH_KEY; $iv = substr(AUTH_SALT, 0, 16); $encrypted_password = openssl_encrypt($password, $method, $key, 0, $iv); update_option('aieo_account_password', $encrypted_password); } if ($result !== 'Hello Pro' && $result !== 'Thank you for registering') { $rooster_connected = ( get_option('aieo_rooster_enabled') && get_option('aieo_rooster_status') === 'connected' ); if ($rooster_connected) { update_option('aieo_account_message', sprintf( __('JWT auth could not complete (%s). Your Pro access remains active via the Rooster Content Connector — no further action needed.', 'ai-eshop-optimizer'), $result )); } else { update_option('aieo_account_message', $result . ' — ' . __('Alternatively, link this site via the "Rooster Content Connector" section below to activate Pro features.', 'ai-eshop-optimizer')); } wp_safe_redirect(wp_get_referer()); exit; } $method = 'aes-256-ctr'; $key = AUTH_KEY; $iv = substr(AUTH_SALT, 0, 16); $encrypted_password = openssl_encrypt($password, $method, $key, 0, $iv); update_option('aieo_account_username', $username); update_option('aieo_account_password', $encrypted_password); update_option('aieo_activate_account', isset($_POST['aieo_activate_account']) ? 1 : 0); if ($result === 'Hello Pro') { update_option('aieo_account_message', __('Successfully connected as a Pro user and settings saved.', 'ai-eshop-optimizer')); if ($remember_auth) { $expiry_time = time() + (30 * DAY_IN_SECONDS); update_option('aieo_auth_expiry', $expiry_time); } else { delete_option('aieo_auth_expiry'); } } elseif ($result === 'Thank you for registering') { update_option('aieo_account_message', __('Successfully connected as a registered user and settings saved.', 'ai-eshop-optimizer')); if ($remember_auth) { $expiry_time = time() + (30 * DAY_IN_SECONDS); update_option('aieo_auth_expiry', $expiry_time); } else { delete_option('aieo_auth_expiry'); } } wp_safe_redirect(wp_get_referer()); exit; } else { wp_die(esc_html__('Security check 4 failed.', 'ai-eshop-optimizer')); } } public function aieo_handle_logout_account() { if (!isset($_GET['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'aieo_logout_account')) { wp_die(esc_html__('Security check failed.', 'ai-eshop-optimizer')); } delete_option('aieo_auth_expiry'); delete_option('aieo_remember_auth'); delete_option('aieo_jwt_token'); delete_option('aieo_user_membership'); delete_option('aieo_subscription_expiration'); delete_option('aieo_activate_account'); delete_option('aieo_account_username'); delete_option('aieo_account_password'); update_option('aieo_account_message', __('You have been logged out successfully. All authentication data has been cleared.', 'ai-eshop-optimizer')); wp_safe_redirect(admin_url('admin.php?page=ai-eshop-optimizer')); exit; } public function sanitize_url_for_comparison($url) { $url = preg_replace('#^https?://#', '', $url); $url = str_replace('www.', '', $url); $url = rtrim($url, '/'); return $url; } public function remote_user_authenticate_and_check_membership($username, $password) { $auth_url = 'https://eshop-optimizer.com/wp-json/jwt-auth/v1/token/'; $response = wp_remote_post($auth_url, [ 'body' => [ 'username' => $username, 'password' => $password, ], ]); $body = wp_remote_retrieve_body($response); update_option('aieo_jwt_response', $body, false); if (is_wp_error($response)) { return 'Authentication request error: ' . $response->get_error_message(); } $token_info = json_decode($body, true); if (empty($token_info['token'])) { if (isset($token_info['message'])) { $error_message = wp_strip_all_tags(html_entity_decode($token_info['message'], ENT_QUOTES, 'UTF-8')); return 'Authentication failed: ' . $error_message; } elseif (isset($token_info['code'])) { $code = $token_info['code']; if (strpos($code, 'invalid_username') !== false) { return 'Authentication failed: The username you entered is not registered. Please check your username or try your email address.'; } elseif (strpos($code, 'incorrect_password') !== false) { return 'Authentication failed: Incorrect password. Please try again.'; } else { return 'Authentication failed: ' . $code; } } return 'Authentication failed: Invalid credentials. Please check your username and password.'; } update_option('aieo_jwt_token', $token_info['token']); $user_info_url = 'https://eshop-optimizer.com/wp-json/wp/v2/users/me/?JWT=' . $token_info['token']; $response = wp_remote_get($user_info_url, [ 'headers' => [ 'Authorization' => 'Bearer ' . $token_info['token'], ], ]); if (is_wp_error($response)) { return 'User info request error: ' . $response->get_error_message(); } $user_info_body = wp_remote_retrieve_body($response); $user_info = json_decode($user_info_body, true); update_option('aieo_jwt_debugging', $user_info_body, false); update_option('aieo_jwt_user_details', $user_info_body, false); if (isset($user_info['aieo_subscription_expiration_date'])) { update_option('aieo_subscription_expiration', $user_info['aieo_subscription_expiration_date']); } else { } $current_site_url = $this->sanitize_url_for_comparison(get_site_url()); $sanitized_remote_url = ''; $matching_website_db_prefix = null; $matching_report_db_suffix = null; if (isset($user_info['site_database_prefixes']) && is_array($user_info['site_database_prefixes'])) { foreach ($user_info['site_database_prefixes'] as $website) { $sanitized_remote_url = $this->sanitize_url_for_comparison($website['website_url']); if ($sanitized_remote_url === $current_site_url) { $matching_website_db_prefix = $website['website_db_prefix']; if (isset($website['report_db_suffix'])) { $matching_report_db_suffix = $website['report_db_suffix']; } break; } } } if ($matching_website_db_prefix) { update_option('aieo_account_table', $matching_website_db_prefix); if ($matching_report_db_suffix) { update_option('aieo_account_report_table', $matching_report_db_suffix); } } else { $error_message = "There is no registered website in the account that matches this URL. Current site URL (sanitized): {$current_site_url} | Remote site URL last checked (sanitized): {$sanitized_remote_url}"; add_action('admin_notices', function () use ($error_message) { echo '

' . esc_html($error_message) . '

'; }); } $messages = []; if (isset($user_info['site_database_prefixes']) && !empty($user_info['site_database_prefixes'])) { update_option('aieo_first_website_db_prefix', $user_info['site_database_prefixes'][0]['website_db_prefix']); } if (!isset($user_info['roles']) || !is_array($user_info['roles'])) { $messages[] = 'Error fetching user roles or roles not set. Response: ' . $user_info_body; } if (isset($user_info['roles']) && is_array($user_info['roles'])) { if (in_array('pro_users', $user_info['roles'])) { if (current_user_can(AIEO_CAPABILITY)) { update_option('aieo_user_membership', 'Pro'); $expiry_date = get_option('aieo_subscription_expiration', '1/1/2025'); update_option('aieo_user_expiry', $expiry_date); } $messages[] = 'You have been successfully authenticated as a Pro User'; } elseif (in_array('free_users', $user_info['roles'])) { if (current_user_can(AIEO_CAPABILITY)) { update_option('aieo_user_membership', 'Free'); $expiry_date = get_option('aieo_subscription_expiration', '1/1/2025'); update_option('aieo_user_expiry', $expiry_date); } $messages[] = 'You have been successfully authenticated as a Free User'; } else { $user_roles = implode(', ', $user_info['roles']); $username = isset($user_info['name']) ? $user_info['name'] : 'User'; $messages[] = 'Authentication successful, but your account does not have Pro or Free user status. Current roles: ' . $user_roles . '. Please subscribe at https://rooster.systems/get-rooster-one/ to access Pro features.'; } } return implode(' | ', $messages); } public function authenticate_with_stored_token() { $token = get_option('aieo_jwt_token'); if (!$token) { return 'No token found.'; } $user_info_url = 'https://eshop-optimizer.com/wp-json/wp/v2/users/me/?nowprocket'; $response = wp_remote_get($user_info_url, [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, ], ]); if (is_wp_error($response)) { return 'User info request error: ' . $response->get_error_message(); } $user_info = json_decode(wp_remote_retrieve_body($response), true); if (!isset($user_info['roles']) || !is_array($user_info['roles'])) { return 'Error fetching user roles or roles not set. Response: ' . wp_remote_retrieve_body($response); } if (isset($user_info['roles']) && is_array($user_info['roles'])) { if (in_array('pro_users', $user_info['roles'])) { if (current_user_can(AIEO_CAPABILITY)) { update_option('aieo_user_membership', 'Pro'); update_option('aieo_user_membership', '1/1/2025'); } return 'You have been successfully authenticated as a Pro User'; } } if (in_array('free_users', $user_info['roles'])) { if (current_user_can(AIEO_CAPABILITY)) { update_option('aieo_user_membership', 'Free'); } return 'You have been successfully authenticated as a Free User'; } return 'Unknown role. User info: ' . wp_remote_retrieve_body($response); } function aieo_handle_eshop_optimizer_transfer_to_remote() { if ( isset($_POST['eshop_optimizer_transfer_to_remote_nonce_8']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['eshop_optimizer_transfer_to_remote_nonce_8'])), 'eshop_optimizer_transfer_to_remote_action') ) { set_transient('aieo_upload_notice', 'Soon with you', 60 * 5); wp_safe_redirect(esc_url(admin_url('admin.php?page=ai-eshop-optimizer'))); exit; } else { wp_die(esc_html__('Security check 5 failed.', 'ai-eshop-optimizer')); } } public function aieo_init_export() { check_ajax_referer('aieo_export_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions', 'ai-eshop-optimizer'))); return; } $settings = array( 'create_attributes' => (isset($_POST['create_attributes']) && $_POST['create_attributes'] == '1') ? '1' : '0', 'create_needs' => (isset($_POST['create_needs']) && $_POST['create_needs'] == '1') ? '1' : '0', 'create_catalogue' => (isset($_POST['create_catalogue']) && $_POST['create_catalogue'] == '1') ? '1' : '0', 'process_eshop_orders' => (isset($_POST['process_eshop_orders']) && $_POST['process_eshop_orders'] == '1') ? '1' : '0', 'process_instore_orders' => (isset($_POST['process_instore_orders']) && $_POST['process_instore_orders'] == '1') ? '1' : '0', 'merge_orders' => (isset($_POST['merge_orders']) && $_POST['merge_orders'] == '1') ? '1' : '0', 'include_unsold' => (isset($_POST['include_unsold']) && $_POST['include_unsold'] == '1') ? '1' : '0', 'aggregate_product_stats' => (isset($_POST['aggregate_product_stats']) && $_POST['aggregate_product_stats'] == '1') ? '1' : '0', 'aggregate_category_brand_stats' => (isset($_POST['aggregate_category_brand_stats']) && $_POST['aggregate_category_brand_stats'] == '1') ? '1' : '0', 'category_sales_period' => (isset($_POST['category_sales_period']) && in_array(wp_unslash($_POST['category_sales_period']), array('all_time', 'last_30_days', 'last_3_months', 'last_6_months', 'last_12_months'), true)) ? sanitize_text_field(wp_unslash($_POST['category_sales_period'])) : get_option('aieo_category_sales_period', 'all_time'), 'aggregate_customer_std_stats' => (isset($_POST['aggregate_customer_std_stats']) && $_POST['aggregate_customer_std_stats'] == '1') ? '1' : '0', 'aggregate_customer_adv_stats' => (isset($_POST['aggregate_customer_adv_stats']) && $_POST['aggregate_customer_adv_stats'] == '1') ? '1' : '0', 'create_uuids' => (isset($_POST['create_uuids']) && $_POST['create_uuids'] == '1') ? '1' : '0', 'create_need_suggestions' => (isset($_POST['create_need_suggestions']) && $_POST['create_need_suggestions'] == '1') ? '1' : '0', 'create_export' => (isset($_POST['create_export']) && $_POST['create_export'] == '1') ? '1' : '0', 'export_product_titles' => (isset($_POST['export_product_titles']) && $_POST['export_product_titles'] == '1') ? '1' : '0', 'export_prices' => (isset($_POST['export_prices']) && $_POST['export_prices'] == '1') ? '1' : '0', 'export_customer_ids' => (isset($_POST['export_customer_ids']) && $_POST['export_customer_ids'] == '1') ? '1' : '0', 'seasonal_option' => isset($_POST['seasonal_option']) ? sanitize_text_field(wp_unslash($_POST['seasonal_option'])) : '', 'vendor_taxonomy' => isset($_POST['vendor_taxonomy']) ? sanitize_text_field(wp_unslash($_POST['vendor_taxonomy'])) : '', 'brand_taxonomy' => isset($_POST['brand_taxonomy']) ? sanitize_text_field(wp_unslash($_POST['brand_taxonomy'])) : '', 'size_taxonomies' => isset($_POST['size_taxonomies']) ? sanitize_text_field(wp_unslash($_POST['size_taxonomies'])) : '', 'color_taxonomies' => isset($_POST['color_taxonomies']) ? sanitize_text_field(wp_unslash($_POST['color_taxonomies'])) : '', ); delete_transient('aieo_export_lock'); delete_transient('aieo_product_stats_lock'); delete_transient('aieo_customer_std_stats_lock'); delete_transient('aieo_customer_adv_stats_lock'); delete_transient('aieo_need_suggestions_progress'); global $wpdb; $temp_orders_table_name = get_option('aieo_site_orders_table', $wpdb->prefix . 'aieo_temp_orders'); $temp_products_table_name = get_option('aieo_site_products_table', $wpdb->prefix . 'aieo_temp_product_catalogue'); $reset_orders_table = ($settings['process_eshop_orders'] === '1' || $settings['process_instore_orders'] === '1' || $settings['merge_orders'] === '1'); if ($reset_orders_table) { $wpdb->query("DROP TABLE IF EXISTS {$temp_orders_table_name}"); aieo_create_temp_orders_table($temp_orders_table_name); $this->aieo_log('Temp orders table reset for fresh order processing.', array( 'table' => $temp_orders_table_name, )); } else { $this->aieo_ensure_temp_orders_schema($temp_orders_table_name); } $reset_products_table = ($settings['create_catalogue'] === '1'); if ($reset_products_table) { $wpdb->query("DROP TABLE IF EXISTS {$temp_products_table_name}"); aieo_create_temp_products_table($temp_products_table_name); $this->aieo_log('Temp products table reset for catalogue build.', array( 'table' => $temp_products_table_name, )); } elseif (!$this->aieo_table_exists($temp_products_table_name)) { aieo_create_temp_products_table($temp_products_table_name); } $export_id = uniqid('export_', true); set_transient('aieo_export_settings', $settings, 7200); set_transient('aieo_export_id', $export_id, 7200); delete_transient('aieo_export_progress'); update_option('aieo_create_attributes', $settings['create_attributes']); update_option('aieo_create_catalogue', $settings['create_catalogue']); update_option('aieo_create_orders', $settings['process_eshop_orders']); update_option('aieo_erp_consolidation', $settings['process_instore_orders']); update_option('aieo_erp_merge_orders', $settings['merge_orders']); update_option('aieo_include_unsold', $settings['include_unsold']); update_option('aieo_include_aggregate_stats', $settings['aggregate_product_stats']); update_option('aieo_include_aggregate_category_brand_stats', $settings['aggregate_category_brand_stats']); update_option('aieo_category_sales_period', $settings['category_sales_period']); update_option('aieo_include_aggregate_cust_std_stats', $settings['aggregate_customer_std_stats']); update_option('aieo_include_aggregate_cust_adv_stats', $settings['aggregate_customer_adv_stats']); update_option('aieo_create_uuids', $settings['create_uuids']); update_option('aieo_create_need_suggestions', $settings['create_need_suggestions']); update_option('aieo_create_export', $settings['create_export']); update_option('aieo_core_attribute_vendor', $settings['vendor_taxonomy']); update_option('aieo_core_attribute_brands', $settings['brand_taxonomy']); update_option('aieo_core_attribute_size', $settings['size_taxonomies']); update_option('aieo_core_attribute_color', $settings['color_taxonomies']); wp_send_json_success(array( 'message' => __('Export initialized', 'ai-eshop-optimizer'), 'export_id' => $export_id )); } public function aieo_get_download_section() { check_ajax_referer('aieo_export_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions', 'ai-eshop-optimizer'))); return; } $csv_info = get_option('aieo_last_export_csv', array()); $file_path = isset($csv_info['path']) ? $csv_info['path'] : ''; $download_url = isset($csv_info['url']) ? $csv_info['url'] : ''; $filename = isset($csv_info['filename']) ? $csv_info['filename'] : basename($file_path); aieo_debug_log('AIEO: Download section - CSV info: ' . wp_json_encode($csv_info)); aieo_debug_log('AIEO: Download section - File path: ' . $file_path); aieo_debug_log('AIEO: Download section - Download URL: ' . $download_url); aieo_debug_log('AIEO: Download section - File exists: ' . (file_exists($file_path) ? 'yes' : 'no')); if (empty($file_path) || empty($download_url) || !file_exists($file_path)) { $error_details = array(); if (empty($file_path)) $error_details[] = 'file path is empty'; if (empty($download_url)) $error_details[] = 'download URL is empty'; if (!empty($file_path) && !file_exists($file_path)) $error_details[] = 'file does not exist at: ' . $file_path; aieo_debug_log('AIEO: Download section - Export file missing or metadata unavailable. Details: ' . implode(', ', $error_details)); wp_send_json_error(array( 'message' => __('Export file not found yet. Details:', 'ai-eshop-optimizer') . ' ' . implode(', ', $error_details) )); return; } aieo_debug_log('AIEO: Download section - File found at: ' . $file_path); $downloadfilemessage = sprintf( __('The orders file has been created!!!
Download:
%2$s', 'ai-eshop-optimizer'), esc_url($download_url), esc_html($filename) ); $aieo_dl_allowed = array( 'a' => array('href' => array(), 'title' => array(), 'class' => array(), 'style' => array()), 'br' => array(), ); if ((int) get_option('aieo_activate_remote_connection') === 1) { $aieo_dl_pro = '

' . esc_html__('As a pro user you can upload the file directly from this plugin via the upload data in your server tab', 'ai-eshop-optimizer') . '

'; } else { $aieo_dl_pro = '

' . esc_html__('Why do we save your file on your server?', 'ai-eshop-optimizer') . '
' . esc_html__('Our pro users on annual contracts are able to transfer their data directly into our server for optimization calculations and fully automate the process', 'ai-eshop-optimizer') . '

'; } $aieo_dl_action = '' . esc_html__('Important! Click to remove the file from your server', 'ai-eshop-optimizer') . ''; $aieo_dl_title = __('Download orders file & clean up!', 'ai-eshop-optimizer'); if (class_exists('\Rooster\Admin\Ui')) { $html = \Rooster\Admin\Ui::notice(wp_kses($downloadfilemessage, $aieo_dl_allowed), 'success', array( 'inline' => true, 'id' => 'aieo-card-orders-file-download', 'title' => $aieo_dl_title, 'detail' => $aieo_dl_pro, 'actions' => $aieo_dl_action, )); } else { $html = '

' . esc_html($aieo_dl_title) . '

' . '

' . wp_kses($downloadfilemessage, $aieo_dl_allowed) . '

' . $aieo_dl_pro . '

' . $aieo_dl_action . '

'; } wp_send_json_success(array( 'html' => $html, 'message' => __('Download section loaded successfully', 'ai-eshop-optimizer'), 'filename' => $filename, 'url' => $download_url )); } public function aieo_check_vitals_data() { check_ajax_referer('aieo_export_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions', 'ai-eshop-optimizer'))); return; } global $wpdb; $vitals_table = $wpdb->prefix . 'aieo_core_product_vitals'; $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $vitals_table)) === $vitals_table; $has_data = false; if ($table_exists) { $count = $wpdb->get_var("SELECT COUNT(*) FROM {$vitals_table}"); $has_data = ($count > 0); } wp_send_json_success(array( 'has_data' => $has_data, 'count' => isset($count) ? (int)$count : 0 )); } public function aieo_get_statistics_section() { try { check_ajax_referer('aieo_export_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions', 'ai-eshop-optimizer'))); return; } aieo_debug_log('AIEO: Statistics section - Starting request'); if (!function_exists('aieo_get_temp_orders_table_for_insights')) { $helper_file = plugin_dir_path(__FILE__) . 'ai-eshop-optimizer-functions.php'; if (file_exists($helper_file)) { require_once $helper_file; aieo_debug_log('AIEO: Statistics section - Loaded helper functions from separate file'); } else { aieo_debug_log('AIEO: Statistics section - Helper file not found, functions should already be loaded'); } } global $wpdb; if (!function_exists('aieo_get_temp_orders_table_for_insights')) { aieo_debug_log('AIEO: Statistics section - Helper functions not available'); wp_send_json_error(array( 'message' => __('Helper functions not loaded. Please refresh the page and try again.', 'ai-eshop-optimizer') )); return; } $aieo_orders_table = aieo_get_temp_orders_table_for_insights(); aieo_debug_log('AIEO: Statistics section - Orders table: ' . ($aieo_orders_table ? $aieo_orders_table : 'NOT FOUND')); if (!$aieo_orders_table) { wp_send_json_error(array( 'message' => __('Statistics not ready yet. Please wait for the advanced statistics processing to complete.', 'ai-eshop-optimizer') )); return; } $has_product_stats = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$aieo_orders_table} WHERE TotalItemSales IS NOT NULL AND TotalItemSales > 0 LIMIT 1"); $has_adv_stats = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$aieo_orders_table} WHERE AvgOrderSize IS NOT NULL AND AvgOrderSize > 0 LIMIT 1"); if (!$has_product_stats || !$has_adv_stats) { wp_send_json_error(array( 'message' => __('Statistics not ready yet. Please wait for the advanced statistics processing to complete.', 'ai-eshop-optimizer') )); return; } $aieo_insight_data = array( 'available' => false, 'single_products' => array(), 'favourite_products' => array(), 'brands' => array(), ); $aieo_insight_data['single_products'] = aieo_fetch_single_product_focus($aieo_orders_table); $aieo_insight_data['favourite_products'] = aieo_fetch_favourite_products($aieo_orders_table); $aieo_insight_data['brands'] = aieo_fetch_brand_focus($aieo_orders_table); if (empty($aieo_insight_data['single_products']) && empty($aieo_insight_data['favourite_products']) && empty($aieo_insight_data['brands'])) { wp_send_json_error(array( 'message' => __('No statistics data available yet.', 'ai-eshop-optimizer') )); return; } $aieo_insight_data['available'] = true; $aieo_st_why = static function ($text) { return class_exists('\Rooster\Admin\Guidance') ? \Rooster\Admin\Guidance::text((string) $text) : '

' . esc_html($text) . '

'; }; $aieo_st_section = static function ($title, $id) { return function_exists('rooster_admin_card_section_html') ? rooster_admin_card_section_html($title, array('id' => $id)) : '

' . esc_html($title) . '

'; }; list($aieo_st_open, $aieo_st_close) = AIEO_DMM_UI::card(array( 'anchor' => 'indicative-statistics', 'icon' => 'chart-column', 'title' => __('Indicative Statistics', 'ai-eshop-optimizer'), )); ob_start(); echo $aieo_st_open; echo '

' . esc_html__("Your customers' favourite products", 'ai-eshop-optimizer') . '

'; echo $aieo_st_why(__('Frequently the first or last items added to multi-product carts—perfect candidates for prominent placement.', 'ai-eshop-optimizer')); ?> $html, 'message' => __('Statistics section loaded successfully', 'ai-eshop-optimizer') )); } catch (Exception $e) { aieo_debug_log('AIEO: Statistics section - Exception: ' . $e->getMessage()); aieo_debug_log('AIEO: Statistics section - Stack trace: ' . $e->getTraceAsString()); wp_send_json_error(array( 'message' => __('Error loading statistics:', 'ai-eshop-optimizer') . ' ' . $e->getMessage() )); } } public function aieo_save_settings() { check_ajax_referer('aieo_export_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions', 'ai-eshop-optimizer'))); return; } $settings = array( 'create_attributes' => (isset($_POST['create_attributes']) && $_POST['create_attributes'] == '1') ? '1' : '0', 'create_needs' => (isset($_POST['create_needs']) && $_POST['create_needs'] == '1') ? '1' : '0', 'create_need_suggestions' => (isset($_POST['create_need_suggestions']) && $_POST['create_need_suggestions'] == '1') ? '1' : '0', 'create_catalogue' => (isset($_POST['create_catalogue']) && $_POST['create_catalogue'] == '1') ? '1' : '0', 'process_eshop_orders' => (isset($_POST['process_eshop_orders']) && $_POST['process_eshop_orders'] == '1') ? '1' : '0', 'process_instore_orders' => (isset($_POST['process_instore_orders']) && $_POST['process_instore_orders'] == '1') ? '1' : '0', 'merge_orders' => (isset($_POST['merge_orders']) && $_POST['merge_orders'] == '1') ? '1' : '0', 'include_unsold' => (isset($_POST['include_unsold']) && $_POST['include_unsold'] == '1') ? '1' : '0', 'aggregate_product_stats' => (isset($_POST['aggregate_product_stats']) && $_POST['aggregate_product_stats'] == '1') ? '1' : '0', 'aggregate_category_brand_stats' => (isset($_POST['aggregate_category_brand_stats']) && $_POST['aggregate_category_brand_stats'] == '1') ? '1' : '0', 'category_sales_period' => (isset($_POST['category_sales_period']) && in_array(wp_unslash($_POST['category_sales_period']), array('all_time', 'last_30_days', 'last_3_months', 'last_6_months', 'last_12_months'), true)) ? sanitize_text_field(wp_unslash($_POST['category_sales_period'])) : get_option('aieo_category_sales_period', 'all_time'), 'aggregate_customer_std_stats' => (isset($_POST['aggregate_customer_std_stats']) && $_POST['aggregate_customer_std_stats'] == '1') ? '1' : '0', 'aggregate_customer_adv_stats' => (isset($_POST['aggregate_customer_adv_stats']) && $_POST['aggregate_customer_adv_stats'] == '1') ? '1' : '0', 'create_uuids' => (isset($_POST['create_uuids']) && $_POST['create_uuids'] == '1') ? '1' : '0', 'create_export' => (isset($_POST['create_export']) && $_POST['create_export'] == '1') ? '1' : '0', 'include_text' => (isset($_POST['include_text']) && $_POST['include_text'] == '1') ? '1' : '0', 'include_attributes' => (isset($_POST['include_attributes']) && $_POST['include_attributes'] == '1') ? '1' : '0', 'include_full_text' => (isset($_POST['include_full_text']) && $_POST['include_full_text'] == '1') ? '1' : '0', 'include_price' => (isset($_POST['include_price']) && $_POST['include_price'] == '1') ? '1' : '0', 'variations_choice' => (isset($_POST['variations_choice']) && $_POST['variations_choice'] == '1') ? '1' : '0', 'include_eponymous' => (isset($_POST['include_eponymous']) && $_POST['include_eponymous'] == '1') ? '1' : '0', 'include_reciprocity' => (isset($_POST['include_reciprocity']) && $_POST['include_reciprocity'] == '1') ? '1' : '0', 'include_seasonality' => (isset($_POST['include_seasonality']) && $_POST['include_seasonality'] == '1') ? '1' : '0', 'include_restricted_seasonality' => (isset($_POST['include_restricted_seasonality']) && $_POST['include_restricted_seasonality'] == '1') ? '1' : '0', 'instore_anonymous' => (isset($_POST['instore_anonymous']) && $_POST['instore_anonymous'] == '1') ? '1' : '0', 'vendor_taxonomy' => isset($_POST['vendor_taxonomy']) ? sanitize_text_field(wp_unslash($_POST['vendor_taxonomy'])) : '', 'brand_taxonomy' => isset($_POST['brand_taxonomy']) ? sanitize_text_field(wp_unslash($_POST['brand_taxonomy'])) : '', 'size_taxonomies' => isset($_POST['size_taxonomies']) ? sanitize_text_field(wp_unslash($_POST['size_taxonomies'])) : '', 'color_taxonomies' => isset($_POST['color_taxonomies']) ? sanitize_text_field(wp_unslash($_POST['color_taxonomies'])) : '', 'need_taxonomy' => isset($_POST['need_taxonomy']) ? sanitize_text_field(wp_unslash($_POST['need_taxonomy'])) : '', 'season_days_past' => isset($_POST['season_days_past']) ? intval($_POST['season_days_past']) : '', 'season_days_after' => isset($_POST['season_days_after']) ? intval($_POST['season_days_after']) : '' ); update_option('aieo_create_attributes', $settings['create_attributes']); update_option('aieo_create_needs', $settings['create_needs']); update_option('aieo_create_need_suggestions', $settings['create_need_suggestions']); update_option('aieo_create_catalogue', $settings['create_catalogue']); update_option('aieo_create_orders', $settings['process_eshop_orders']); update_option('aieo_erp_consolidation', $settings['process_instore_orders']); update_option('aieo_erp_merge_orders', $settings['merge_orders']); update_option('aieo_include_unsold', $settings['include_unsold']); update_option('aieo_include_aggregate_stats', $settings['aggregate_product_stats']); update_option('aieo_include_aggregate_category_brand_stats', $settings['aggregate_category_brand_stats']); update_option('aieo_category_sales_period', $settings['category_sales_period']); update_option('aieo_include_aggregate_cust_std_stats', $settings['aggregate_customer_std_stats']); update_option('aieo_include_aggregate_cust_adv_stats', $settings['aggregate_customer_adv_stats']); update_option('aieo_create_uuids', $settings['create_uuids']); update_option('aieo_create_export', $settings['create_export']); update_option('aieo_include_text', $settings['include_text']); update_option('aieo_include_attributes', $settings['include_attributes']); update_option('aieo_include_full_text', $settings['include_full_text']); update_option('aieo_include_price', $settings['include_price']); update_option('aieo_variations_choice', $settings['variations_choice']); update_option('aieo_include_eponymous', $settings['include_eponymous']); update_option('aieo_include_reciprocity', $settings['include_reciprocity']); update_option('aieo_include_seasonality', $settings['include_seasonality']); update_option('aieo_include_restricted_seasonality', $settings['include_restricted_seasonality']); update_option('aieo_instore_anonymous', $settings['instore_anonymous']); update_option('aieo_core_attribute_vendor', $settings['vendor_taxonomy']); update_option('aieo_core_attribute_brands', $settings['brand_taxonomy']); update_option('aieo_core_attribute_size', $settings['size_taxonomies']); update_option('aieo_core_attribute_color', $settings['color_taxonomies']); update_option('aieo_core_attribute_need', $settings['need_taxonomy']); update_option('aieo_season_days_past', $settings['season_days_past']); update_option('aieo_season_days_after', $settings['season_days_after']); wp_cache_delete('aieo_create_attributes', 'options'); wp_cache_delete('aieo_create_needs', 'options'); wp_cache_delete('aieo_create_need_suggestions', 'options'); wp_cache_delete('aieo_create_catalogue', 'options'); wp_cache_delete('aieo_create_orders', 'options'); wp_cache_delete('aieo_erp_consolidation', 'options'); wp_cache_delete('aieo_erp_merge_orders', 'options'); wp_cache_delete('aieo_include_unsold', 'options'); wp_cache_delete('aieo_include_aggregate_stats', 'options'); wp_cache_delete('aieo_include_aggregate_cust_std_stats', 'options'); wp_cache_delete('aieo_include_aggregate_cust_adv_stats', 'options'); wp_cache_delete('aieo_create_uuids', 'options'); wp_cache_delete('aieo_create_export', 'options'); wp_cache_delete('aieo_include_text', 'options'); wp_cache_delete('aieo_include_attributes', 'options'); wp_cache_delete('aieo_include_full_text', 'options'); wp_cache_delete('aieo_include_price', 'options'); wp_cache_delete('aieo_variations_choice', 'options'); wp_cache_delete('aieo_include_eponymous', 'options'); wp_cache_delete('aieo_include_reciprocity', 'options'); wp_cache_delete('aieo_include_seasonality', 'options'); wp_cache_delete('aieo_include_restricted_seasonality', 'options'); wp_cache_delete('aieo_instore_anonymous', 'options'); wp_cache_delete('aieo_season_days_past', 'options'); wp_cache_delete('aieo_season_days_after', 'options'); wp_cache_delete('aieo_core_attribute_need', 'options'); wp_send_json_success(array('message' => __('Settings saved successfully', 'ai-eshop-optimizer'))); } private static function aieo_register_export_lock_shutdown($lock_key, $export_id) { static $registered = array(); $sig = $lock_key . '|' . $export_id; if (isset($registered[$sig])) { return; } $registered[$sig] = true; register_shutdown_function(function () use ($lock_key, $export_id) { $err = error_get_last(); $fatal_types = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); if (!$err || !in_array($err['type'], $fatal_types, true)) { return; } if (get_transient($lock_key) === $export_id) { delete_transient($lock_key); } }); } public function aieo_process_export_batch() { check_ajax_referer('aieo_export_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions', 'ai-eshop-optimizer'))); return; } global $wpdb; $stage = isset($_POST['stage']) ? sanitize_text_field(wp_unslash($_POST['stage'])) : ''; $export_id = isset($_POST['export_id']) ? sanitize_text_field(wp_unslash($_POST['export_id'])) : ''; if (empty($export_id)) { $export_id = get_transient('aieo_export_id'); } $source = isset($_POST['export_id']) ? 'POST' : 'transient'; aieo_debug_log("AIEO Export Batch - Stage: {$stage}, Export ID: " . ($export_id ? $export_id : 'NULL') . " (source: {$source})"); if (!$export_id) { $all_transients = $wpdb->get_results( "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE '_transient_aieo_%' ORDER BY option_name", ARRAY_A ); aieo_debug_log("AIEO Export - Transients found: " . wp_json_encode($all_transients)); wp_send_json_error(array('message' => __('Export session expired. Please start a new export.', 'ai-eshop-optimizer'))); return; } set_transient('aieo_export_id', $export_id, 7200); $settings = get_transient('aieo_export_settings'); if ($settings) { set_transient('aieo_export_settings', $settings, 7200); } $export_lock = get_transient('aieo_export_lock'); if ($export_lock && $export_lock !== $export_id) { wp_send_json_error(array( 'message' => __('An export is already running. Please wait for it to complete or refresh the page.', 'ai-eshop-optimizer') )); return; } set_transient('aieo_export_lock', $export_id, 1800); $settings = get_transient('aieo_export_settings'); if (!$settings) { $settings = array( 'create_attributes' => get_option('aieo_create_attributes', '0'), 'create_needs' => get_option('aieo_create_needs', '0'), 'create_catalogue' => get_option('aieo_create_catalogue', '0'), 'process_eshop_orders' => get_option('aieo_create_orders', '0'), 'process_instore_orders' => get_option('aieo_erp_consolidation', '0'), 'merge_orders' => get_option('aieo_erp_merge_orders', '0'), 'include_unsold' => get_option('aieo_include_unsold', '0'), 'aggregate_product_stats' => get_option('aieo_include_aggregate_stats', '0'), 'aggregate_category_brand_stats' => get_option('aieo_include_aggregate_category_brand_stats', '0'), 'aggregate_customer_std_stats' => get_option('aieo_include_aggregate_cust_std_stats', '0'), 'aggregate_customer_adv_stats' => get_option('aieo_include_aggregate_cust_adv_stats', '0'), 'create_uuids' => get_option('aieo_create_uuids', '0'), 'create_need_suggestions' => get_option('aieo_create_need_suggestions', '0'), 'create_export' => get_option('aieo_create_export', '0'), 'export_product_titles' => get_option('aieo_export_product_titles', '0'), 'export_prices' => get_option('aieo_export_prices', '0'), 'export_customer_ids' => get_option('aieo_export_customer_ids', '0'), 'seasonal_option' => get_option('aieo_seasonal_option', '0'), 'vendor_taxonomy' => get_option('aieo_vendor_taxonomy', ''), 'brand_taxonomy' => get_option('aieo_brand_taxonomy', ''), 'size_taxonomies' => get_option('aieo_size_taxonomies', ''), 'color_taxonomies' => get_option('aieo_color_taxonomies', '') ); set_transient('aieo_export_settings', $settings, 7200); } $site_prefix = $wpdb->prefix; $time_limit = 600; if ($stage === 'create_attributes') { $time_limit = 900; } elseif ($stage === 'process_eshop_orders' || $stage === 'process_instore_orders') { $time_limit = 900; } elseif ($stage === 'aggregate_customer_std_stats' || $stage === 'aggregate_customer_adv_stats') { $time_limit = 1800; } set_time_limit($time_limit); try { switch ($stage) { case 'create_attributes': if ($settings['create_attributes'] == '1') { $sub_step = isset($_POST['sub_step']) ? intval($_POST['sub_step']) : 0; $total_sub_steps = 6; aieo_debug_log("AIEO: create_attributes - Sub-step: {$sub_step}"); switch ($sub_step) { case 0: aieo_debug_log("AIEO: Executing InsertCoreProductAttributes"); $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_InsertCoreProductAttributes` (%s, %s, %s, %s)", $settings['vendor_taxonomy'], $settings['brand_taxonomy'], $settings['size_taxonomies'], $settings['color_taxonomies'] )); $count_attributes = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}aieo_core_product_attributes"); aieo_debug_log("AIEO: Brands created: {$count_attributes} records"); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Step 1/6: ✓ Brands: %d records', 'ai-eshop-optimizer'), $count_attributes), 'current' => 1, 'total' => $total_sub_steps, 'sub_step' => 1 )); break; case 1: aieo_debug_log("AIEO: Executing InsertProductCategoryHierarchy"); aieo_call_sproc("CALL `{$site_prefix}AIEO_InsertProductCategoryHierarchy` ()", null); $count_hierarchy = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}aieo_core_product_category_hierarchy"); aieo_debug_log("AIEO: Category Hierarchy created: {$count_hierarchy} records"); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Step 2/6: ✓ Category Hierarchy: %d records', 'ai-eshop-optimizer'), $count_hierarchy), 'current' => 2, 'total' => $total_sub_steps, 'sub_step' => 2 )); break; case 2: aieo_debug_log("AIEO: Executing InsertProductVitals"); aieo_call_sproc("CALL `{$site_prefix}AIEO_InsertProductVitals` ()", null); $count_product_vitals = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}aieo_core_product_vitals WHERE parent_id = product_id"); aieo_debug_log("AIEO: Product Vitals (Parents) created: {$count_product_vitals} records"); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Step 3/6: ✓ Product Vitals (Parents): %d records', 'ai-eshop-optimizer'), $count_product_vitals), 'current' => 3, 'total' => $total_sub_steps, 'sub_step' => 3 )); break; case 3: aieo_debug_log("AIEO: Executing InsertVariationVitals"); aieo_debug_log("AIEO: Settings - Vendor: {$settings['vendor_taxonomy']}, Brand: {$settings['brand_taxonomy']}, Size: {$settings['size_taxonomies']}, Color: {$settings['color_taxonomies']}"); $variation_vitals_table = $wpdb->prefix . 'aieo_core_variation_vitals'; $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $variation_vitals_table)); if (!$table_exists) { aieo_debug_log("AIEO: ERROR - Variation vitals table does not exist: {$variation_vitals_table}"); wp_send_json_error(array('message' => __('Variation vitals table does not exist. Please check database setup.', 'ai-eshop-optimizer'))); break; } $products_with_variations = $wpdb->get_var(" SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->prefix}posts p INNER JOIN {$wpdb->prefix}posts v ON p.ID = v.post_parent WHERE p.post_type = 'product' AND p.post_status = 'publish' AND v.post_type = 'product_variation' AND v.post_status = 'publish' "); aieo_debug_log("AIEO: Found {$products_with_variations} products with variations"); $result = aieo_call_sproc("CALL `{$site_prefix}AIEO_InsertVariationVitals` ()", null); if ($wpdb->last_error) { aieo_debug_log("AIEO: ERROR in InsertVariationVitals - Database error: " . $wpdb->last_error); wp_send_json_error(array( 'message' => __('Database error:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); break; } if ($result === false) { aieo_debug_log("AIEO: ERROR - InsertVariationVitals returned false"); wp_send_json_error(array('message' => __('Failed to execute Variation Vitals procedure. Check WordPress debug log for details.', 'ai-eshop-optimizer'))); break; } $count_variation_vitals = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}aieo_core_variation_vitals"); aieo_debug_log("AIEO: Variation Vitals created: {$count_variation_vitals} records"); $sample_variations = $wpdb->get_results("SELECT product_id, parent_id, size, color FROM {$wpdb->prefix}aieo_core_variation_vitals LIMIT 5", ARRAY_A); aieo_debug_log("AIEO: Sample variation vitals: " . wp_json_encode($sample_variations)); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Step 4/6: ✓ Variation Vitals: %d records', 'ai-eshop-optimizer'), $count_variation_vitals), 'current' => 4, 'total' => $total_sub_steps, 'sub_step' => 4 )); break; case 4: aieo_debug_log("AIEO: Executing CombineVariationVitalsInProductVitals"); aieo_call_sproc("CALL `{$site_prefix}AIEO_CombineVariationVitalsInProductVitals` ()", null); $count_variations_combined = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}aieo_core_product_vitals WHERE parent_id != product_id"); aieo_debug_log("AIEO: Variations combined: {$count_variations_combined} records"); if (!class_exists('AI_eShop_Product_Needs')) { require_once plugin_dir_path(__FILE__) . 'class-product-needs.php'; } AI_eShop_Product_Needs::get_instance()->populate_product_needs(); $count_with_needs = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}aieo_core_product_vitals WHERE primary_need_id IS NOT NULL"); aieo_debug_log("AIEO: Needs populated for {$count_with_needs} vitals rows"); delete_transient('aieo_chat_sellable_breakdown'); delete_transient('aieo_chat_count_sellable_units_0'); delete_transient('aieo_chat_count_sellable_units_1'); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Step 5/6: ✓ Variations Added to Product Vitals: %1$d records (needs populated for %2$d)', 'ai-eshop-optimizer'), $count_variations_combined, $count_with_needs), 'current' => 5, 'total' => $total_sub_steps, 'sub_step' => 5 )); break; case 5: $total_vitals = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}aieo_core_product_vitals"); aieo_debug_log("AIEO: Total Product Vitals: {$total_vitals} records"); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('Step 6/6: ✓ Product attributes and vitals created! Total: %d records', 'ai-eshop-optimizer'), $total_vitals), 'current' => $total_sub_steps, 'total' => $total_sub_steps )); break; default: wp_send_json_error(array('message' => __('Invalid sub-step:', 'ai-eshop-optimizer') . ' ' . $sub_step)); break; } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Create attributes skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'create_needs': if ($settings['create_needs'] == '1') { require_once plugin_dir_path(__FILE__) . 'class-product-needs.php'; $needs_handler = AI_eShop_Product_Needs::get_instance(); if ($needs_handler->populate_product_needs()) { $count = $wpdb->get_var(" SELECT COUNT(DISTINCT product_id) FROM {$wpdb->prefix}aieo_core_product_vitals WHERE primary_need_id IS NOT NULL "); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ Successfully populated needs for %d products', 'ai-eshop-optimizer'), $count), 'current' => $count, 'total' => $count )); } else { wp_send_json_error(array('message' => __('Failed to populate product needs', 'ai-eshop-optimizer'))); } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Create needs skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'create_catalogue': if ($settings['create_catalogue'] == '1') { $price_sql_choice = $settings['export_prices'] == '1' ? 1 : 0; $text_sql_choice = $settings['export_product_titles'] == '1' ? 1 : 0; $full_text_sql_choice = 0; $catalogue_table = get_option('aieo_site_products_table', $wpdb->prefix . 'aieo_temp_product_catalogue'); $batch_offset = isset($_POST['batch_offset']) ? intval($_POST['batch_offset']) : 0; $batch_size = 2000; if ($batch_offset === 0) { $total_vitals = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}aieo_core_product_vitals"); set_transient('aieo_catalogue_total', $total_vitals, 3600); $wpdb->query("TRUNCATE TABLE {$catalogue_table}"); } else { $total_vitals = get_transient('aieo_catalogue_total'); } $wpdb->query($wpdb->prepare(" INSERT INTO {$catalogue_table} (OrderId, OrderStatus, SKU, ParentSKU, ProductId, ParentProductId, CategoryId, TagId, Stock, OnSale, RoleBasedSpecialPricing, CurrentPrice, CurrentRegularPrice, ItemName, ParentItemName, ProfitabilityIndex, VendorId, Vendor, BrandId, Brand, BrandSlug, MenuId, MenuCategory, HeadId, HeadCategory, SubcatId, SubCategory, Weight, Length, Width, Height, Size, Color, ImageUrl, ImageGalleryUrl, ContentIntro, ContentIntroPlain, ProductUrl, ERPId, CategoryName, TagName, Seasonality, ContentCreated, ContentLastUpdated, ProdIdGraphUUID, PPIdGraphUUID) SELECT 0 AS OrderId, 'unsold' AS OrderStatus, pv.sku AS SKU, pv.parent_sku AS ParentSKU, pv.product_id AS ProductId, pv.parent_product_id AS ParentProductId, pv.category_ids AS CategoryId, pv.tag_ids AS TagId, pv.stock AS Stock, pv.onsale AS OnSale, pv.role_special_pricing AS RoleBasedSpecialPricing, CASE WHEN %d = 0 THEN '' ELSE pv.price END AS CurrentPrice, CASE WHEN %d = 0 THEN '' ELSE pv.regular_price END AS CurrentRegularPrice, CASE WHEN %d = 0 THEN '' ELSE pv.product_name END AS ItemName, CASE WHEN %d = 0 THEN '' ELSE pv.parent_product_name END AS ParentItemName, '1' AS ProfitabilityIndex, pv.vendor_id AS VendorId, CASE WHEN %d = 0 THEN '' ELSE pv.vendor END AS Vendor, pv.brand_id AS BrandId, CASE WHEN %d = 0 THEN '' ELSE pv.brand END AS Brand, CASE WHEN %d = 0 THEN '' ELSE pv.brand_slug END AS BrandSlug, pv.menu_id AS MenuId, CASE WHEN %d = 0 THEN '' ELSE pv.menu_category END AS MenuCategory, pv.head_id AS HeadId, CASE WHEN %d = 0 THEN '' ELSE pv.head_category END AS HeadCategory, pv.subcat_id AS SubcatId, CASE WHEN %d = 0 THEN '' ELSE pv.sub_category END AS SubCategory, pv.weight AS Weight, pv.length AS Length, pv.width AS Width, pv.height AS Height, pv.size AS Size, pv.color AS Color, pv.image_url AS ImageUrl, pv.image_gallery_urls AS ImageGalleryUrl, CASE WHEN %d = 0 THEN '' ELSE pv.product_description END AS ContentIntro, CASE WHEN %d = 0 THEN '' ELSE pv.product_description END AS ContentIntroPlain, pv.product_url AS ProductUrl, pv.erp_id AS ERPId, CASE WHEN %d = 0 THEN '' ELSE pv.category_names END AS CategoryName, CASE WHEN %d = 0 THEN '' ELSE pv.tag_names END AS TagName, '0' AS Seasonality, pv.content_created AS ContentCreated, pv.content_last_updated AS ContentLastUpdated, pv.ProdIdGraphUUID AS ProdIdGraphUUID, pvp.PPIdGraphUUID AS PPIdGraphUUID FROM {$wpdb->prefix}aieo_core_product_vitals pv INNER JOIN {$wpdb->prefix}aieo_core_product_vitals pvp ON pv.parent_product_id = pvp.product_id LIMIT %d OFFSET %d ", $price_sql_choice, $price_sql_choice, $text_sql_choice, $text_sql_choice, $text_sql_choice, $text_sql_choice, $text_sql_choice, $text_sql_choice, $text_sql_choice, $text_sql_choice, $text_sql_choice, $text_sql_choice, $text_sql_choice, $text_sql_choice, $batch_size, $batch_offset)); if ($wpdb->last_error) { wp_send_json_error(array( 'message' => __('Catalogue creation failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } $count_total = $wpdb->get_var("SELECT COUNT(*) FROM {$catalogue_table}"); if ($count_total >= $total_vitals || $batch_offset + $batch_size >= $total_vitals) { $count_parents = $wpdb->get_var("SELECT COUNT(*) FROM {$catalogue_table} WHERE ParentProductId = ProductId"); $count_variations = $wpdb->get_var("SELECT COUNT(*) FROM {$catalogue_table} WHERE ParentProductId != ProductId"); delete_transient('aieo_catalogue_total'); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ Product Catalogue: %1$d total (%2$d parents, %3$d variations)', 'ai-eshop-optimizer'), $count_total, $count_parents, $count_variations), 'current' => $count_total, 'total' => $total_vitals )); } else { wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Processing catalogue: %1$d of %2$d products...', 'ai-eshop-optimizer'), $count_total, $total_vitals), 'current' => $count_total, 'total' => $total_vitals, 'batch_offset' => $batch_offset + $batch_size )); } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Create catalogue skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'process_eshop_orders': if ($settings['process_eshop_orders'] == '1') { $progress_step = isset($_POST['progress_step']) ? intval($_POST['progress_step']) : 0; $temp_orders_table = get_option('aieo_site_orders_table', $wpdb->prefix . 'aieo_temp_orders'); if ($progress_step === 0) { $is_hpos = class_exists('Automattic\WooCommerce\Utilities\OrderUtil') && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); if ($is_hpos) { $total_orders = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wc_orders WHERE type = 'shop_order' AND status IN ('wc-processing', 'wc-completed')"); } else { $total_orders = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}posts WHERE post_type = 'shop_order' AND post_status IN ('wc-processing', 'wc-completed')"); } set_transient('aieo_orders_total', $total_orders, 3600); set_transient('aieo_orders_hpos', $is_hpos ? 1 : 0, 3600); $wpdb->query("TRUNCATE TABLE {$temp_orders_table}"); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Processing %1$s orders: 0 of %2$d orders...', 'ai-eshop-optimizer'), ($is_hpos ? __('HPOS', 'ai-eshop-optimizer') : __('Legacy', 'ai-eshop-optimizer')), $total_orders), 'current' => 0, 'total' => $total_orders, 'progress_step' => 1 )); } else if ($progress_step === 1) { $total_orders = get_transient('aieo_orders_total'); $is_hpos = get_transient('aieo_orders_hpos'); $price_sql_choice = $settings['export_prices'] == '1' ? 1 : 0; $text_sql_choice = $settings['export_product_titles'] == '1' ? 1 : 0; $full_text_sql_choice = 0; $eponymous_sql_choice = $settings['export_customer_ids'] == '1' ? 1 : 0; $seasonality_sql_choice = $settings['seasonal_option'] == '1' ? 1 : 0; $order_id = get_option('aieo_order_id', 0); $max_records = get_option('aieo_max_records', 999999); @set_time_limit(300); if ($is_hpos) { $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_Export_Orders_HPOS` (%d, %d, %d, %d, %d, %d, %d)", $price_sql_choice, $text_sql_choice, $full_text_sql_choice, $eponymous_sql_choice, $seasonality_sql_choice, $order_id, $max_records )); } else { $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_Export_Orders_non_HPOS` (%d, %d, %d, %d, %d, %d, %d)", $price_sql_choice, $text_sql_choice, $full_text_sql_choice, $eponymous_sql_choice, $seasonality_sql_choice, $order_id, $max_records )); } if ($wpdb->last_error) { delete_transient('aieo_orders_total'); delete_transient('aieo_orders_hpos'); wp_send_json_error(array( 'message' => __('Order processing failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } $count_total = $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table}"); $count_orders = $wpdb->get_var("SELECT COUNT(DISTINCT OrderId) FROM {$temp_orders_table}"); $count_products = $wpdb->get_var("SELECT COUNT(DISTINCT ProductId) FROM {$temp_orders_table}"); delete_transient('aieo_orders_total'); delete_transient('aieo_orders_hpos'); $hpos_status = $is_hpos ? __('HPOS', 'ai-eshop-optimizer') : __('Legacy', 'ai-eshop-optimizer'); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ Eshop Orders (%1$s): %2$d order items from %3$d orders (%4$d unique products)', 'ai-eshop-optimizer'), $hpos_status, $count_total, $count_orders, $count_products), 'current' => $count_total, 'total' => $count_total )); } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Process eshop orders skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'process_instore_orders': if ($settings['process_instore_orders'] == '1') { $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$wpdb->prefix}s1erp_orders'"); if ($table_exists) { $price_sql_choice = $settings['export_prices'] == '1' ? 1 : 0; $eponymous_sql_choice = $settings['export_customer_ids'] == '1' ? 1 : 0; $seasonality_sql_choice = $settings['seasonal_option'] == '1' ? 1 : 0; $season_days_past = get_option('aieo_season_days_past', 15); $season_days_after = get_option('aieo_season_days_after', 60); $instore_invoices_list = get_option('aieo_erp_series_to_ignore', ''); $instore_invoices_list_sql_choice = !empty($instore_invoices_list) ? 1 : 0; $instore_anonymous_sql_choice = get_option('aieo_instore_anonymous', 0); $instore_anonymous_list = get_option('aieo_erp_store_anonymous_invoice_users', ''); $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_Export_Store_Orders` (%d, %d, %d, %d, %d, %d, %s, %d, %s)", $price_sql_choice, $eponymous_sql_choice, $seasonality_sql_choice, $season_days_past, $season_days_after, $instore_invoices_list_sql_choice, $instore_invoices_list, $instore_anonymous_sql_choice, $instore_anonymous_list )); $store_orders_table = get_option('aieo_store_orders_table', $wpdb->prefix . 'aieo_temp_store_orders'); $count_total = $wpdb->get_var("SELECT COUNT(*) FROM {$store_orders_table}"); $count_orders = $wpdb->get_var("SELECT COUNT(DISTINCT OrderId) FROM {$store_orders_table}"); $count_products = $wpdb->get_var("SELECT COUNT(DISTINCT ProductId) FROM {$store_orders_table}"); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ In-Store Orders: %1$d order items from %2$d invoices (%3$d unique products)', 'ai-eshop-optimizer'), $count_total, $count_orders, $count_products), 'current' => $count_total, 'total' => $count_total )); } else { wp_send_json_success(array( 'complete' => true, 'message' => __('In-store orders skipped (meta-connector not active)', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Process in-store orders skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'merge_orders': if ($settings['merge_orders'] == '1') { $store_orders_table_name = get_option('aieo_store_orders_table', $wpdb->prefix . 'aieo_temp_store_orders'); $temp_orders_table_name = get_option('aieo_site_orders_table', $wpdb->prefix . 'aieo_temp_orders'); $analytics_eponymous_id_field = get_option('aieo_analytics_eponymous_id_field', 'customer_id'); $instore_product_threshold = get_option('aieo_erp_product_price_to_ignore', ''); $instore_product_threshold_sql_choice = !empty($instore_product_threshold) ? 1 : 0; $instore_products_list = get_option('aieo_erp_products_to_ignore', ''); $instore_products_list_sql_choice = !empty($instore_products_list) ? 1 : 0; $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_Merge_Store_Orders` (%s, %s, %s, %d, %s, %d, %s)", $store_orders_table_name, $temp_orders_table_name, $analytics_eponymous_id_field, $instore_product_threshold_sql_choice, $instore_product_threshold, $instore_products_list_sql_choice, $instore_products_list )); $count_total = $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table_name}"); $count_eshop = $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table_name} WHERE OrderId > 0"); $count_store = $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table_name} WHERE OrderId < 0"); $count_orders = $wpdb->get_var("SELECT COUNT(DISTINCT OrderId) FROM {$temp_orders_table_name}"); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ Orders Merged: %1$d total items (%2$d eshop + %3$d in-store) from %4$d orders', 'ai-eshop-optimizer'), $count_total, $count_eshop, $count_store, $count_orders), 'current' => $count_total, 'total' => $count_total )); } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Merge orders skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'include_unsold': if ($settings['include_unsold'] == '1') { $temp_orders_table = $wpdb->prefix . 'aieo_temp_orders'; $catalogue_table = get_option('aieo_site_products_table', $wpdb->prefix . 'aieo_temp_product_catalogue'); $progress_step = isset($_POST['progress_step']) ? intval($_POST['progress_step']) : 0; if ($progress_step === 0) { $count_orders = $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table}"); $count_catalogue = $wpdb->get_var("SELECT COUNT(*) FROM {$catalogue_table}"); if ($count_orders == 0) { wp_send_json_error(array('message' => __('Orders table is empty. Cannot include unsold products.', 'ai-eshop-optimizer'))); return; } set_transient('aieo_unsold_count_before', $count_orders, 3600); set_transient('aieo_unsold_start_time', time(), 3600); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Analyzing %d products to find unsold items...', 'ai-eshop-optimizer'), $count_catalogue), 'current' => 0, 'total' => 100, 'progress_step' => 1 )); } else if ($progress_step === 1) { $start_time = get_transient('aieo_unsold_start_time'); $elapsed = time() - $start_time; $result = $wpdb->query(" INSERT INTO {$temp_orders_table} (OrderStatus, SKU, ProductId, ParentProductId, CategoryId, TagId, CurrentPrice, CurrentRegularPrice, Stock, ItemName, ParentItemName, Brand, CategoryName, TagName, ContentIntro, ContentIntroPlain, ContentCreated, ContentLastUpdated, PPIdGraphUUID, ProdIdGraphUUID) SELECT st.OrderStatus, st.SKU, st.ProductId, st.ParentProductId, st.CategoryId, st.TagId, st.CurrentPrice, st.CurrentRegularPrice, st.Stock, st.ItemName, st.ParentItemName, st.Brand, st.CategoryName, st.TagName, st.ContentIntro, st.ContentIntroPlain, st.ContentCreated, st.ContentLastUpdated, st.PPIdGraphUUID, st.ProdIdGraphUUID FROM {$catalogue_table} st LEFT JOIN {$temp_orders_table} atot ON atot.ProductId = st.ProductId WHERE atot.ProductId IS NULL "); if ($wpdb->last_error) { delete_transient('aieo_unsold_count_before'); delete_transient('aieo_unsold_start_time'); wp_send_json_error(array('message' => __('Error inserting unsold products:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error)); return; } $unsold_added = $wpdb->rows_affected; wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Added %1$d unsold products. Updating UUIDs... (%2$ds elapsed, 50%% complete)', 'ai-eshop-optimizer'), $unsold_added, $elapsed), 'current' => 50, 'total' => 100, 'progress_step' => 2 )); } else if ($progress_step === 2) { $start_time = get_transient('aieo_unsold_start_time'); $elapsed = time() - $start_time; $result = $wpdb->query(" UPDATE {$temp_orders_table} atot INNER JOIN {$catalogue_table} st ON atot.ProductId = st.ProductId SET atot.PPIdGraphUUID = st.PPIdGraphUUID, atot.ProdIdGraphUUID = st.ProdIdGraphUUID WHERE atot.PPIdGraphUUID IS NULL OR atot.ProdIdGraphUUID IS NULL "); if ($wpdb->last_error) { delete_transient('aieo_unsold_count_before'); delete_transient('aieo_unsold_start_time'); wp_send_json_error(array('message' => __('Error updating UUIDs:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error)); return; } $uuids_updated = $wpdb->rows_affected; $count_before = get_transient('aieo_unsold_count_before'); $count_after = $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table}"); $unsold_added = $count_after - $count_before; delete_transient('aieo_unsold_count_before'); delete_transient('aieo_unsold_start_time'); $this->aieo_ensure_mysql_completion(); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ Unsold products included: Added %1$d unsold products after examining %2$d sold product line items (Total: %3$d records, %4$d UUIDs updated, %5$ds elapsed)', 'ai-eshop-optimizer'), $unsold_added, $count_before, $count_after, $uuids_updated, $elapsed), 'current' => $count_after, 'total' => $count_after )); } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Include unsold skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'aggregate_product_stats': if ($settings['aggregate_product_stats'] == '1') { $temp_orders_table_name = get_option('aieo_site_orders_table', $wpdb->prefix . 'aieo_temp_orders'); $progress_step = isset($_POST['progress_step']) ? intval($_POST['progress_step']) : 0; $stats_temp_table = $wpdb->prefix . 'aieo_product_stats_temp'; if ($progress_step === 0) { $stats_lock = get_transient('aieo_product_stats_lock'); if ($stats_lock && $stats_lock !== $export_id) { wp_send_json_error(array( 'message' => __('Product statistics are already being calculated by another process.', 'ai-eshop-optimizer') )); return; } set_transient('aieo_product_stats_lock', $export_id, 300); self::aieo_register_export_lock_shutdown('aieo_product_stats_lock', $export_id); $total_products = $wpdb->get_var("SELECT COUNT(DISTINCT ParentProductId) FROM {$temp_orders_table_name} WHERE ParentProductId IS NOT NULL"); set_transient('aieo_product_stats_total', $total_products, 3600); set_transient('aieo_product_stats_start_time', time(), 3600); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Initializing product statistics for %d products...', 'ai-eshop-optimizer'), $total_products), 'current' => 0, 'total' => 100, 'progress_step' => 1 )); } else if ($progress_step === 1) { $start_time = get_transient('aieo_product_stats_start_time'); $elapsed = time() - $start_time; $wpdb->query("DROP TABLE IF EXISTS {$stats_temp_table}"); $result = $wpdb->query(" CREATE TABLE {$stats_temp_table} AS SELECT ParentProductId, SUM(PrimaryQty) AS TotalItemSales, COUNT(DISTINCT OrderId) AS DistinctOrderSales, SUM(PrimaryQty * Price) AS TotalTurnover, SUM(CASE WHEN ItemSequence = 1 AND OrderSize = 1 THEN 1 ELSE 0 END) AS UniqueItemSales, SUM(CASE WHEN ItemSequence = 1 THEN 1 ELSE 0 END) AS FirstItemSales, SUM(CASE WHEN ItemSequence = OrderSize THEN 1 ELSE 0 END) AS LastItemSales, AVG(JourneyLinearWeight) AS AvgJLWValue, AVG(ValueLinearWeight) AS AvgVLWValue FROM {$temp_orders_table_name} WHERE ParentProductId IS NOT NULL AND (OriginId <> 2 OR LoyaltyID IS NOT NULL) GROUP BY ParentProductId "); if ($wpdb->last_error) { $wpdb->query("DROP TABLE IF EXISTS {$stats_temp_table}"); delete_transient('aieo_product_stats_total'); delete_transient('aieo_product_stats_start_time'); delete_transient('aieo_product_stats_lock'); wp_send_json_error(array( 'message' => __('Product statistics aggregation failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } $wpdb->query("ALTER TABLE {$stats_temp_table} ADD INDEX idx_parent_product (ParentProductId)"); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Aggregated statistics for all products. Applying to orders table... (%ds elapsed, 50%% complete)', 'ai-eshop-optimizer'), $elapsed), 'current' => 50, 'total' => 100, 'progress_step' => 2 )); } else if ($progress_step === 2) { $total_products = get_transient('aieo_product_stats_total'); $start_time = get_transient('aieo_product_stats_start_time'); $elapsed = time() - $start_time; $wpdb->query(" UPDATE {$temp_orders_table_name} AS t1 INNER JOIN {$stats_temp_table} AS t2 ON t1.ParentProductId = t2.ParentProductId SET t1.TotalItemSales = t2.TotalItemSales, t1.DistinctOrderSales = t2.DistinctOrderSales, t1.TotalTurnover = t2.TotalTurnover, t1.UniqueItemSales = t2.UniqueItemSales, t1.FirstItemSales = t2.FirstItemSales, t1.LastItemSales = t2.LastItemSales, t1.AvgJLW = t2.AvgJLWValue, t1.AvgVLW = t2.AvgVLWValue "); if ($wpdb->last_error) { $wpdb->query("DROP TABLE IF EXISTS {$stats_temp_table}"); delete_transient('aieo_product_stats_total'); delete_transient('aieo_product_stats_start_time'); delete_transient('aieo_product_stats_lock'); wp_send_json_error(array( 'message' => __('Product statistics update failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } $wpdb->query("DROP TABLE IF EXISTS {$stats_temp_table}"); $count_with_stats = $wpdb->get_var("SELECT COUNT(DISTINCT ParentProductId) FROM {$temp_orders_table_name} WHERE TotalItemSales IS NOT NULL AND TotalItemSales > 0"); delete_transient('aieo_product_stats_total'); delete_transient('aieo_product_stats_start_time'); delete_transient('aieo_product_stats_lock'); $this->aieo_ensure_mysql_completion(); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ Product statistics: %1$d products with sales data (out of %2$d total products, completed in %3$ds)', 'ai-eshop-optimizer'), $count_with_stats, $total_products, $elapsed), 'current' => $total_products, 'total' => $total_products )); } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Aggregate product stats skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'aggregate_category_brand_stats': if ($settings['aggregate_category_brand_stats'] == '1') { $temp_orders_table_name = get_option('aieo_site_orders_table', $wpdb->prefix . 'aieo_temp_orders'); $popularity_table = get_option('aieo_core_category_brand_popularity_table', $wpdb->prefix . 'aieo_core_category_brand_popularity'); $progress_step = isset($_POST['progress_step']) ? intval($_POST['progress_step']) : 0; $sales_period = get_option('aieo_category_sales_period', 'all_time'); $date_filter = ''; $period_label = __('All time', 'ai-eshop-optimizer'); switch ($sales_period) { case 'last_30_days': $date_filter = " AND t.OrderDate >= DATE_SUB(UTC_DATE(), INTERVAL 30 DAY)"; $period_label = __('Last 30 days', 'ai-eshop-optimizer'); break; case 'last_3_months': $date_filter = " AND t.OrderDate >= DATE_SUB(UTC_DATE(), INTERVAL 3 MONTH)"; $period_label = __('Last 3 months', 'ai-eshop-optimizer'); break; case 'last_6_months': $date_filter = " AND t.OrderDate >= DATE_SUB(UTC_DATE(), INTERVAL 6 MONTH)"; $period_label = __('Last 6 months', 'ai-eshop-optimizer'); break; case 'last_12_months': $date_filter = " AND t.OrderDate >= DATE_SUB(UTC_DATE(), INTERVAL 12 MONTH)"; $period_label = __('Last 12 months', 'ai-eshop-optimizer'); break; default: $date_filter = ''; break; } if ($progress_step === 0) { $stats_lock = get_transient('aieo_category_brand_stats_lock'); if ($stats_lock && $stats_lock !== $export_id) { wp_send_json_error(array( 'message' => __('Category/brand statistics are already being calculated by another process.', 'ai-eshop-optimizer') )); return; } set_transient('aieo_category_brand_stats_lock', $export_id, 300); self::aieo_register_export_lock_shutdown('aieo_category_brand_stats_lock', $export_id); set_transient('aieo_category_brand_stats_start_time', time(), 3600); $wpdb->query("TRUNCATE TABLE {$popularity_table}"); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Initializing category/brand popularity analysis (%s)...', 'ai-eshop-optimizer'), $period_label), 'current' => 0, 'total' => 100, 'progress_step' => 1 )); } else if ($progress_step === 1) { $start_time = get_transient('aieo_category_brand_stats_start_time'); $elapsed = time() - $start_time; $wpdb->query(" INSERT INTO {$popularity_table} (category_id, category_name, category_slug, category_level, parent_category_id, brand_id, total_sales, total_turnover, total_orders, product_count) SELECT t.MenuId as category_id, MAX(t.MenuCategory) as category_name, NULL as category_slug, 1 as category_level, NULL as parent_category_id, NULL as brand_id, COALESCE(SUM(t.PrimaryQty), 0) as total_sales, COALESCE(SUM(t.PrimaryQty * t.Price), 0) as total_turnover, COUNT(DISTINCT t.OrderId) as total_orders, COUNT(DISTINCT t.ParentProductId) as product_count FROM {$temp_orders_table_name} t WHERE t.MenuId IS NOT NULL{$date_filter} GROUP BY t.MenuId "); $wpdb->query(" INSERT INTO {$popularity_table} (category_id, category_name, category_slug, category_level, parent_category_id, brand_id, total_sales, total_turnover, total_orders, product_count) SELECT t.HeadId as category_id, MAX(t.HeadCategory) as category_name, NULL as category_slug, 2 as category_level, MAX(t.MenuId) as parent_category_id, NULL as brand_id, COALESCE(SUM(t.PrimaryQty), 0) as total_sales, COALESCE(SUM(t.PrimaryQty * t.Price), 0) as total_turnover, COUNT(DISTINCT t.OrderId) as total_orders, COUNT(DISTINCT t.ParentProductId) as product_count FROM {$temp_orders_table_name} t WHERE t.HeadId IS NOT NULL{$date_filter} GROUP BY t.HeadId "); $wpdb->query(" INSERT INTO {$popularity_table} (category_id, category_name, category_slug, category_level, parent_category_id, brand_id, total_sales, total_turnover, total_orders, product_count) SELECT t.SubcatId as category_id, MAX(t.SubCategory) as category_name, NULL as category_slug, 3 as category_level, MAX(t.HeadId) as parent_category_id, NULL as brand_id, COALESCE(SUM(t.PrimaryQty), 0) as total_sales, COALESCE(SUM(t.PrimaryQty * t.Price), 0) as total_turnover, COUNT(DISTINCT t.OrderId) as total_orders, COUNT(DISTINCT t.ParentProductId) as product_count FROM {$temp_orders_table_name} t WHERE t.SubcatId IS NOT NULL{$date_filter} GROUP BY t.SubcatId "); if ($wpdb->last_error) { delete_transient('aieo_category_brand_stats_start_time'); delete_transient('aieo_category_brand_stats_lock'); wp_send_json_error(array( 'message' => __('Category statistics aggregation failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } $category_count = $wpdb->get_var("SELECT COUNT(*) FROM {$popularity_table} WHERE brand_id IS NULL"); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Aggregated %d categories. Now processing brands...', 'ai-eshop-optimizer'), $category_count) . " ({$elapsed}s)", 'current' => 25, 'total' => 100, 'progress_step' => 2 )); } else if ($progress_step === 2) { $start_time = get_transient('aieo_category_brand_stats_start_time'); $elapsed = time() - $start_time; $wpdb->query(" INSERT INTO {$popularity_table} (category_id, brand_id, brand_name, brand_slug, total_sales, total_turnover, total_orders, product_count) SELECT NULL as category_id, t.BrandId as brand_id, MAX(t.Brand) as brand_name, MAX(t.BrandSlug) as brand_slug, COALESCE(SUM(t.PrimaryQty), 0) as total_sales, COALESCE(SUM(t.PrimaryQty * t.Price), 0) as total_turnover, COUNT(DISTINCT t.OrderId) as total_orders, COUNT(DISTINCT t.ParentProductId) as product_count FROM {$temp_orders_table_name} t WHERE t.BrandId IS NOT NULL{$date_filter} GROUP BY t.BrandId "); if ($wpdb->last_error) { delete_transient('aieo_category_brand_stats_start_time'); delete_transient('aieo_category_brand_stats_lock'); wp_send_json_error(array( 'message' => __('Brand statistics aggregation failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } $brand_count = $wpdb->get_var("SELECT COUNT(*) FROM {$popularity_table} WHERE category_id IS NULL AND brand_id IS NOT NULL"); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Aggregated %d brands. Now processing category+brand combinations...', 'ai-eshop-optimizer'), $brand_count) . " ({$elapsed}s)", 'current' => 50, 'total' => 100, 'progress_step' => 3 )); } else if ($progress_step === 3) { $start_time = get_transient('aieo_category_brand_stats_start_time'); $elapsed = time() - $start_time; $wpdb->query(" INSERT INTO {$popularity_table} (category_id, category_name, category_level, parent_category_id, brand_id, brand_name, brand_slug, total_sales, total_turnover, total_orders, product_count) SELECT t.SubcatId as category_id, MAX(t.SubCategory) as category_name, 3 as category_level, MAX(t.HeadId) as parent_category_id, t.BrandId as brand_id, MAX(t.Brand) as brand_name, MAX(t.BrandSlug) as brand_slug, COALESCE(SUM(t.PrimaryQty), 0) as total_sales, COALESCE(SUM(t.PrimaryQty * t.Price), 0) as total_turnover, COUNT(DISTINCT t.OrderId) as total_orders, COUNT(DISTINCT t.ParentProductId) as product_count FROM {$temp_orders_table_name} t WHERE t.SubcatId IS NOT NULL AND t.BrandId IS NOT NULL{$date_filter} GROUP BY t.SubcatId, t.BrandId "); if ($wpdb->last_error) { delete_transient('aieo_category_brand_stats_start_time'); delete_transient('aieo_category_brand_stats_lock'); wp_send_json_error(array( 'message' => __('Category+brand combination aggregation failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } $combo_count = $wpdb->get_var("SELECT COUNT(*) FROM {$popularity_table} WHERE category_id IS NOT NULL AND brand_id IS NOT NULL"); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Aggregated %d category+brand combinations. Now calculating rankings...', 'ai-eshop-optimizer'), $combo_count) . " ({$elapsed}s)", 'current' => 75, 'total' => 100, 'progress_step' => 4 )); } else if ($progress_step === 4) { $start_time = get_transient('aieo_category_brand_stats_start_time'); $elapsed = time() - $start_time; $mysql_version = $wpdb->get_var("SELECT VERSION()"); $supports_window_functions = false; if (stripos($mysql_version, 'mariadb') !== false) { if (preg_match('/(\d+\.\d+)/', $mysql_version, $matches)) { $supports_window_functions = version_compare($matches[1], '10.2', '>='); } } else { if (preg_match('/^(\d+\.\d+)/', $mysql_version, $matches)) { $supports_window_functions = version_compare($matches[1], '8.0', '>='); } } if ($supports_window_functions) { $wpdb->query(" UPDATE {$popularity_table} p INNER JOIN ( SELECT id, RANK() OVER (PARTITION BY category_level ORDER BY total_sales DESC) as rank_sales, RANK() OVER (PARTITION BY category_level ORDER BY total_turnover DESC) as rank_turnover FROM {$popularity_table} WHERE brand_id IS NULL AND category_id IS NOT NULL ) r ON p.id = r.id SET p.category_rank_by_sales = r.rank_sales, p.category_rank_by_turnover = r.rank_turnover "); $wpdb->query(" UPDATE {$popularity_table} p INNER JOIN ( SELECT id, RANK() OVER (PARTITION BY parent_category_id ORDER BY total_sales DESC) as rank_sales, RANK() OVER (PARTITION BY parent_category_id ORDER BY total_turnover DESC) as rank_turnover FROM {$popularity_table} WHERE brand_id IS NULL AND parent_category_id IS NOT NULL ) r ON p.id = r.id SET p.subcategory_rank_in_parent_sales = r.rank_sales, p.subcategory_rank_in_parent_turnover = r.rank_turnover "); $wpdb->query(" UPDATE {$popularity_table} p INNER JOIN ( SELECT id, RANK() OVER (ORDER BY total_sales DESC) as rank_sales, RANK() OVER (ORDER BY total_turnover DESC) as rank_turnover FROM {$popularity_table} WHERE category_id IS NULL AND brand_id IS NOT NULL ) r ON p.id = r.id SET p.brand_rank_by_sales = r.rank_sales, p.brand_rank_by_turnover = r.rank_turnover "); $wpdb->query(" UPDATE {$popularity_table} p INNER JOIN ( SELECT id, RANK() OVER (PARTITION BY category_id ORDER BY total_sales DESC) as rank_sales, RANK() OVER (PARTITION BY category_id ORDER BY total_turnover DESC) as rank_turnover FROM {$popularity_table} WHERE category_id IS NOT NULL AND brand_id IS NOT NULL ) r ON p.id = r.id SET p.brand_rank_in_category_sales = r.rank_sales, p.brand_rank_in_category_turnover = r.rank_turnover "); $wpdb->query(" UPDATE {$popularity_table} p INNER JOIN ( SELECT id, RANK() OVER (PARTITION BY brand_id ORDER BY total_sales DESC) as rank_sales, RANK() OVER (PARTITION BY brand_id ORDER BY total_turnover DESC) as rank_turnover FROM {$popularity_table} WHERE category_id IS NOT NULL AND brand_id IS NOT NULL ) r ON p.id = r.id SET p.category_rank_in_brand_sales = r.rank_sales, p.category_rank_in_brand_turnover = r.rank_turnover "); } else { $wpdb->query(" UPDATE {$popularity_table} p SET p.category_rank_by_sales = ( SELECT COUNT(*) + 1 FROM (SELECT id, category_level, total_sales FROM {$popularity_table} WHERE brand_id IS NULL AND category_id IS NOT NULL) p2 WHERE p2.category_level = p.category_level AND p2.total_sales > p.total_sales ), p.category_rank_by_turnover = ( SELECT COUNT(*) + 1 FROM (SELECT id, category_level, total_turnover FROM {$popularity_table} WHERE brand_id IS NULL AND category_id IS NOT NULL) p3 WHERE p3.category_level = p.category_level AND p3.total_turnover > p.total_turnover ) WHERE p.brand_id IS NULL AND p.category_id IS NOT NULL "); $wpdb->query(" UPDATE {$popularity_table} p SET p.subcategory_rank_in_parent_sales = ( SELECT COUNT(*) + 1 FROM (SELECT id, parent_category_id, total_sales FROM {$popularity_table} WHERE brand_id IS NULL AND parent_category_id IS NOT NULL) p2 WHERE p2.parent_category_id = p.parent_category_id AND p2.total_sales > p.total_sales ), p.subcategory_rank_in_parent_turnover = ( SELECT COUNT(*) + 1 FROM (SELECT id, parent_category_id, total_turnover FROM {$popularity_table} WHERE brand_id IS NULL AND parent_category_id IS NOT NULL) p3 WHERE p3.parent_category_id = p.parent_category_id AND p3.total_turnover > p.total_turnover ) WHERE p.brand_id IS NULL AND p.parent_category_id IS NOT NULL "); $wpdb->query(" UPDATE {$popularity_table} p SET p.brand_rank_by_sales = ( SELECT COUNT(*) + 1 FROM (SELECT id, total_sales FROM {$popularity_table} WHERE category_id IS NULL AND brand_id IS NOT NULL) p2 WHERE p2.total_sales > p.total_sales ), p.brand_rank_by_turnover = ( SELECT COUNT(*) + 1 FROM (SELECT id, total_turnover FROM {$popularity_table} WHERE category_id IS NULL AND brand_id IS NOT NULL) p3 WHERE p3.total_turnover > p.total_turnover ) WHERE p.category_id IS NULL AND p.brand_id IS NOT NULL "); $wpdb->query(" UPDATE {$popularity_table} p SET p.brand_rank_in_category_sales = ( SELECT COUNT(*) + 1 FROM (SELECT id, category_id, total_sales FROM {$popularity_table} WHERE category_id IS NOT NULL AND brand_id IS NOT NULL) p2 WHERE p2.category_id = p.category_id AND p2.total_sales > p.total_sales ), p.brand_rank_in_category_turnover = ( SELECT COUNT(*) + 1 FROM (SELECT id, category_id, total_turnover FROM {$popularity_table} WHERE category_id IS NOT NULL AND brand_id IS NOT NULL) p3 WHERE p3.category_id = p.category_id AND p3.total_turnover > p.total_turnover ) WHERE p.category_id IS NOT NULL AND p.brand_id IS NOT NULL "); $wpdb->query(" UPDATE {$popularity_table} p SET p.category_rank_in_brand_sales = ( SELECT COUNT(*) + 1 FROM (SELECT id, brand_id, total_sales FROM {$popularity_table} WHERE category_id IS NOT NULL AND brand_id IS NOT NULL) p2 WHERE p2.brand_id = p.brand_id AND p2.total_sales > p.total_sales ), p.category_rank_in_brand_turnover = ( SELECT COUNT(*) + 1 FROM (SELECT id, brand_id, total_turnover FROM {$popularity_table} WHERE category_id IS NOT NULL AND brand_id IS NOT NULL) p3 WHERE p3.brand_id = p.brand_id AND p3.total_turnover > p.total_turnover ) WHERE p.category_id IS NOT NULL AND p.brand_id IS NOT NULL "); } if ($wpdb->last_error) { delete_transient('aieo_category_brand_stats_start_time'); delete_transient('aieo_category_brand_stats_lock'); wp_send_json_error(array( 'message' => __('Ranking calculation failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } $total_rows = $wpdb->get_var("SELECT COUNT(*) FROM {$popularity_table}"); $category_count = $wpdb->get_var("SELECT COUNT(*) FROM {$popularity_table} WHERE brand_id IS NULL"); $brand_count = $wpdb->get_var("SELECT COUNT(*) FROM {$popularity_table} WHERE category_id IS NULL"); $combo_count = $wpdb->get_var("SELECT COUNT(*) FROM {$popularity_table} WHERE category_id IS NOT NULL AND brand_id IS NOT NULL"); delete_transient('aieo_category_brand_stats_start_time'); delete_transient('aieo_category_brand_stats_lock'); $this->aieo_ensure_mysql_completion(); wp_send_json_success(array( 'complete' => true, 'message' => sprintf( __('✓ Category/brand popularity: %1$d categories, %2$d brands, %3$d combinations (completed in %4$ds)', 'ai-eshop-optimizer'), $category_count, $brand_count, $combo_count, $elapsed ), 'current' => 100, 'total' => 100 )); } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Category/brand statistics skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'aggregate_customer_std_stats': if ($settings['aggregate_customer_std_stats'] == '1') { $temp_orders_table_name = get_option('aieo_site_orders_table', $wpdb->prefix . 'aieo_temp_orders'); $progress_step = isset($_POST['progress_step']) ? intval($_POST['progress_step']) : 0; $batch_size = 5000; $batch_offset = isset($_POST['batch_offset']) ? intval($_POST['batch_offset']) : 0; if ($progress_step === 0) { $stats_lock = get_transient('aieo_customer_std_stats_lock'); if ($stats_lock && $stats_lock !== $export_id) { wp_send_json_error(array( 'message' => __('Customer standard statistics are already being calculated by another process.', 'ai-eshop-optimizer') )); return; } set_transient('aieo_customer_std_stats_lock', $export_id, 300); self::aieo_register_export_lock_shutdown('aieo_customer_std_stats_lock', $export_id); $total_customers = $wpdb->get_var("SELECT COUNT(DISTINCT EponymousID) FROM {$temp_orders_table_name} WHERE EponymousID != ''"); set_transient('aieo_customer_std_stats_total', $total_customers, 3600); set_transient('aieo_customer_std_stats_start_time', time(), 3600); set_transient('aieo_customer_std_stats_processed', 0, 3600); $existing_indexes = $wpdb->get_results("SHOW INDEX FROM {$temp_orders_table_name}"); $index_names = array(); foreach ($existing_indexes as $idx) { $index_names[] = $idx->Key_name; } if (!in_array('idx_eponymous_id', $index_names)) { $wpdb->query("CREATE INDEX idx_eponymous_id ON {$temp_orders_table_name} (EponymousId)"); } if (!in_array('idx_eponymous_orderid', $index_names)) { $wpdb->query("CREATE INDEX idx_eponymous_orderid ON {$temp_orders_table_name} (OrderId)"); } wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Initializing customer statistics for %d customers...', 'ai-eshop-optimizer'), $total_customers), 'current' => 0, 'total' => $total_customers, 'progress_step' => 1, 'batch_offset' => 0 )); } else if ($progress_step >= 1) { $total_customers = get_transient('aieo_customer_std_stats_total'); $start_time = get_transient('aieo_customer_std_stats_start_time'); @set_time_limit(180); aieo_debug_log("AIEO: Processing customer stats batch offset={$batch_offset}, size={$batch_size} at " . gmdate('H:i:s')); $result = $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_UpdateCustomerCentricStdStats` (%s, %d, %d, %d)", $temp_orders_table_name, 0, $batch_offset, $batch_size )); if ($wpdb->last_error) { aieo_debug_log("AIEO ERROR: " . $wpdb->last_error); delete_transient('aieo_customer_std_stats_total'); delete_transient('aieo_customer_std_stats_start_time'); delete_transient('aieo_customer_std_stats_processed'); delete_transient('aieo_customer_std_stats_lock'); wp_send_json_error(array( 'message' => __('Customer statistics batch failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } $processed = min($batch_offset + $batch_size, $total_customers); set_transient('aieo_customer_std_stats_processed', $processed, 3600); $elapsed_time = time() - $start_time; $percent = $total_customers > 0 ? round(($processed / $total_customers) * 100) : 100; if ($processed >= $total_customers) { $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}aieo_base_order_stats"); delete_transient('aieo_customer_std_stats_total'); delete_transient('aieo_customer_std_stats_start_time'); delete_transient('aieo_customer_std_stats_processed'); delete_transient('aieo_customer_std_stats_lock'); $this->aieo_ensure_mysql_completion(); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ Customer standard statistics: %1$d customers analyzed (completed in %2$ds)', 'ai-eshop-optimizer'), $total_customers, $elapsed_time), 'current' => $total_customers, 'total' => $total_customers )); } else { $next_offset = $processed; wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Processed %1$d / %2$d customers (%3$d%% complete, %4$ds elapsed)', 'ai-eshop-optimizer'), $processed, $total_customers, $percent, $elapsed_time), 'current' => $processed, 'total' => $total_customers, 'progress_step' => $progress_step + 1, 'batch_offset' => $next_offset )); } } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Aggregate customer std stats skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'aggregate_customer_adv_stats': if ($settings['aggregate_customer_adv_stats'] == '1') { $temp_orders_table_name = get_option('aieo_site_orders_table', $wpdb->prefix . 'aieo_temp_orders'); $progress_step = isset($_POST['progress_step']) ? intval($_POST['progress_step']) : 0; $batch_size = 5000; $batch_offset = isset($_POST['batch_offset']) ? intval($_POST['batch_offset']) : 0; if ($progress_step === 0) { $stats_lock = get_transient('aieo_customer_adv_stats_lock'); if ($stats_lock && $stats_lock !== $export_id) { wp_send_json_error(array( 'message' => __('Customer advanced statistics are already being calculated by another process.', 'ai-eshop-optimizer') )); return; } set_transient('aieo_customer_adv_stats_lock', $export_id, 300); self::aieo_register_export_lock_shutdown('aieo_customer_adv_stats_lock', $export_id); $total_customers = $wpdb->get_var("SELECT COUNT(DISTINCT EponymousID) FROM {$temp_orders_table_name} WHERE EponymousID != ''"); set_transient('aieo_customer_adv_stats_total', $total_customers, 3600); set_transient('aieo_customer_adv_stats_start_time', time(), 3600); set_transient('aieo_customer_adv_stats_processed', 0, 3600); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Initializing advanced customer behavior analysis for %d customers...', 'ai-eshop-optimizer'), $total_customers), 'current' => 0, 'total' => $total_customers, 'progress_step' => 1, 'batch_offset' => 0 )); } else if ($progress_step >= 1) { $total_customers = get_transient('aieo_customer_adv_stats_total'); $start_time = get_transient('aieo_customer_adv_stats_start_time'); @set_time_limit(180); aieo_debug_log("AIEO: Processing customer adv stats batch offset={$batch_offset}, size={$batch_size} at " . gmdate('H:i:s')); $result = $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_UpdateCustomerCentricAdvStats` (%s, %d, %d, %d)", $temp_orders_table_name, 0, $batch_offset, $batch_size )); if ($wpdb->last_error) { aieo_debug_log("AIEO ERROR: " . $wpdb->last_error); delete_transient('aieo_customer_adv_stats_total'); delete_transient('aieo_customer_adv_stats_start_time'); delete_transient('aieo_customer_adv_stats_processed'); delete_transient('aieo_customer_adv_stats_lock'); wp_send_json_error(array( 'message' => __('Advanced customer statistics batch failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } $processed = min($batch_offset + $batch_size, $total_customers); set_transient('aieo_customer_adv_stats_processed', $processed, 3600); $elapsed_time = time() - $start_time; $percent = $total_customers > 0 ? round(($processed / $total_customers) * 100) : 100; if ($processed >= $total_customers) { $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}aieo_adv_stats_orders_rn"); $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}aieo_adv_stats_orders_rn_copy"); delete_transient('aieo_customer_adv_stats_total'); delete_transient('aieo_customer_adv_stats_start_time'); delete_transient('aieo_customer_adv_stats_processed'); delete_transient('aieo_customer_adv_stats_lock'); $this->aieo_ensure_mysql_completion(); wp_send_json_success(array( 'complete' => true, 'message' => sprintf(__('✓ Advanced customer behavior analysis: %1$d customers analyzed (completed in %2$ds)', 'ai-eshop-optimizer'), $total_customers, $elapsed_time), 'current' => $total_customers, 'total' => $total_customers )); } else { $next_offset = $processed; wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Analyzed %1$d / %2$d customers (%3$d%% complete, %4$ds elapsed)', 'ai-eshop-optimizer'), $processed, $total_customers, $percent, $elapsed_time), 'current' => $processed, 'total' => $total_customers, 'progress_step' => $progress_step + 1, 'batch_offset' => $next_offset )); } } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Aggregate customer adv stats skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'create_uuids': if ($settings['create_uuids'] == '1') { $temp_orders_table_name = get_option('aieo_site_orders_table', $wpdb->prefix . 'aieo_temp_orders'); $progress_step = isset($_POST['progress_step']) ? intval($_POST['progress_step']) : 0; $uuid_stages = array( array( 'name' => 'O_PP', 'column' => 'O_PP_GraphUUID', 'desc' => 'Order-Parent relationships', 'where' => 'OrderId IS NOT NULL AND ParentProductId IS NOT NULL', 'expr' => "UNHEX(MD5(CONCAT('opp:', CAST(OrderId AS CHAR), ':', CAST(ParentProductId AS CHAR))))" ), array( 'name' => 'O_Prod', 'column' => 'O_Prod_GraphUUID', 'desc' => 'Order-Product relationships', 'where' => 'OrderId IS NOT NULL AND ProductId IS NOT NULL', 'expr' => "UNHEX(MD5(CONCAT('oprod:', CAST(OrderId AS CHAR), ':', CAST(ProductId AS CHAR))))" ), array( 'name' => 'ParentProductId', 'column' => 'PPIdGraphUUID', 'desc' => 'Parent Product UUIDs', 'where' => 'ParentProductId IS NOT NULL', 'expr' => "UNHEX(MD5(CONCAT('pp:', CAST(ParentProductId AS CHAR))))" ), array( 'name' => 'OrderId', 'column' => 'OrderIdGraphUUID', 'desc' => 'Order UUIDs', 'where' => 'OrderId IS NOT NULL', 'expr' => "UNHEX(MD5(CONCAT('ord:', CAST(OrderId AS CHAR))))" ), array( 'name' => 'ProductId', 'column' => 'ProdIdGraphUUID', 'desc' => 'Product UUIDs', 'where' => 'ProductId IS NOT NULL', 'expr' => "UNHEX(MD5(CONCAT('prod:', CAST(ProductId AS CHAR))))" ), array( 'name' => 'EponymousId', 'column' => 'EpoIdGraphUUID', 'desc' => 'Customer UUIDs', 'where' => 'EponymousId IS NOT NULL AND EponymousId <> 0', 'expr' => "UNHEX(MD5(CONCAT('epo:', CAST(EponymousId AS CHAR))))" ), array( 'name' => 'O_Epo', 'column' => 'O_Epo_GraphUUID', 'desc' => 'Order-Customer relationships', 'where' => 'OrderId IS NOT NULL AND EponymousId IS NOT NULL AND EponymousId <> 0', 'expr' => "UNHEX(MD5(CONCAT('oepo:', CAST(OrderId AS CHAR), ':', CAST(EponymousId AS CHAR))))" ), array( 'name' => 'PP_Epo', 'column' => 'PP_Epo_GraphUUID', 'desc' => 'Product-Customer relationships', 'where' => 'ParentProductId IS NOT NULL AND EponymousId IS NOT NULL AND EponymousId <> 0', 'expr' => "UNHEX(MD5(CONCAT('ppe:', CAST(ParentProductId AS CHAR), ':', CAST(EponymousId AS CHAR))))" ), array( 'name' => 'Prod_Epo', 'column' => 'Prod_Epo_GraphUUID', 'desc' => 'Variant-Customer relationships', 'where' => 'ProductId IS NOT NULL AND EponymousId IS NOT NULL AND EponymousId <> 0', 'expr' => "UNHEX(MD5(CONCAT('pe:', CAST(ProductId AS CHAR), ':', CAST(EponymousId AS CHAR))))" ) ); if ($progress_step === 0) { $count = $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table_name}"); if ($count === null) { $this->aieo_log('Failed to count records before UUID generation.', array( 'table' => $temp_orders_table_name, 'db_error' => $wpdb->last_error, )); wp_send_json_error(array( 'message' => __('Unable to initialize UUID generation because record counting failed.', 'ai-eshop-optimizer') )); return; } if ((int) $count === 0) { $stage_count = count($uuid_stages); $this->aieo_log('UUID stage skipped because temp orders table is empty.', array( 'table' => $temp_orders_table_name, )); wp_send_json_success(array( 'complete' => true, 'message' => __('✓ UUID generation skipped: no records detected in temporary orders table.', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0, 'progress_step' => $stage_count + 1 )); return; } set_transient('aieo_uuid_total_records', $count, 7200); set_transient('aieo_uuid_start_time', time(), 7200); set_transient('aieo_uuid_processed', 0, 7200); $stage_count = count($uuid_stages); wp_send_json_success(array( 'complete' => false, 'message' => sprintf(__('Initializing UUID generation for %1$d records (%2$d entity types)...', 'ai-eshop-optimizer'), $count, $stage_count), 'current' => 0, 'total' => $count * $stage_count, 'progress_step' => 1 )); } else if ($progress_step >= 1 && $progress_step <= count($uuid_stages)) { set_time_limit(180); $stage_index = $progress_step - 1; $stage = $uuid_stages[$stage_index]; $stage_count = count($uuid_stages); $total_records = (int) get_transient('aieo_uuid_total_records'); $start_time = (int) get_transient('aieo_uuid_start_time'); $elapsed = time() - $start_time; $column = $stage['column']; $key_where = isset($stage['where']) ? $stage['where'] : '1=1'; $uuid_expression = $stage['expr']; $empty_clause = $this->aieo_uuid_empty_clause($column); $before_null = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table_name} WHERE {$empty_clause} AND {$key_where}"); $non_eligible_missing = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table_name} WHERE {$empty_clause} AND NOT ({$key_where})"); $this->aieo_log('Starting UUID generation stage.', array( 'stage' => $stage['desc'], 'column' => $column, 'records_missing' => $before_null, 'missing_without_required_keys' => $non_eligible_missing, )); $updated = 0; if ($before_null > 0) { $updated = (int) $wpdb->query(" UPDATE {$temp_orders_table_name} SET {$column} = {$uuid_expression} WHERE {$empty_clause} AND {$key_where} "); } if ($wpdb->last_error) { delete_transient('aieo_uuid_total_records'); delete_transient('aieo_uuid_start_time'); delete_transient('aieo_uuid_processed'); wp_send_json_error(array( 'message' => __('UUID generation failed at stage', 'ai-eshop-optimizer') . " '{$stage['desc']}': " . $wpdb->last_error )); return; } $records_updated = max(0, $updated); $after_null = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table_name} WHERE {$empty_clause} AND {$key_where}"); $cumulative = (int) get_transient('aieo_uuid_processed') + $records_updated; set_transient('aieo_uuid_processed', $cumulative, 7200); $percent = $stage_count > 0 ? round(($progress_step / $stage_count) * 100) : 100; $this->aieo_log('Finished UUID generation stage.', array( 'stage' => $stage['desc'], 'updated' => $records_updated, 'remaining_null' => $after_null, 'elapsed_stage_seconds' => $elapsed, )); $stage_message = sprintf(__('Generated %1$s: %2$d records updated (%3$ds elapsed, %4$d%% complete)', 'ai-eshop-optimizer'), $stage['desc'], $records_updated, $elapsed, $percent); if ($before_null === 0 && $non_eligible_missing > 0 && $records_updated === 0) { $stage_message = sprintf(__('%1$s skipped: no rows satisfied required keys (%2$d rows missing source identifiers).', 'ai-eshop-optimizer'), $stage['desc'], $non_eligible_missing); } elseif ($records_updated === 0 && $before_null > 0) { $stage_message = sprintf(__('%1$s: Unable to assign UUIDs because %2$d rows are missing required keys.', 'ai-eshop-optimizer'), $stage['desc'], $before_null); } wp_send_json_success(array( 'complete' => false, 'message' => $stage_message, 'current' => $cumulative, 'total' => $total_records * $stage_count, 'progress_step' => $progress_step + 1 )); } else if ($progress_step === count($uuid_stages) + 1) { set_time_limit(180); $start_time = get_transient('aieo_uuid_start_time'); $elapsed = time() - $start_time; $count = (int) get_transient('aieo_uuid_total_records'); if ($count > 0) { $wpdb->query($wpdb->prepare( "CALL `{$site_prefix}AIEO_GenerateGraphDBUUIDFreqs` (%s)", $temp_orders_table_name )); if ($wpdb->last_error) { delete_transient('aieo_uuid_total_records'); delete_transient('aieo_uuid_start_time'); delete_transient('aieo_uuid_processed'); wp_send_json_error(array( 'message' => __('UUID frequency generation failed:', 'ai-eshop-optimizer') . ' ' . $wpdb->last_error )); return; } } $elapsed = time() - $start_time; delete_transient('aieo_uuid_total_records'); delete_transient('aieo_uuid_start_time'); delete_transient('aieo_uuid_processed'); $this->aieo_ensure_mysql_completion(); $stage_count = count($uuid_stages); $completion_message = ($count > 0) ? sprintf(__('✓ UUIDs created: %1$d records with %2$d UUID types (BINARY(16) format for 2-3x faster performance) and relationship frequencies (completed in %3$ds)', 'ai-eshop-optimizer'), $count, $stage_count, $elapsed) : __('✓ UUID stage completed: no records required UUID processing.', 'ai-eshop-optimizer'); wp_send_json_success(array( 'complete' => true, 'message' => $completion_message, 'current' => $count, 'total' => $count )); } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Create UUIDs skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'create_need_suggestions': if ($settings['create_need_suggestions'] == '1') { require_once AIEO_DIR . 'includes/class-need-suggestions.php'; $generator = AI_eShop_Need_Suggestions::get_instance(); $progress = get_transient('aieo_need_suggestions_progress'); if (!$progress) { $stats = $generator->get_total_products_with_needs(true); $progress = array( 'offset' => 0, 'total' => $stats['parent_count'], 'variation_count' => $stats['variation_count'], 'total_impacted' => $stats['total_impacted'], 'processed' => 0, 'suggestions_created' => 0, 'skipped' => 0, 'failed' => 0, ); } $batch_size = 300; $results = $generator->generate_suggestions_batch($progress['offset'], $batch_size); $progress['offset'] = $results['next_offset']; $progress['processed'] += $results['processed']; $progress['suggestions_created'] += $results['suggestions_created']; $progress['skipped'] += $results['skipped']; $progress['failed'] += $results['failed']; if ($results['complete']) { delete_transient('aieo_need_suggestions_progress'); $variation_count = isset($progress['variation_count']) ? $progress['variation_count'] : 0; $total_impacted = isset($progress['total_impacted']) ? $progress['total_impacted'] : $progress['processed']; $message = sprintf( __('✓ Need suggestions: %1$d products processed, %2$d suggestions created, %3$d skipped, %4$d failed. Total impacted: %5$d products (%6$d variations inherit from parents)', 'ai-eshop-optimizer'), $progress['processed'], $progress['suggestions_created'], $progress['skipped'], $progress['failed'], $total_impacted, $variation_count ); wp_send_json_success(array( 'complete' => true, 'message' => $message, 'current' => $progress['processed'], 'total' => $progress['total'] )); } else { set_transient('aieo_need_suggestions_progress', $progress, 3600); $total_impacted = isset($progress['total_impacted']) ? $progress['total_impacted'] : $progress['total']; $message = sprintf( __('Processing need suggestions: %1$d / %2$d products (%3$d suggestions created) - will impact %4$d total products', 'ai-eshop-optimizer'), $progress['processed'], $progress['total'], $progress['suggestions_created'], $total_impacted ); wp_send_json_success(array( 'complete' => false, 'message' => $message, 'current' => $progress['processed'], 'total' => $progress['total'] )); } } else { wp_send_json_success(array( 'complete' => true, 'message' => __('Need suggestions skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; case 'create_export': if ($settings['create_export'] == '1') { $orders_table_name = $wpdb->prefix . "aieo_" . gmdate('Y_m') . "_orders"; $temp_orders_table = $wpdb->prefix . 'aieo_temp_orders'; if (!$this->aieo_table_exists($temp_orders_table)) { wp_send_json_error(array( 'message' => __('Cannot create export: Orders table does not exist yet. Please process orders first.', 'ai-eshop-optimizer') )); return; } $temp_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table}"); if ($temp_count === 0) { wp_send_json_error(array( 'message' => __('Cannot create export: No orders found in table. Please process orders first.', 'ai-eshop-optimizer') )); return; } $wpdb->query("DROP TABLE IF EXISTS {$orders_table_name}"); $structure_result = $wpdb->query("CREATE TABLE {$orders_table_name} LIKE {$temp_orders_table}"); if ($structure_result === false) { $error = $wpdb->last_error; $this->aieo_log('Failed to clone orders table structure for monthly archive.', array( 'source_table' => $temp_orders_table, 'target_table' => $orders_table_name, 'db_error' => $error, )); wp_send_json_error(array( 'message' => __('Export failed while creating monthly table structure:', 'ai-eshop-optimizer') . ' ' . $error )); return; } $insert_result = $wpdb->query("INSERT INTO {$orders_table_name} SELECT * FROM {$temp_orders_table}"); if ($insert_result === false) { $error = $wpdb->last_error; $this->aieo_log('Failed to copy order data into monthly archive.', array( 'source_table' => $temp_orders_table, 'target_table' => $orders_table_name, 'db_error' => $error, )); wp_send_json_error(array( 'message' => __('Export failed while copying order data:', 'ai-eshop-optimizer') . ' ' . $error )); return; } $count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$orders_table_name}"); $temp_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$temp_orders_table}"); $this->aieo_log('Monthly orders table finalized.', array( 'table' => $orders_table_name, 'rows_copied' => $count, 'temp_rows_retained' => $temp_count, )); $uploads_dir = wp_upload_dir(); $parsed_url = wp_parse_url(home_url()); $domain = isset($parsed_url['host']) ? $parsed_url['host'] : 'site'; $timestamp = gmdate('Ymd_His'); $export_id_fragment = $export_id ? substr(md5($export_id), 0, 8) : wp_generate_password(8, false); $csv_filename = sprintf('%s_exported_orders_hpos_%s_%s.csv', $domain, $timestamp, $export_id_fragment); $csv_path = trailingslashit($uploads_dir['basedir']) . $csv_filename; $csv_result = $this->aieo_create_csv_from_orders_table($orders_table_name, $csv_path); $csv_created = ($csv_result !== false); if (!$csv_created) { $this->aieo_log('CSV export failed after table duplication.', array( 'table' => $orders_table_name, )); } else { $csv_url = trailingslashit($uploads_dir['baseurl']) . $csv_filename; update_option('aieo_last_export_csv', array( 'filename' => $csv_filename, 'path' => $csv_result, 'url' => $csv_url, 'generated_at' => current_time('mysql', true), )); } delete_transient('aieo_export_lock'); delete_transient('aieo_export_settings'); delete_transient('aieo_export_id'); $export_message = $csv_created ? sprintf(__('Export file created: %d records ready', 'ai-eshop-optimizer'), $count) : sprintf(__('Monthly table finalized with %d records, but CSV creation failed. Check error logs.', 'ai-eshop-optimizer'), $count); wp_send_json_success(array( 'complete' => true, 'message' => $export_message, 'current' => $count, 'total' => $count )); } else { delete_transient('aieo_export_lock'); delete_transient('aieo_export_settings'); delete_transient('aieo_export_id'); wp_send_json_success(array( 'complete' => true, 'message' => __('Create export skipped', 'ai-eshop-optimizer'), 'current' => 0, 'total' => 0 )); } break; default: delete_transient('aieo_export_lock'); delete_transient('aieo_export_id'); wp_send_json_error(array('message' => __('Unknown stage:', 'ai-eshop-optimizer') . ' ' . $stage)); return; } } catch (Exception $e) { delete_transient('aieo_export_lock'); delete_transient('aieo_export_id'); wp_send_json_error(array( 'message' => __('Error in stage', 'ai-eshop-optimizer') . ' ' . $stage . ': ' . $e->getMessage() )); } } public function aieo_optimize_database() { check_ajax_referer('aieo_optimize_db', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions', 'ai-eshop-optimizer'))); return; } $optimize_lock = get_transient('aieo_optimize_db_lock'); if ($optimize_lock) { wp_send_json_error(array( 'message' => __('Database optimization is already running. Please wait for it to complete.', 'ai-eshop-optimizer') )); return; } $lock_id = uniqid('opt_', true); set_transient('aieo_optimize_db_lock', $lock_id, 600); try { require_once AIEO_DIR . 'includes/sql/apply_mariadb114_indexes.php'; $db_check = aieo_check_mariadb_version(); $detected_version = isset($db_check['version_number']) ? (string) $db_check['version_number'] : ''; if (isset($db_check['db_type']) && $db_check['db_type'] === 'mysql8') { require_once AIEO_DIR . 'includes/sql/apply_mysql8_indexes.php'; $result = aieo_apply_mysql8_optimized_indexes(); $product_vitals_result= aieo_apply_mysql8_product_vitals_indexes(); $db_label = $detected_version !== '' ? 'MySQL ' . $detected_version : 'MySQL 8'; } else { $result = aieo_apply_mariadb114_optimized_indexes(); $product_vitals_result= aieo_apply_mariadb114_product_vitals_indexes(); $db_label = $detected_version !== '' ? 'MariaDB ' . $detected_version : 'MariaDB 11.4'; } $combined_details = array_merge( isset($result['details']) ? $result['details'] : array(), array('--- Product Vitals Tables ---'), isset($product_vitals_result['details']) ? $product_vitals_result['details'] : array() ); $combined_result = array( 'success' => $result['success'] && $product_vitals_result['success'], 'message' => sprintf(__('%s optimization completed for both orders and product vitals tables', 'ai-eshop-optimizer'), $db_label), 'details' => $combined_details ); delete_transient('aieo_optimize_db_lock'); if ($combined_result['success']) { wp_send_json_success($combined_result); } else { wp_send_json_error($combined_result); } } catch (Exception $e) { delete_transient('aieo_optimize_db_lock'); wp_send_json_error(array( 'message' => __('Database optimization failed:', 'ai-eshop-optimizer') . ' ' . $e->getMessage(), 'details' => array($e->getFile() . ':' . $e->getLine()) )); } } public function aieo_get_index_stats() { check_ajax_referer('aieo_get_indexes', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions', 'ai-eshop-optimizer'))); return; } require_once AIEO_DIR . 'includes/sql/apply_mariadb114_indexes.php'; $indexes = aieo_get_index_statistics(); $html = ''; if (!empty($indexes) && function_exists('rooster_admin_builders') && rooster_admin_builders()) { $rows = array(); foreach ((array) $indexes as $idx) { $rows[] = array( 'index_name' => array('value' => (string) $idx['index_name']), 'columns' => (string) $idx['columns'], 'comment' => '' !== (string) $idx['comment'] ? (string) $idx['comment'] : '-', '_id' => (string) $idx['index_name'], '_s' => array('index_name' => (string) $idx['index_name']), ); } $html = AIEO_Table::local('aieo-dbp-indexes', array( array('key' => 'index_name', 'label' => __('Index Name', 'ai-eshop-optimizer'), 'type' => 'code'), array('key' => 'columns', 'label' => __('Columns', 'ai-eshop-optimizer')), array('key' => 'comment', 'label' => __('Comment', 'ai-eshop-optimizer')), ), $rows, array('size' => 0, 'label' => __('Indexes', 'ai-eshop-optimizer'), 'prefix' => 'aieo-dbp-indexes_', 'sortable' => true)); } wp_send_json_success(array( 'indexes' => $indexes, 'html' => $html, 'icons' => '' !== $html ? \Rooster\Admin\Ui::icons_of($html) : '', )); } public function aieo_fix_collations() { check_ajax_referer('aieo_fix_collation', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(array('message' => __('Insufficient permissions', 'ai-eshop-optimizer'))); return; } $collation_lock = get_transient('aieo_fix_collation_lock'); if ($collation_lock) { wp_send_json_error(array( 'message' => __('Collation fix is already running. Please wait for it to complete.', 'ai-eshop-optimizer') )); return; } $lock_id = uniqid('coll_', true); set_transient('aieo_fix_collation_lock', $lock_id, 900); try { require_once AIEO_DIR . 'includes/sql/apply_mariadb114_indexes.php'; $result = aieo_standardize_database_collations(); delete_transient('aieo_fix_collation_lock'); if ($result['success']) { wp_send_json_success($result); } else { wp_send_json_error($result); } } catch (Exception $e) { delete_transient('aieo_fix_collation_lock'); wp_send_json_error(array( 'message' => __('Collation fix failed:', 'ai-eshop-optimizer') . ' ' . $e->getMessage(), 'details' => array($e->getFile() . ':' . $e->getLine()) )); } } public function aieo_handle_rooster_connector() { if (!current_user_can(AIEO_CAPABILITY)) { wp_die(esc_html__('Unauthorized', 'ai-eshop-optimizer')); } check_admin_referer('aieo_rooster_connector_action', 'aieo_rooster_connector_nonce'); $selected_client = isset($_POST['aieo_rooster_selected_client']) ? absint($_POST['aieo_rooster_selected_client']) : 0; if ($selected_client > 0) { $api_key = get_option('aieo_rooster_api_key'); $hub_url = get_option('aieo_rooster_hub_url', 'https://rooster.systems'); $aieo_fp = class_exists('AIEO_Rooster_Signup') ? AIEO_Rooster_Signup::fp() : ''; $aieo_prev_home = class_exists('AIEO_Rooster_Signup') ? (string) get_option(AIEO_Rooster_Signup::OPT_FP_HOME, '') : ''; $aieo_payload = [ 'client_id' => $selected_client, 'site_url' => home_url(), 'fp' => $aieo_fp, 'label' => get_bloginfo('name'), 'plugin_version' => defined('AIEO_VERSION') ? AIEO_VERSION : '', ]; if (class_exists('AIEO_Rooster_Signup')) { $aieo_payload['src'] = AIEO_Rooster_Signup::source_string(); $aieo_payload['runtime'] = AIEO_Rooster_Signup::runtime_flag(); } if ('' !== $aieo_prev_home && untrailingslashit($aieo_prev_home) !== untrailingslashit(home_url())) { $aieo_payload['previous_site_url'] = $aieo_prev_home; } $response = wp_remote_post(rtrim($hub_url, '/') . '/wp-json/om-hub/v1/content-connector/register-site', [ 'headers' => [ 'X-Content-Connector-Key' => $api_key, 'Content-Type' => 'application/json', 'Accept' => 'application/json', ], 'body' => wp_json_encode($aieo_payload), 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), ]); if (!is_wp_error($response)) { $code = wp_remote_retrieve_response_code($response); $body = json_decode(wp_remote_retrieve_body($response), true); if ($code >= 200 && $code < 300 && !empty($body['success'])) { update_option('aieo_rooster_status', 'connected'); update_option('aieo_rooster_client_id', $selected_client); update_option('aieo_rooster_client_label', $body['label'] ?? ''); $aieo_partner_email = ''; foreach (array('email', 'partner_email', 'client_email', 'contact_email') as $aieo_ek) { if (!empty($body[$aieo_ek]) && is_email($body[$aieo_ek])) { $aieo_partner_email = $body[$aieo_ek]; break; } } if ($aieo_partner_email !== '') { update_option('aieo_rooster_client_email', $aieo_partner_email); } update_option('aieo_rooster_status_message', ''); update_option('aieo_rooster_sites', ''); if (class_exists('AIEO_Rooster_Signup')) { update_option(AIEO_Rooster_Signup::OPT_FP_HOME, home_url(), false); } } else { update_option('aieo_rooster_status', 'error'); update_option('aieo_rooster_status_message', $body['message'] ?? "Registration failed (HTTP {$code})"); } } else { update_option('aieo_rooster_status', 'error'); update_option('aieo_rooster_status_message', $response->get_error_message()); } wp_safe_redirect(wp_get_referer() ?: admin_url('admin.php?page=ai-eshop-optimizer')); exit; } $enabled = isset($_POST['aieo_rooster_enabled']) ? 1 : 0; $api_key = sanitize_text_field(wp_unslash($_POST['aieo_rooster_api_key'] ?? '')); $hub_url = esc_url_raw(wp_unslash($_POST['aieo_rooster_hub_url'] ?? 'https://rooster.systems')); update_option('aieo_rooster_enabled', $enabled); if ('' !== trim(str_replace("\u{2022}", '', $api_key))) { update_option('aieo_rooster_api_key', $api_key); } else { $api_key = (string) get_option('aieo_rooster_api_key', ''); } update_option('aieo_rooster_hub_url', rtrim($hub_url, '/')); if ($enabled && $api_key) { AIEO_Rooster_Signup::authenticate($api_key, $hub_url); } else { update_option('aieo_rooster_status', ''); update_option('aieo_rooster_status_message', ''); update_option('aieo_rooster_sites', ''); } wp_safe_redirect(wp_get_referer() ?: admin_url('admin.php?page=ai-eshop-optimizer')); exit; } public function aieo_check_rooster_credits() { if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json_error(['message' => 'Unauthorized'], 403); } check_ajax_referer('aieo_check_rooster_credits', 'nonce'); $api_key = trim((string) get_option('aieo_rooster_api_key', '')); $hub_url = trim((string) get_option('aieo_rooster_hub_url', 'https://rooster.systems')); if ($api_key === '') { wp_send_json_error([ 'status' => 'not_configured', 'message' => 'Rooster Content Connector token is not configured. Save your token first in the Rooster One Pro.', ], 200); } if ($hub_url === '') { $hub_url = 'https://rooster.systems'; } $response = wp_remote_get(rtrim($hub_url, '/') . '/wp-json/om-portal/v1/anthropic/status', [ 'headers' => [ 'X-OM-API-Key' => $api_key, 'Accept' => 'application/json', ], 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), ]); if (is_wp_error($response)) { wp_send_json_error([ 'status' => 'network_error', 'message' => $response->get_error_message(), ], 200); } $code = wp_remote_retrieve_response_code($response); $body = json_decode(wp_remote_retrieve_body($response), true); if (!is_array($body)) { $body = []; } if ($code === 401 || $code === 403) { wp_send_json_error([ 'status' => 'unauthorized', 'message' => $body['message'] ?? 'Rooster token is invalid or expired.', ], 200); } if ($code === 402) { wp_send_json_error([ 'status' => 'not_subscribed', 'message' => $body['message'] ?? 'This Rooster account does not include AI Credits. Subscribe at rooster.systems → Rooster One.', ], 200); } if ($code < 200 || $code >= 300) { wp_send_json_error([ 'status' => 'error', 'message' => $body['message'] ?? "Rooster returned HTTP {$code}", 'http' => $code, ], 200); } wp_send_json_success($body); } public function aieo_handle_transfer_to_rooster() { if (!current_user_can(AIEO_CAPABILITY)) { wp_die(esc_html__('Unauthorized', 'ai-eshop-optimizer')); } check_admin_referer('aieo_transfer_to_rooster_action', 'aieo_transfer_to_rooster_nonce'); $api_key = get_option('aieo_rooster_api_key'); $hub_url = get_option('aieo_rooster_hub_url', 'https://rooster.systems'); if (!$api_key || !get_option('aieo_rooster_enabled')) { update_option('aieo_rooster_transfer_message', 'Rooster connection not configured.'); wp_safe_redirect(wp_get_referer() ?: admin_url('admin.php?page=ai-eshop-optimizer')); exit; } set_time_limit(300); global $wpdb; $orders_table = $wpdb->prefix . 'aieo_temp_orders'; $base_url = rtrim($hub_url, '/') . '/wp-json/om-hub/v1'; $headers = [ 'X-Content-Connector-Key' => $api_key, 'Content-Type' => 'application/json', 'Accept' => 'application/json', ]; $client_id = get_option('aieo_rooster_client_id', 0); $status_resp = wp_remote_get($base_url . '/content-connector/sync-status?client_id=' . (int) $client_id, [ 'headers' => $headers, 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), ]); $orders_cursor = 0; if (!is_wp_error($status_resp)) { $status = json_decode(wp_remote_retrieve_body($status_resp), true); if (!empty($status['registered'])) { $orders_cursor = (int) ($status['orders_cursor'] ?? 0); } } $batch_size = 500; $total_sent = 0; $max_order_id = $orders_cursor; $offset = 0; $vitals_table = $wpdb->prefix . 'aieo_core_product_vitals'; $select_cols = "o.OrderId, o.OrderStatus, o.OrderDate, o.OrderSize, o.ItemSequence, o.PrimaryQty, o.LineValue, o.OrderItemsTotal, o.Price, o.RegularPrice, o.CurrentPrice, o.OnSale, o.ProductId, o.ParentProductId, o.SKU, o.ParentSKU, COALESCE(o.ItemName, pv.name) AS ItemName, COALESCE(o.ParentItemName, pv.parent_name) AS ParentItemName, o.Stock, COALESCE(o.ImageUrl, pv.image_url) AS ImageUrl, o.ProductUrl, o.CategoryName, o.MenuCategory, o.HeadCategory, o.SubCategory, o.BrandId, o.Brand, o.BrandSlug, o.Vendor, o.EponymousID, o.CustomerName, o.CustomerEmail, o.CustomerLocality, o.TotalCustomerOrders, o.TotalCustomerTurnover, o.AvgOrderSize, o.AvgSpend, o.LastTransactionDate, o.CurrentDBO, o.AvgDBO, o.CustomerSegment, o.TotalItemSales, o.DistinctOrderSales, o.TotalTurnover, o.ProfitabilityIndex, o.Fav1Product, o.Fav2Product, o.Fav3Product, o.Fav1Brand, o.Fav2Brand, o.Fav3Brand, o.Size, o.Color, o.Discontinued, pn.primary_need, pn.secondary_need, pn.other_needs"; while (true) { $rows = $wpdb->get_results($wpdb->prepare( "SELECT {$select_cols} FROM {$orders_table} o LEFT JOIN {$vitals_table} pv ON pv.product_id = o.ProductId LEFT JOIN {$vitals_table} pn ON pn.product_id = COALESCE(NULLIF(o.ParentProductId, 0), o.ProductId) WHERE o.OrderId > %d ORDER BY o.OrderId ASC LIMIT %d OFFSET %d", $orders_cursor, $batch_size, $offset ), ARRAY_A); if (empty($rows)) break; foreach ($rows as &$row) { foreach ([] as $col) { if (isset($row[$col]) && $row[$col] !== null) { $row[$col] = bin2hex($row[$col]); } } $oid = (int) $row['OrderId']; if ($oid > $max_order_id) $max_order_id = $oid; } unset($row); $response = wp_remote_post($base_url . '/content-connector/ingest', [ 'headers' => $headers, 'body' => wp_json_encode(['client_id' => (int) $client_id, 'rows' => $rows]), 'timeout' => 60, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), ]); if (is_wp_error($response)) { update_option('aieo_rooster_transfer_message', "Transfer failed at batch {$offset}: " . $response->get_error_message()); wp_safe_redirect(wp_get_referer() ?: admin_url('admin.php?page=ai-eshop-optimizer')); exit; } $resp_code = wp_remote_retrieve_response_code($response); if ($resp_code < 200 || $resp_code >= 300) { $resp_body = json_decode(wp_remote_retrieve_body($response), true); update_option('aieo_rooster_transfer_message', 'Transfer failed: ' . ($resp_body['message'] ?? "HTTP {$resp_code}")); wp_safe_redirect(wp_get_referer() ?: admin_url('admin.php?page=ai-eshop-optimizer')); exit; } $total_sent += count($rows); $offset += $batch_size; if (count($rows) < $batch_size) break; } wp_remote_post($base_url . '/content-connector/sync-complete', [ 'headers' => $headers, 'body' => wp_json_encode([ 'client_id' => (int) $client_id, 'orders_cursor' => $max_order_id, ]), 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), ]); update_option('aieo_rooster_transfer_message', "Transfer complete: {$total_sent} rows synced (delta from OrderId > {$orders_cursor})."); update_option('aieo_rooster_last_sync', current_time('mysql', true)); wp_safe_redirect(wp_get_referer() ?: admin_url('admin.php?page=ai-eshop-optimizer')); exit; } public function aieo_rooster_transfer_init() { check_ajax_referer('aieo_rooster_transfer_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) wp_send_json_error(__('Unauthorized', 'ai-eshop-optimizer')); $api_key = get_option('aieo_rooster_api_key'); $hub_url = get_option('aieo_rooster_hub_url', 'https://rooster.systems'); $client_id = (int) get_option('aieo_rooster_client_id', 0); $mode = sanitize_text_field(wp_unslash($_POST['mode'] ?? 'delta')); if (!$api_key || !get_option('aieo_rooster_enabled') || !$client_id) { wp_send_json_error(__('Rooster connection not configured.', 'ai-eshop-optimizer')); } global $wpdb; $table = $wpdb->prefix . 'aieo_temp_orders'; $base_url = rtrim($hub_url, '/') . '/wp-json/om-hub/v1'; $headers = ['X-Content-Connector-Key' => $api_key, 'Accept' => 'application/json']; $orders_cursor = 0; if ($mode === 'full') { wp_remote_post($base_url . '/content-connector/reset', [ 'headers' => array_merge($headers, ['Content-Type' => 'application/json']), 'body' => wp_json_encode(['client_id' => $client_id]), 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), ]); } else { $status_resp = wp_remote_get($base_url . '/content-connector/sync-status?client_id=' . $client_id, [ 'headers' => $headers, 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), ]); if (!is_wp_error($status_resp)) { $status = json_decode(wp_remote_retrieve_body($status_resp), true); if (!empty($status['registered'])) { $orders_cursor = (int) ($status['orders_cursor'] ?? 0); } } } $total_rows = (int) $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE OrderId > %d", $orders_cursor )); wp_send_json_success([ 'total_rows' => $total_rows, 'orders_cursor' => $orders_cursor, 'client_id' => $client_id, 'batch_size' => 500, ]); } public function aieo_rooster_transfer_batch() { check_ajax_referer('aieo_rooster_transfer_nonce', 'nonce'); if (!current_user_can(AIEO_CAPABILITY)) wp_send_json_error(__('Unauthorized', 'ai-eshop-optimizer')); $api_key = get_option('aieo_rooster_api_key'); $hub_url = get_option('aieo_rooster_hub_url', 'https://rooster.systems'); $client_id = isset($_POST['client_id']) ? absint($_POST['client_id']) : (int) get_option('aieo_rooster_client_id', 0); $cursor = isset($_POST['cursor']) ? absint($_POST['cursor']) : 0; $batch_size = isset($_POST['batch_size']) ? absint($_POST['batch_size']) : 500; $offset = isset($_POST['offset']) ? absint($_POST['offset']) : 0; global $wpdb; $table = $wpdb->prefix . 'aieo_temp_orders'; $base_url = rtrim($hub_url, '/') . '/wp-json/om-hub/v1'; $vitals_table = $wpdb->prefix . 'aieo_core_product_vitals'; $select_cols = "o.OrderId, o.OrderStatus, o.OrderDate, o.OrderSize, o.ItemSequence, o.PrimaryQty, o.LineValue, o.OrderItemsTotal, o.Price, o.RegularPrice, o.CurrentPrice, o.OnSale, o.ProductId, o.ParentProductId, o.SKU, o.ParentSKU, COALESCE(o.ItemName, pv.name) AS ItemName, COALESCE(o.ParentItemName, pv.parent_name) AS ParentItemName, o.Stock, COALESCE(o.ImageUrl, pv.image_url) AS ImageUrl, o.ProductUrl, o.ERPId, o.CategoryId, o.CategoryName, o.MenuId, o.MenuCategory, o.HeadId, o.HeadCategory, o.SubcatId, o.SubCategory, o.TagId, o.TagName, o.BrandId, o.Brand, o.BrandSlug, o.Vendor, o.VendorId, o.EponymousID, o.EshopCustomerID, o.CustomerName, o.CustomerEmail, o.CustomerPhone, o.CustomerPostalCode, o.CustomerLocality, o.CustomerEshopSince, o.CustomerStoreSince, o.TotalCustomerItems, o.TotalCustomerOrders, o.TotalCustomerTurnover, o.AvgOrderSize, o.MaxOrderSize, o.MinOrderSize, o.AvgSpend, o.MaxSpend, o.MinSpend, o.LastTransactionDate, o.LastOrderSpend, o.CurrentDBO, o.AvgDBO, o.MaxDBO, o.MinDBO, o.CustomerDevice, o.CustomerSegment, o.TotalItemSales, o.DistinctOrderSales, o.TotalTurnover, o.UniqueItemSales, o.ProfitabilityIndex, o.Fav1Product, o.Fav2Product, o.Fav3Product, o.Fav1Brand, o.Fav2Brand, o.Fav3Brand, o.Fav1MainCat, o.Fav2MainCat, o.Fav3MainCat, o.Fav1Size, o.Fav2Size, o.Fav3Size, o.Fav1Color, o.Fav2Color, o.Fav3Color, o.Size, o.Color, o.GASource, o.GAMedium, o.GACampaign, o.Discontinued, o.RoleBasedSpecialPricing, o.HasReciprocal, pn.primary_need, pn.secondary_need, pn.other_needs, pn.primary_need_id, pn.secondary_need_id"; $rows = $wpdb->get_results($wpdb->prepare( "SELECT {$select_cols} FROM {$table} o LEFT JOIN {$vitals_table} pv ON pv.product_id = o.ProductId LEFT JOIN {$vitals_table} pn ON pn.product_id = COALESCE(NULLIF(o.ParentProductId, 0), o.ProductId) WHERE o.OrderId > %d ORDER BY o.OrderId ASC LIMIT %d OFFSET %d", $cursor, $batch_size, $offset ), ARRAY_A); if (empty($rows)) { wp_remote_post($base_url . '/content-connector/sync-complete', [ 'headers' => [ 'X-Content-Connector-Key' => $api_key, 'Content-Type' => 'application/json', 'Accept' => 'application/json', ], 'body' => wp_json_encode(['client_id' => $client_id, 'orders_cursor' => $cursor]), 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), ]); update_option('aieo_rooster_last_sync', current_time('mysql', true)); wp_send_json_success(['done' => true, 'sent' => 0]); } $max_order_id = $cursor; foreach ($rows as &$row) { foreach ($uuid_cols as $col) { if (isset($row[$col]) && $row[$col] !== null) { $row[$col] = bin2hex($row[$col]); } } $oid = (int) $row['OrderId']; if ($oid > $max_order_id) $max_order_id = $oid; } unset($row); $response = wp_remote_post($base_url . '/content-connector/ingest', [ 'headers' => [ 'X-Content-Connector-Key' => $api_key, 'Content-Type' => 'application/json', 'Accept' => 'application/json', ], 'body' => wp_json_encode(['client_id' => $client_id, 'rows' => $rows]), 'timeout' => 60, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), ]); if (is_wp_error($response)) { wp_send_json_error(sprintf(__('Hub error: %s', 'ai-eshop-optimizer'), $response->get_error_message())); } $resp_code = wp_remote_retrieve_response_code($response); if ($resp_code < 200 || $resp_code >= 300) { $resp_body = json_decode(wp_remote_retrieve_body($response), true); wp_send_json_error(sprintf(__('Hub error: %s', 'ai-eshop-optimizer'), $resp_body['message'] ?? sprintf(__('HTTP %d', 'ai-eshop-optimizer'), $resp_code))); } $is_last = count($rows) < $batch_size; if ($is_last) { wp_remote_post($base_url . '/content-connector/sync-complete', [ 'headers' => [ 'X-Content-Connector-Key' => $api_key, 'Content-Type' => 'application/json', 'Accept' => 'application/json', ], 'body' => wp_json_encode(['client_id' => $client_id, 'orders_cursor' => $max_order_id]), 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), ]); update_option('aieo_rooster_last_sync', current_time('mysql', true)); } wp_send_json_success([ 'done' => $is_last, 'sent' => count($rows), 'offset' => $offset + $batch_size, 'max_order' => $max_order_id, ]); } } } if (!class_exists('aieo_UTM_appender')) { class aieo_UTM_appender { public function __construct() { add_filter('post_type_link', array($this, 'aieo_custom_product_title_link'), 10, 2); add_filter('woocommerce_product_add_to_cart_url', array($this, 'aieo_modify_add_to_cart_url'), 10, 2); add_action('wp_enqueue_scripts', array($this, 'enqueue_gtag_script')); } public function enqueue_gtag_script() { $aieo_ga_owns = ! class_exists('AIEO_DMM_Conversions') || 'aieo' === AIEO_DMM_Conversions::get_config()['ga_source']; if (is_product() && get_option('aieo_activate_utm_stats') == 1 && $aieo_ga_owns) { wp_enqueue_script('gtag-event-script', plugin_dir_url(__FILE__) . 'js/ai-eshop-optimizer-googleGTM.js', array(), AIEO_VERSION, true); $script_data = array( 'aieo_google_ga4' => get_option('aieo_google_ga4', 'default_value_if_not_set') ); wp_localize_script('gtag-event-script', 'myLocalizedData', $script_data); } } private function aieo_internal_surface_key($loop_name) { $map = array( 'upsells' => 'product_upsells', 'cross-sells' => 'product_crosssells', 'related' => 'product_related', 'recently-viewed' => 'product_recently_viewed', ); return isset($map[$loop_name]) ? $map[$loop_name] : ''; } private function aieo_internal_promo_args($surface, $anchor_id, $target_id, $loop_pos) { $args = array( 'aieo_ipr' => $surface, 'aieo_ianc' => (int) $anchor_id, 'aieo_iloop' => (int) $loop_pos, ); foreach (array_keys($args) as $k) { if (strpos($k, 'utm_') === 0 || in_array($k, array('gclid', 'fbclid', 'gad_source', 'srsltid'), true)) { unset($args[$k]); } } return $args; } public function aieo_custom_product_title_link($url, $post) { if ($post->post_type !== 'product' || !is_product() || get_option('aieo_activate_utm_stats') != 1) { return $url; } global $woocommerce_loop; $main_product_id = get_queried_object_id(); $target_product_id = $post->ID; $surface = isset($woocommerce_loop['name']) ? $this->aieo_internal_surface_key($woocommerce_loop['name']) : ''; if ('' === $surface) { return $url; } return add_query_arg( $this->aieo_internal_promo_args( $surface, $main_product_id, $target_product_id, isset($woocommerce_loop['loop']) ? $woocommerce_loop['loop'] : 0 ), $url ); } public function aieo_modify_add_to_cart_url($url, $product) { global $woocommerce_loop; if (!is_product() || get_option('aieo_activate_utm_stats') != 1) { return $url; } $main_product_id = get_queried_object_id(); $target_product_id = $product->get_id(); $surface = isset($woocommerce_loop['name']) ? $this->aieo_internal_surface_key($woocommerce_loop['name']) : ''; if ('' === $surface) { return $url; } return add_query_arg( $this->aieo_internal_promo_args( $surface, $main_product_id, $target_product_id, isset($woocommerce_loop['loop']) ? $woocommerce_loop['loop'] : 0 ), $url ); } } if (get_option('aieo_activate_utm_stats') == 1) { new aieo_UTM_appender(); } } if (!class_exists('aieo_display_recommendations')) { class aieo_display_recommendations { private $has_recommendations = false; private $enabled_types = []; private $effective_prices_cache = array(); private function aieo_rail_enabled($reco_id, $legacy_option) { if (class_exists('AIEO_DMM_Product_Display') && method_exists('AIEO_DMM_Product_Display', 'reco_surface_visible')) { return AIEO_DMM_Product_Display::reco_surface_visible($reco_id) ? 1 : 0; } return get_option($legacy_option, 0) == 1 ? 1 : 0; } public function __construct() { add_action('wp_ajax_aieo_get_recommendations', array($this, 'ajax_get_recommendations')); add_action('wp_ajax_nopriv_aieo_get_recommendations', array($this, 'ajax_get_recommendations')); if ( ! defined( 'AIEO_RUNTIME_OWNS_AJAX' ) ) { add_action('wp_ajax_aieo_get_product_bundle', array($this, 'ajax_get_product_bundle')); add_action('wp_ajax_nopriv_aieo_get_product_bundle', array($this, 'ajax_get_product_bundle')); add_action('wp_ajax_aieo_get_recently_viewed_html', array($this, 'ajax_get_recently_viewed_html_only')); add_action('wp_ajax_nopriv_aieo_get_recently_viewed_html', array($this, 'ajax_get_recently_viewed_html_only')); add_action('wp_ajax_aieo_get_products_dtos', array($this, 'ajax_get_products_dtos')); add_action('wp_ajax_nopriv_aieo_get_products_dtos', array($this, 'ajax_get_products_dtos')); } if ( ! defined( 'AIEO_RUNTIME_OWNS_AJAX' ) ) { add_action('wp_ajax_aieo_get_semantic_recos', array($this, 'ajax_get_semantic_recos')); add_action('wp_ajax_nopriv_aieo_get_semantic_recos', array($this, 'ajax_get_semantic_recos')); } else { $self = $this; add_action('init', static function () use ($self) { if (has_action('wp_ajax_nopriv_aieo_get_semantic_recos')) { return; } add_action('wp_ajax_aieo_get_semantic_recos', array($self, 'ajax_get_semantic_recos')); add_action('wp_ajax_nopriv_aieo_get_semantic_recos', array($self, 'ajax_get_semantic_recos')); }, 999); } add_action('wp_ajax_nopriv_aieo_get_recommendations', array($this, 'ajax_get_recommendations')); $display_order = get_option('aieo_recommendations_display_order', 0); $display_order = ($display_order == 1) ? 1 : 0; if ($display_order == 1) { $upsell_priority = 20; $cross_sell_priority = 15; } else { $upsell_priority = 15; $cross_sell_priority = 20; } $display_upsells = $this->aieo_rail_enabled('upsells', 'aieo_display_upsells'); if ($display_upsells == 1) { remove_action('woocommerce_after_single_product_summary', 'woocommerce_upsell_display', 15); add_action('woocommerce_after_single_product_summary', array($this, 'aieo_show_upsell_placeholder'), $upsell_priority); $this->has_recommendations = true; $this->enabled_types[] = 'upsells'; } else { remove_action('woocommerce_after_single_product_summary', 'woocommerce_upsell_display', 15); } $display_cross_sells = $this->aieo_rail_enabled('crosssells', 'aieo_display_cross_sells'); if ($display_cross_sells == 1) { add_action('woocommerce_after_single_product_summary', array($this, 'aieo_show_cross_sell_placeholder'), $cross_sell_priority); $this->has_recommendations = true; $this->enabled_types[] = 'crosssells'; } $display_related = $this->aieo_rail_enabled('related', 'aieo_display_related'); if ($display_related == 1) { remove_action('woocommerce_after_single_product_summary', 'woocommerce_output_related_products', 20); add_action('woocommerce_after_single_product_summary', array($this, 'aieo_show_related_placeholder'), 20); $this->has_recommendations = true; $this->enabled_types[] = 'related'; } else { remove_action('woocommerce_after_single_product_summary', 'woocommerce_output_related_products', 20); } $display_recently_viewed_products = $this->aieo_rail_enabled('recently_viewed', 'aieo_display_recently_viewed_products'); if ($display_recently_viewed_products == 1) { remove_action('woocommerce_after_single_product_summary', 'woocommerce_output_related_products', 30); add_action('woocommerce_after_single_product_summary', array($this, 'aieo_show_recently_viewed_placeholder'), 30); $this->has_recommendations = true; $this->enabled_types[] = 'recently_viewed'; add_action('template_redirect', array($this, 'aieo_track_recently_viewed'), 25); } else { remove_action('woocommerce_after_single_product_summary', 'woocommerce_output_related_products', 30); } $display_same_needs = $this->aieo_rail_enabled('same_needs', 'aieo_display_same_needs'); if ($display_same_needs == 1) { add_action('woocommerce_after_single_product_summary', array($this, 'aieo_show_same_needs_placeholder'), 10); $this->has_recommendations = true; $this->enabled_types[] = 'same_needs'; } $semantic_needs_loader = ( class_exists('AIEO_Semantic_Reco_Source') && class_exists('AIEO_DMM_Product_Display') && AIEO_DMM_Product_Display::reco_surface_visible('semantic_related') && AIEO_Semantic_Reco_Source::available() ); if ($this->has_recommendations || $semantic_needs_loader) { add_action('wp_enqueue_scripts', array($this, 'enqueue_lazy_loader_script')); } add_action('aieo_recos_rail_rendering', array($this, 'enqueue_for_rail')); } public function enqueue_for_rail() { if (wp_script_is('aieo-lazy-recommendations', 'enqueued')) { return; } $this->enqueue_loader(); } public function enqueue_lazy_loader_script() { if (!is_singular('product')) { return; } $this->enqueue_loader(); } private function enqueue_loader() { $_aieo_recos_js_path = AIEO_DIR . 'assets/js/lazy-recommendations-v7.js'; $_aieo_recos_js_ver = file_exists($_aieo_recos_js_path) ? (string) filemtime($_aieo_recos_js_path) : AIEO_VERSION; wp_enqueue_script( 'aieo-lazy-recommendations', AIEO_URI . 'assets/js/lazy-recommendations-v7.js', array('jquery'), $_aieo_recos_js_ver, true ); $reco_css_path = AIEO_DIR . 'assets/css/aieo-recommendations.css'; $reco_css_ver = file_exists($reco_css_path) ? (string) filemtime($reco_css_path) : AIEO_VERSION; wp_enqueue_style( 'aieo-recommendations', AIEO_URI . 'assets/css/aieo-recommendations.css', array(), $reco_css_ver ); $card_recipe = self::rail_card_recipe(); if ('archive' === $card_recipe) { if (method_exists('AIEO_Card', 'load_style')) { AIEO_Card::load_style(); } else { AIEO_Card::load(); } $registry = WP_Block_Type_Registry::get_instance(); foreach (array('woocommerce/product-template', 'woocommerce/product-image', 'woocommerce/product-price', 'core/post-title') as $block_name) { $block_type = $registry->get_registered($block_name); foreach ($block_type ? (array) $block_type->style_handles : array() as $handle) { wp_enqueue_style($handle); } } } global $post; $bundle_enabled = false; $bundle_mode = 'rec_only'; if (class_exists('AIEO_DMM_Settings')) { $bundle_mode = AIEO_DMM_Settings::get('bundle_mode', 'full'); $bundle_enabled = in_array($bundle_mode, array('full', 'rec_only'), true); } wp_localize_script('aieo-lazy-recommendations', 'aieoRecommendationsConfig', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aieo_recommendations'), 'productId' => is_singular('product') ? (int) get_queried_object_id() : 0, 'types' => $this->enabled_types, 'bundleEnabled' => $bundle_enabled, 'bundleMode' => $bundle_mode, 'rvLocalTtlMs' => 30 * 60 * 1000, 'rvLocalKey' => 'aieo_rv_cache_v4', 'sectionTitles' => self::section_titles(), 'badgePlacements' => class_exists('AIEO_DMM_Badges') ? AIEO_DMM_Badges::get_settings()['placements'] : array( 'archive' => array('location' => 'below_favourite', 'shape' => 'circle'), 'pdp' => array('location' => 'top_left', 'shape' => 'chip'), 'recos' => array('location' => 'inline', 'shape' => 'chip'), 'side_cart' => array('location' => 'inline', 'shape' => 'chip'), ), 'badgeSizes' => class_exists('AIEO_DMM_Badges') ? AIEO_DMM_Badges::get_settings()['sizes'] : array(), 'cardShowDesc' => class_exists('AIEO_DMM_Product_Card') ? (bool) AIEO_DMM_Product_Card::display_flag('desc_show') : false, 'cardShowRating' => class_exists('AIEO_DMM_Product_Card') ? (bool) AIEO_DMM_Product_Card::display_flag('rating_show') : true, 'cardRecipe' => $card_recipe, 'cardHeart' => ('archive' === $card_recipe && class_exists('AIEO_Wishlist') && method_exists('AIEO_Wishlist', 'archive_heart_template')) ? AIEO_Wishlist::instance()->archive_heart_template() : '', )); } private static function section_titles() { return array( 'upsells' => __('Up-sell Recommendations', 'ai-eshop-optimizer'), 'crosssells' => __('Cross-sell Recommendations', 'ai-eshop-optimizer'), 'related' => __('Related Products Recommendations', 'ai-eshop-optimizer'), 'same_needs' => __('Products for the Same Needs', 'ai-eshop-optimizer'), 'recently_viewed' => __('Recently Viewed Products Recommendations', 'ai-eshop-optimizer'), 'semantic_related' => ( class_exists('AIEO_DMM_Settings') && '' !== (string) AIEO_DMM_Settings::get('semantic.related_block_title', '') ) ? (string) AIEO_DMM_Settings::get('semantic.related_block_title', '') : __('You may also like', 'ai-eshop-optimizer'), ); } public static function rail_card_recipe() { if (!class_exists('AIEO_Card') || !AIEO_Card::enabled()) { return ''; } if (class_exists('AIEO_DMM_Product_Card') && 'aieo' === AIEO_DMM_Product_Card::design_source()) { return ''; } return 'archive'; } public function ajax_get_recommendations() { check_ajax_referer('aieo_recommendations', 'nonce'); $site_locale = get_locale(); $switched_locale = false; if ( function_exists( 'switch_to_locale' ) && determine_locale() !== $site_locale ) { switch_to_locale( $site_locale ); $switched_locale = true; } if ( is_textdomain_loaded( 'ai-eshop-optimizer' ) ) { unload_textdomain( 'ai-eshop-optimizer' ); } $lang_dir = AIEO_DIR . 'languages/'; $locales_to_try = array( $site_locale ); if ( strpos( $site_locale, '_' ) !== false ) { $locales_to_try[] = substr( $site_locale, 0, strpos( $site_locale, '_' ) ); } foreach ( $locales_to_try as $try_locale ) { $mofile = $lang_dir . 'ai-eshop-optimizer-' . $try_locale . '.mo'; if ( file_exists( $mofile ) ) { load_textdomain( 'ai-eshop-optimizer', $mofile ); break; } } $type = isset($_POST['type']) ? sanitize_text_field(wp_unslash($_POST['type'])) : ''; $product_id = isset($_POST['product_id']) ? absint(wp_unslash($_POST['product_id'])) : 0; if (empty($type) || empty($product_id)) { wp_send_json_error(array('message' => __('Invalid parameters', 'ai-eshop-optimizer'))); } $cacheable_types = array('upsells', 'crosssells', 'related', 'same_needs'); if (in_array($type, $cacheable_types, true) && class_exists('AIEO_Recommendations_Cache')) { $cached = AIEO_Recommendations_Cache::get($product_id, $type); if ($cached !== false && isset($cached['html'])) { wp_send_json_success(array( 'html' => $cached['html'], 'cached' => true, )); } } global $product, $post; $post = get_post($product_id); if (!$post || $post->post_type !== 'product') { wp_send_json_error(array('message' => __('Product not found', 'ai-eshop-optimizer'))); } setup_postdata($post); $product = wc_get_product($product_id); $html = ''; switch ($type) { case 'upsells': $html = $this->get_upsell_html(); break; case 'crosssells': $html = $this->get_cross_sell_html(); break; case 'related': $html = $this->get_related_html(); break; case 'recently_viewed': $html = $this->get_recently_viewed_html(); break; case 'same_needs': $html = $this->get_same_needs_html(); break; } wp_reset_postdata(); if (in_array($type, $cacheable_types, true) && class_exists('AIEO_Recommendations_Cache') && !empty($html)) { AIEO_Recommendations_Cache::set($product_id, $type, array('html' => $html)); } if ( $switched_locale && function_exists( 'restore_previous_locale' ) ) { restore_previous_locale(); } wp_send_json_success(array('html' => $html)); } public function ajax_get_product_bundle() { check_ajax_referer('aieo_recommendations', 'nonce'); $product_id = isset($_POST['product_id']) ? absint(wp_unslash($_POST['product_id'])) : 0; if ($product_id <= 0) { wp_send_json_error(array('message' => __('Invalid product id', 'ai-eshop-optimizer'))); } $mode = 'full'; if (class_exists('AIEO_DMM_Settings')) { $stored = AIEO_DMM_Settings::get('bundle_mode', 'full'); if ($stored === 'rec_only' || $stored === 'full') { $mode = $stored; } } $relation_type = $mode === 'rec_only' ? 'bundle_rec_only_v10' : 'bundle_full_v10'; if (class_exists('AIEO_Reco_Experiment') && method_exists('AIEO_Reco_Experiment', 'cache_suffix')) { $relation_type .= AIEO_Reco_Experiment::cache_suffix(); } $role_hash = $this->aieo_bundle_role_hash(); $site_locale = function_exists('get_locale') ? get_locale() : 'en_US'; if (class_exists('AIEO_Recommendations_Cache')) { $cached = AIEO_Recommendations_Cache::get($product_id, $relation_type); if ($cached !== false && is_array($cached) && isset($cached['role_hash']) && $cached['role_hash'] === $role_hash && isset($cached['locale']) && $cached['locale'] === $site_locale) { $cached['cached'] = true; $cached['rv_hint'] = $this->aieo_bundle_rv_hint(); wp_send_json_success($cached); } } $switched_locale = false; if (function_exists('switch_to_locale') && determine_locale() !== $site_locale) { switch_to_locale($site_locale); $switched_locale = true; } if (is_textdomain_loaded('ai-eshop-optimizer')) { unload_textdomain('ai-eshop-optimizer'); } $lang_dir = AIEO_DIR . 'languages/'; $locales_to_try = array($site_locale); if (strpos($site_locale, '_') !== false) { $locales_to_try[] = substr($site_locale, 0, strpos($site_locale, '_')); } foreach ($locales_to_try as $try_locale) { $mofile = $lang_dir . 'ai-eshop-optimizer-' . $try_locale . '.mo'; if (file_exists($mofile)) { load_textdomain('ai-eshop-optimizer', $mofile); break; } } global $product, $post; $post = get_post($product_id); if (!$post || $post->post_type !== 'product') { wp_send_json_error(array('message' => __('Product not found', 'ai-eshop-optimizer'))); } setup_postdata($post); $product = wc_get_product($product_id); if (!$product) { wp_send_json_error(array('message' => __('Product not loaded', 'ai-eshop-optimizer'))); } $this->aieo_bundle_preload_related_prices($product, $mode); $upsells = $this->get_upsell_dtos(); $crosssells = $this->get_cross_sell_dtos(); $needs = $this->get_same_needs_dtos(); $related = $this->get_related_dtos(); $semantic = $this->get_semantic_related_dtos(); $variations = null; if ($mode === 'full') { $variations = $this->aieo_bundle_variations_payload($product); } wp_reset_postdata(); $seed_dto = $this->build_product_dto($product); if (is_array($seed_dto)) { $seed_dto['short_description'] = (string) $product->get_short_description(); } $attribute_labels = $this->aieo_bundle_attribute_labels($product); $attribute_taxonomy_labels = $this->aieo_bundle_attribute_taxonomy_labels($product); $blob = array( 'product_id' => $product_id, 'mode' => $mode, 'role_hash' => $role_hash, 'locale' => $site_locale, 'seed' => $seed_dto, 'upsells' => $upsells, 'crosssells' => $crosssells, 'needs' => $needs, 'related' => $related, 'semantic_related' => $semantic, 'variations' => $variations, 'attribute_labels' => $attribute_labels, 'attribute_taxonomy_labels' => $attribute_taxonomy_labels, 'built_at' => time(), ); if (class_exists('AIEO_Recommendations_Cache')) { AIEO_Recommendations_Cache::set($product_id, $relation_type, $blob); } if ($switched_locale && function_exists('restore_previous_locale')) { restore_previous_locale(); } $blob['cached'] = false; $blob['rv_hint'] = $this->aieo_bundle_rv_hint(); wp_send_json_success($blob); } public function ajax_get_recently_viewed_html_only() { check_ajax_referer('aieo_recommendations', 'nonce'); $products = $this->get_recently_viewed_dtos(); wp_send_json_success(array( 'products' => $products, 'ids' => $this->aieo_bundle_rv_hint(), 'built_at' => time(), )); } public function ajax_get_semantic_recos() { check_ajax_referer('aieo_recommendations', 'nonce'); $product_id = isset($_POST['product_id']) ? absint(wp_unslash($_POST['product_id'])) : 0; if ($product_id <= 0) { wp_send_json_error(array('message' => __('Invalid product id', 'ai-eshop-optimizer'))); } if (class_exists('AIEO_DMM_Product_Display') && !AIEO_DMM_Product_Display::reco_surface_visible('semantic_related')) { wp_send_json_success(array('products' => array(), 'source' => 'disabled', 'built_at' => time())); } $limit = 6; if (class_exists('AIEO_DMM_Settings')) { $limit = (int) AIEO_DMM_Settings::get('semantic.related_block_limit', 6); } $limit = max(1, min(24, $limit)); $relation_type = 'semantic_recos_v1_r' . $this->aieo_bundle_role_hash(); if (class_exists('AIEO_Recommendations_Cache')) { $cached = AIEO_Recommendations_Cache::get($product_id, $relation_type); if (false !== $cached && is_array($cached) && isset($cached['products'])) { $cached['cached'] = true; wp_send_json_success($cached); } } $ids = (array) apply_filters('aieo_semantic_reco_ids', AIEO_Semantic_Reco_Source::ids_for($product_id, $limit * 2), $product_id, $limit); $ids = array_values(array_filter(array_map('intval', $ids))); if (empty($ids)) { wp_send_json_success(array('products' => array(), 'source' => 'none', 'built_at' => time())); } $ids = $this->filter_instock_products($ids); $ids = array_slice($ids, 0, $limit); if (empty($ids)) { wp_send_json_success(array('products' => array(), 'source' => 'out_of_stock', 'built_at' => time())); } $payload = array( 'products' => $this->build_product_dtos($ids), 'source' => (string) apply_filters('aieo_semantic_reco_source', 'local-artifact', $product_id), 'built_at' => time(), ); if (class_exists('AIEO_Recommendations_Cache')) { AIEO_Recommendations_Cache::set($product_id, $relation_type, $payload, HOUR_IN_SECONDS); } $payload['cached'] = false; wp_send_json_success($payload); } public function ajax_get_products_dtos() { check_ajax_referer('aieo_recommendations', 'nonce'); $ids = array(); if (isset($_POST['ids'])) { $raw = is_array($_POST['ids']) ? array_map('absint', wp_unslash($_POST['ids'])) : sanitize_text_field(wp_unslash($_POST['ids'])); if (is_string($raw)) { $raw = array_map('trim', explode(',', $raw)); } if (is_array($raw)) { foreach ($raw as $id) { $id = (int) $id; if ($id > 0) { $ids[] = $id; } } } } $ids = array_values(array_unique($ids)); if (count($ids) > 50) { $ids = array_slice($ids, 0, 50); } $products = $this->build_product_dtos($ids); wp_send_json_success(array( 'products' => $products, 'built_at' => time(), )); } private function aieo_bundle_role_hash() { $user_id = get_current_user_id(); $roles = array('guest'); if ($user_id > 0) { $u = get_userdata($user_id); if ($u && !empty($u->roles) && is_array($u->roles)) { $roles = $u->roles; } } sort($roles); $loyalty = array(); if ($user_id > 0 && class_exists('AIEO_DMM_Pricing_Engine')) { $loyalty = AIEO_DMM_Pricing_Engine::instance()->get_loyalty_class_ids_for_user($user_id); sort($loyalty); } return substr(md5(implode(',', $roles) . '|' . implode(',', $loyalty)), 0, 12); } private function aieo_bundle_rv_hint() { $cookie = isset($_COOKIE['woocommerce_recently_viewed']) ? sanitize_text_field(wp_unslash($_COOKIE['woocommerce_recently_viewed'])) : ''; if ($cookie === '') { return array(); } $ids = array_filter(array_map('absint', explode('|', $cookie))); return array_slice(array_values(array_reverse($ids)), 0, 15); } private function aieo_bundle_preload_related_prices($product, $mode) { if (!class_exists('AIEO_DMM_Pricing_Engine') || !is_a($product, 'WC_Product')) { return; } $ids = array(); $collect = function ($arr) use (&$ids) { if (is_array($arr)) { $ids = array_merge($ids, $arr); } }; $collect($product->get_upsell_ids()); $collect($product->get_cross_sell_ids()); if (function_exists('wc_get_related_products')) { $collect(wc_get_related_products($product->get_id(), 12)); } if ($mode === 'full' && $product->is_type('variable')) { $collect($product->get_children()); } $ids = array_filter(array_map('intval', array_unique($ids))); if (!empty($ids)) { AIEO_DMM_Pricing_Engine::instance()->preload_role_prices($ids); } } private function aieo_bundle_variations_payload($product) { if (!is_a($product, 'WC_Product') || !$product->is_type('variable')) { return null; } if (!is_callable(array($product, 'get_available_variations'))) { return null; } $data = $product->get_available_variations(); return apply_filters('aieo_bundle_variations_payload', $data, $product); } private function aieo_bundle_attribute_labels($product) { $out = array(); if (!is_a($product, 'WC_Product') || !$product->is_type('variable')) { return $out; } $attributes = $product->get_variation_attributes(); if (empty($attributes) || !is_array($attributes)) { return $out; } foreach ($attributes as $attribute_name => $values) { $key = 'attribute_' . sanitize_title($attribute_name); $map = array(); if (taxonomy_exists($attribute_name)) { foreach ((array) $values as $slug) { $term = get_term_by('slug', $slug, $attribute_name); $raw = ($term && !is_wp_error($term)) ? $term->name : $slug; $map[$slug] = html_entity_decode((string) $raw, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } } else { foreach ((array) $values as $val) { $map[$val] = html_entity_decode((string) $val, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } } $out[$key] = $map; } return $out; } private function aieo_bundle_attribute_taxonomy_labels($product) { $out = array(); if (!is_a($product, 'WC_Product') || !$product->is_type('variable')) { return $out; } $attributes = $product->get_variation_attributes(); if (empty($attributes) || !is_array($attributes)) { return $out; } foreach ($attributes as $attribute_name => $values) { $key = 'attribute_' . sanitize_title($attribute_name); $out[$key] = (string) wc_attribute_label((string) $attribute_name); } return $out; } private function has_instock_products($product_ids) { if (empty($product_ids) || !is_array($product_ids)) { return false; } foreach ($product_ids as $product_id) { $product = wc_get_product($product_id); if ($product && $product->is_in_stock()) { return true; } } return false; } private function filter_instock_products($product_ids) { if (empty($product_ids) || !is_array($product_ids)) { return array(); } $instock_ids = array(); foreach ($product_ids as $product_id) { $product = wc_get_product($product_id); if ($product && $product->is_in_stock()) { $instock_ids[] = $product_id; } } return (array) apply_filters('aieo_reco_filter_candidates', $instock_ids, $product_ids); } private function output_placeholder($id, $type, $title) { if ('recently_viewed' === $type) { echo ''; return; } if ('archive' === self::rail_card_recipe()) { $titles = self::section_titles(); $rating = !class_exists('AIEO_DMM_Product_Card') || AIEO_DMM_Product_Card::display_flag('rating_show'); $outline = '
  • ' . '
    ' . '

    ' . ($rating ? '
    ' : '') . '
    ' . '
  • '; echo '
    ' . '

    ' . esc_html(isset($titles[$type]) ? $titles[$type] : trim((string) $title)) . '

    ' . '' . '
    '; return; } $placeholder_img = function_exists('wc_placeholder_img_src') ? wc_placeholder_img_src('woocommerce_thumbnail') : ''; ?> post_type !== 'product') { return; } $upsells = get_post_meta($post->ID, '_upsell_ids', true); if (empty($upsells) || !is_array($upsells)) { return; } if (!$this->has_instock_products($upsells)) { return; } $this->output_placeholder('aieo-up-sells-placeholder', 'upsells', __('Up-sell Recommendations ', 'ai-eshop-optimizer')); } public function aieo_show_cross_sell_placeholder() { global $post; if (!$post || $post->post_type !== 'product') { return; } $crosssells = get_post_meta($post->ID, '_crosssell_ids', true); if (empty($crosssells) || !is_array($crosssells)) { return; } if (!$this->has_instock_products($crosssells)) { return; } $this->output_placeholder('aieo-cross-sells-placeholder', 'crosssells', __('Cross-sell Recommendations ', 'ai-eshop-optimizer')); } public function aieo_show_related_placeholder() { global $product; if (!$product || !is_a($product, 'WC_Product')) { return; } $related_ids = wc_get_related_products($product->get_id(), 6); if (empty($related_ids)) { return; } if (!$this->has_instock_products($related_ids)) { return; } $this->output_placeholder('aieo-related-placeholder', 'related', __('Related Products Recommendations ', 'ai-eshop-optimizer')); } public function aieo_show_recently_viewed_placeholder() { $this->output_placeholder('aieo-recently-viewed-placeholder', 'recently_viewed', __('Recently Viewed Products Recommendations', 'ai-eshop-optimizer')); } public function aieo_track_recently_viewed() { if (!is_singular('product')) { return; } global $post; if (!$post || $post->post_type !== 'product') { return; } $viewed = array(); if (!empty($_COOKIE['woocommerce_recently_viewed'])) { $raw = wp_unslash($_COOKIE['woocommerce_recently_viewed']); $viewed = wp_parse_id_list(explode('|', (string) $raw)); } $viewed = array_values(array_diff($viewed, array($post->ID))); array_unshift($viewed, (int) $post->ID); $viewed = array_slice(array_unique(array_map('absint', $viewed)), 0, 15); if (function_exists('wc_setcookie')) { wc_setcookie('woocommerce_recently_viewed', implode('|', $viewed)); } } public function aieo_show_same_needs_placeholder() { global $post, $wpdb; if (!$post || $post->post_type !== 'product') { return; } $product_id = $post->ID; $parent_id = wp_get_post_parent_id($product_id); if ($parent_id) { $product_id = $parent_id; } $suggestions_table = $wpdb->prefix . 'aieo_product_need_suggestions'; $suggestions_json = $wpdb->get_var($wpdb->prepare( "SELECT suggested_products FROM {$suggestions_table} WHERE product_id = %d LIMIT 1", $product_id )); if (!$suggestions_json) { return; } $suggested_ids = json_decode($suggestions_json, true); if (empty($suggested_ids) || !is_array($suggested_ids)) { return; } if (!$this->has_instock_products($suggested_ids)) { return; } $this->output_placeholder('aieo-same-needs-placeholder', 'same_needs', __('Products for the Same Needs', 'ai-eshop-optimizer')); } private function get_recently_viewed_html() { $viewed_products = array(); if (!empty($_COOKIE['woocommerce_recently_viewed'])) { $recently_viewed = sanitize_text_field(wp_unslash($_COOKIE['woocommerce_recently_viewed'])); $viewed_products = array_reverse(array_filter(array_map('absint', explode('|', $recently_viewed)))); } if (empty($viewed_products)) { return ''; } $args = array( 'post_type' => 'product', 'posts_per_page' => 5, 'no_found_rows' => 1, 'post_status' => 'publish', 'post__in' => $viewed_products, 'orderby' => 'post__in', ); $products = new WP_Query($args); if (!$products->have_posts()) { wp_reset_postdata(); return ''; } ob_start(); echo ''; wp_reset_postdata(); return ob_get_clean(); } private function build_product_dto($product) { if (!$product || !is_a($product, 'WC_Product')) { return null; } $image_id = (int) $product->get_image_id(); if ($image_id <= 0) { $parent_id = (int) $product->get_parent_id(); if ($parent_id > 0) { $parent = wc_get_product($parent_id); if ($parent) { $image_id = (int) $parent->get_image_id(); } } } $image = array( 'src' => '', 'srcset' => '', 'sizes' => '', 'width' => 0, 'height' => 0, 'alt' => $product->get_name(), ); if ($image_id > 0) { $src = wp_get_attachment_image_src($image_id, 'woocommerce_thumbnail'); if ($src) { $image['src'] = (string) $src[0]; $image['width'] = (int) $src[1]; $image['height'] = (int) $src[2]; } $srcset = wp_get_attachment_image_srcset($image_id, 'woocommerce_thumbnail'); if ($srcset) { $image['srcset'] = (string) $srcset; } $sizes = wp_get_attachment_image_sizes($image_id, 'woocommerce_thumbnail'); if ($sizes) { $image['sizes'] = (string) $sizes; } $alt = (string) get_post_meta($image_id, '_wp_attachment_image_alt', true); if ($alt !== '') { $image['alt'] = $alt; } } elseif (function_exists('wc_placeholder_img_src')) { $image['src'] = (string) wc_placeholder_img_src('woocommerce_thumbnail'); } $price_html = $this->build_clean_price_html($product); $role_badge = $this->build_role_badge_dto($product); $on_sale = (bool) $product->is_on_sale(); if ($role_badge !== null) { $on_sale = false; } $badge_dtos = array(); if (class_exists('AIEO_DMM_Badge_Renderer')) { $badge_dtos = AIEO_DMM_Badge_Renderer::dto_for_product($product, 'recos'); } $short_desc = wp_trim_words(wp_strip_all_tags((string) $product->get_short_description()), 25, '…'); $rating_html = ''; if (class_exists('AIEO_Ratings')) { $ratings = AIEO_Ratings::instance(); if ($ratings && method_exists($ratings, 'render_aggregate_html')) { $rating_html = (string) $ratings->render_aggregate_html((int) $product->get_id()); } } return array( 'id' => (int) $product->get_id(), 'name' => $product->get_name(), 'permalink' => $product->get_permalink(), 'sku' => (string) $product->get_sku(), 'type' => (string) $product->get_type(), 'on_sale' => $on_sale, 'in_stock' => (bool) $product->is_in_stock(), 'image' => $image, 'price_html' => $price_html, 'role_badge' => $role_badge, 'badges' => $badge_dtos, 'short_description' => $short_desc, 'rating_html' => $rating_html, ); } private function lookup_variable_price_range($product) { if (!apply_filters('aieo_dto_use_meta_lookup_price_range', true, $product)) { return null; } if (is_user_logged_in()) { return null; } if (function_exists('wc_tax_enabled') && wc_tax_enabled()) { return null; } if (!$this->variation_price_filters_are_safe()) { return null; } global $wpdb; $table = isset($wpdb->wc_product_meta_lookup) ? $wpdb->wc_product_meta_lookup : $wpdb->prefix . 'wc_product_meta_lookup'; $pid = (int) $product->get_id(); $row = $wpdb->get_row( $wpdb->prepare("SELECT min_price, max_price FROM {$table} WHERE product_id = %d", $pid), ARRAY_A ); if (!$row || null === $row['min_price'] || null === $row['max_price']) { return null; } $min = (float) $row['min_price']; $max = (float) $row['max_price']; if ($min <= 0 && $max <= 0) { return null; } if ($max < $min) { return null; } $children = $product->get_visible_children(); if (empty($children)) { return null; } $ids = implode(',', array_map('intval', $children)); $rows = $wpdb->get_col( "SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id IN ({$ids}) AND meta_key = '_price' AND meta_value <> ''" ); if (empty($rows) || count($rows) !== count($children)) { return null; } $prices = array(); foreach ($rows as $raw) { $prices[] = (float) wc_format_decimal((string) $raw); } $child_min = min($prices); $child_max = max($prices); if (abs($child_min - $min) > 0.005 || abs($child_max - $max) > 0.005) { return null; } return array($min, $max); } private function variation_price_filters_are_safe() { global $wp_filter; $hooks = array( 'woocommerce_variation_prices_price', 'woocommerce_variation_prices_regular_price', 'woocommerce_variation_prices_sale_price', 'woocommerce_variation_prices', ); $allowed = apply_filters('aieo_dto_price_filter_allowlist', array( 'AIEO_DMM_Role_Pricing::filter_role_price', )); foreach ($hooks as $hook) { if (empty($wp_filter[$hook]) || empty($wp_filter[$hook]->callbacks)) { continue; } foreach ($wp_filter[$hook]->callbacks as $callbacks) { foreach ($callbacks as $callback) { $fn = $callback['function']; if (is_string($fn)) { $name = $fn; } elseif (is_array($fn) && 2 === count($fn) && is_string($fn[1])) { $target = $fn[0]; $name = (is_object($target) ? get_class($target) : (string) $target) . '::' . $fn[1]; } else { return false; } if (!in_array($name, $allowed, true)) { return false; } } } } return true; } private function build_clean_price_html($product) { if (!$product || !is_a($product, 'WC_Product')) { return ''; } if ($product->is_type('variable') && method_exists($product, 'get_variation_price')) { $range = $this->lookup_variable_price_range($product); if (null !== $range) { list($min, $max) = $range; } else { $min = (float) $product->get_variation_price('min'); $max = (float) $product->get_variation_price('max'); } if ($min > 0 && $max > 0 && $min !== $max && function_exists('wc_format_price_range')) { return (string) wc_format_price_range($min, $max); } return (string) wc_price($min); } $pid = (int) $product->get_id(); if (isset($this->effective_prices_cache[$pid])) { $row = $this->effective_prices_cache[$pid]; $base = (float) ($row['base_price'] ?? 0); $sale = isset($row['sale_price']) ? (float) $row['sale_price'] : null; if ($base > 0) { if ($sale !== null && $sale > 0 && $sale < $base) { return (string) wc_format_sale_price(wc_price($base), wc_price($sale)); } return (string) wc_price($base); } } if ($product->is_on_sale() && function_exists('wc_format_sale_price')) { $regular = $product->get_regular_price(); $sale = $product->get_sale_price(); if ($regular !== '' && $sale !== '' && $regular !== $sale) { return (string) wc_format_sale_price(wc_price($regular), wc_price($sale)); } } return (string) wc_price($product->get_price()); } private function build_role_badge_dto($product) { if (!$product || !is_a($product, 'WC_Product')) { return null; } if (!class_exists('AIEO_DMM_Settings')) { return null; } if (!class_exists('OM_Role_Price_Handler')) { return null; } $parent_id = $product->is_type('variation') ? (int) $product->get_parent_id() : (int) $product->get_id(); if ($parent_id <= 0) { return null; } $handler = OM_Role_Price_Handler::instance(); if (!method_exists($handler, 'parent_has_role_pricing')) { return null; } if (!$handler->parent_has_role_pricing($parent_id)) { return null; } $is_anon = function_exists('is_user_logged_in') ? !is_user_logged_in() : false; $show_tip = $is_anon && (bool) AIEO_DMM_Settings::get('show_login_message', false); $icon = (string) AIEO_DMM_Settings::get('mini_cart_badge_icon', ''); $text = (string) AIEO_DMM_Settings::get('mini_cart_badge_text', ''); if ($icon === '' && $text === '') { $text = 'PRO'; } $alt = ''; if ($show_tip) { $alt = (string) AIEO_DMM_Settings::get('mini_cart_badge_alt', ''); if ($alt === '') { $alt = (string) AIEO_DMM_Settings::get('login_message_text', ''); } if ($alt !== '' && function_exists('wc_get_page_permalink')) { $login_url = wc_get_page_permalink('myaccount'); if (!$login_url) { $login_url = wp_login_url(); } $alt = str_replace('{login_url}', esc_url($login_url), $alt); } } return array( 'icon' => $icon, 'text' => $text, 'alt' => $alt, ); } private function build_product_dtos($product_ids) { $out = array(); if (empty($product_ids) || !is_array($product_ids)) { return $out; } $this->preload_effective_prices(array_map('intval', $product_ids)); foreach ($product_ids as $id) { $id = (int) $id; if ($id <= 0) { continue; } $product = wc_get_product($id); $dto = $this->build_product_dto($product); if ($dto !== null) { $out[] = $dto; } } return $out; } private function preload_effective_prices(array $product_ids) { if (empty($product_ids)) { return; } if (!class_exists('AIEO_Effective_Prices')) { return; } if (!AIEO_Effective_Prices::is_enabled()) { return; } $needed = array(); foreach ($product_ids as $id) { $id = (int) $id; if ($id > 0 && !isset($this->effective_prices_cache[$id])) { $needed[] = $id; } } if (empty($needed)) { return; } $audience = class_exists('AIEO_Identity_Bridge') ? AIEO_Identity_Bridge::audience_key_for_current_user() : 'guest'; $rows = AIEO_Effective_Prices::get_for_batch($needed, $audience); if (is_array($rows)) { foreach ($rows as $pid => $row) { $this->effective_prices_cache[(int) $pid] = $row; } } } private function get_upsell_dtos() { global $post; if (!$post) { return array(); } $upsells = $this->collect_reco_ids($post->ID, 'upsells', '_upsell_ids'); if (empty($upsells)) { return array(); } $instock = $this->filter_instock_products($upsells); return $this->build_product_dtos($instock); } private function get_cross_sell_dtos() { global $post; if (!$post) { return array(); } $crosssells = $this->collect_reco_ids($post->ID, 'crosssells', '_crosssell_ids'); if (empty($crosssells)) { return array(); } $instock = $this->filter_instock_products($crosssells); return $this->build_product_dtos($instock); } private function get_related_dtos() { global $product; if (!$product || !is_a($product, 'WC_Product')) { return array(); } $related_ids = $this->collect_reco_ids((int) $product->get_id(), 'related', null); if (empty($related_ids)) { return array(); } $instock = $this->filter_instock_products($related_ids); if (empty($instock)) { return array(); } shuffle($instock); return $this->build_product_dtos($instock); } private function get_semantic_related_dtos() { global $product; if (!$product || !is_a($product, 'WC_Product')) { return array(); } if (!class_exists('AIEO_Semantic_Reco_Source')) { return array(); } if (class_exists('AIEO_DMM_Product_Display') && !AIEO_DMM_Product_Display::reco_surface_visible('semantic_related')) { return array(); } $limit = class_exists('AIEO_DMM_Settings') ? (int) AIEO_DMM_Settings::get('semantic.related_block_limit', 6) : 6; $limit = max(1, min(24, $limit)); $pid = (int) $product->get_id(); $ids = AIEO_Semantic_Reco_Source::ids_for($pid, $limit * 2); $ids = (array) apply_filters('aieo_semantic_reco_ids', $ids, $pid, $limit); $ids = array_values(array_filter(array_map('intval', $ids))); if (empty($ids)) { return array(); } $ids = $this->filter_instock_products($ids); $ids = array_slice($ids, 0, $limit); return empty($ids) ? array() : $this->build_product_dtos($ids); } private function collect_reco_ids($source_pid, $surface, $legacy_meta_key) { $limit = 8; if (class_exists('AIEO_Effective_Recos') && AIEO_Effective_Recos::is_enabled()) { $audience = class_exists('AIEO_Identity_Bridge') ? AIEO_Identity_Bridge::audience_key_for_current_user() : 'guest'; $ids = AIEO_Effective_Recos::get_top($source_pid, $surface, $audience, $limit); if (!empty($ids)) { return $ids; } } if ($legacy_meta_key === null) { if (function_exists('wc_get_related_products')) { return (array) wc_get_related_products($source_pid, $limit); } return array(); } $raw = get_post_meta($source_pid, $legacy_meta_key, true); return is_array($raw) ? array_map('intval', $raw) : array(); } private function get_same_needs_dtos() { global $post; if (!$post || !$post->ID) { return array(); } require_once plugin_dir_path(__FILE__) . 'class-need-suggestions.php'; $need_suggestions = AI_eShop_Need_Suggestions::get_instance(); $suggested_ids = $need_suggestions->get_suggestions($post->ID); if (empty($suggested_ids)) { return array(); } $instock = $this->filter_instock_products($suggested_ids); if (empty($instock)) { return array(); } $instock = array_slice($instock, 0, 6); return $this->build_product_dtos($instock); } private function get_recently_viewed_dtos() { $viewed = array(); if (!empty($_COOKIE['woocommerce_recently_viewed'])) { $raw = sanitize_text_field(wp_unslash($_COOKIE['woocommerce_recently_viewed'])); $viewed = array_values(array_reverse(array_filter(array_map('absint', explode('|', $raw))))); } if (empty($viewed)) { return array(); } $viewed = array_slice($viewed, 0, 15); return $this->build_product_dtos($viewed); } private function get_related_html() { global $product; if (!$product) { return ''; } $related_ids = wc_get_related_products($product->get_id()); if (empty($related_ids)) { return ''; } $instock_related = $this->filter_instock_products($related_ids); if (empty($instock_related)) { return ''; } $args = array( 'post_type' => 'product', 'posts_per_page' => -1, 'post__in' => $instock_related, 'orderby' => 'rand' ); $products = new WP_Query($args); if (!$products->have_posts()) { wp_reset_postdata(); return ''; } ob_start(); echo ''; wp_reset_postdata(); return ob_get_clean(); } private function get_upsell_html() { global $post; $upsells = get_post_meta($post->ID, '_upsell_ids', true); if (empty($upsells)) { return ''; } $instock_upsells = $this->filter_instock_products($upsells); if (empty($instock_upsells)) { return ''; } $args = array( 'post_type' => 'product', 'posts_per_page' => -1, 'post__in' => $instock_upsells, 'orderby' => 'post__in', ); $products = new WP_Query($args); if (!$products->have_posts()) { wp_reset_postdata(); return ''; } ob_start(); echo ''; wp_reset_postdata(); return ob_get_clean(); } private function get_cross_sell_html() { global $post; $crosssells = get_post_meta($post->ID, '_crosssell_ids', true); if (empty($crosssells)) { return ''; } $instock_crosssells = $this->filter_instock_products($crosssells); if (empty($instock_crosssells)) { return ''; } $args = array( 'post_type' => 'product', 'posts_per_page' => -1, 'post__in' => $instock_crosssells, 'orderby' => 'post__in', ); $products = new WP_Query($args); if (!$products->have_posts()) { wp_reset_postdata(); return ''; } ob_start(); echo ''; wp_reset_postdata(); return ob_get_clean(); } private function get_same_needs_html() { global $post; if (!$post || !$post->ID) { return ''; } require_once plugin_dir_path(__FILE__) . 'class-need-suggestions.php'; $need_suggestions = AI_eShop_Need_Suggestions::get_instance(); $suggested_ids = $need_suggestions->get_suggestions($post->ID); if (empty($suggested_ids)) { return ''; } $instock_suggested = $this->filter_instock_products($suggested_ids); if (empty($instock_suggested)) { return ''; } $args = array( 'post_type' => 'product', 'posts_per_page' => 6, 'post__in' => $instock_suggested, 'orderby' => 'post__in', 'post_status' => 'publish', ); $products = new WP_Query($args); if (!$products->have_posts()) { wp_reset_postdata(); return ''; } ob_start(); echo ''; wp_reset_postdata(); return ob_get_clean(); } } }