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 '';
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)) . '
'
. ''
. str_repeat($outline, 4)
. '
'
. '';
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();
}
}
}