array('label' => __('Detected groups', 'ai-eshop-optimizer'), 'icon' => 'search'),
'manual' => array('label' => __('Group by hand', 'ai-eshop-optimizer'), 'icon' => 'pencil-line'),
'attributes' => array('label' => __('Attributes', 'ai-eshop-optimizer'), 'icon' => 'tags'),
'log' => array('label' => __('Merge history', 'ai-eshop-optimizer'), 'icon' => 'scroll-text'),
);
}
public function get_tab_arg() { return 'ds_section'; }
public function get_pro_promo() {
return array(
'title' => __('Duplicate products', 'ai-eshop-optimizer'),
'description' => __('Finds the groups of products that are really one product sold as several, once per colour or size, shows the evidence behind each group, and merges the group into a single variable product without losing the sales history: past orders keep counting towards the merged product.', 'ai-eshop-optimizer'),
'bullets' => array(
__('Reads the difference out of the product titles and names it, by colour, by size, or by both', 'ai-eshop-optimizer'),
__('Refuses to merge any group whose difference it cannot name, rather than guessing', 'ai-eshop-optimizer'),
__('Always previews first: the product that would be created, each variation with its SKU, price and stock, and how many past order lines are involved', 'ai-eshop-optimizer'),
__('Originals are retired, never deleted, so every merge can be undone exactly as it was', 'ai-eshop-optimizer'),
__('Groups you know about yourself can be assembled by hand, with a running selection that survives searching again', 'ai-eshop-optimizer'),
),
);
}
const ENGINE_MARKER = 'AIEO_Duplicate_Product_Merge';
private function engine_present() {
return class_exists(self::ENGINE_MARKER);
}
private function require_pro() {
if (!self::is_aieo_pro_user()) {
wp_send_json_error(array('message' => __('This feature requires the Pro version.', 'ai-eshop-optimizer')));
}
if (!$this->engine_present()) {
wp_send_json_error(array('message' => __('Rooster One Pro is not installed. The merge engine ships with the Pro plugin — install and activate it and this screen comes alive.', 'ai-eshop-optimizer')));
}
}
private function render_engine_missing_notice() {
echo '
'
. esc_html__('Rooster One Pro is not installed.', 'ai-eshop-optimizer')
. ' '
. esc_html__('The duplicate-products engine — group detection, the axis resolver and the reversible merge — ships with the Rooster One Pro plugin. Install and activate it and this screen comes alive without further setup.', 'ai-eshop-optimizer')
. '
';
if (class_exists('AIEO_One_Installer')) {
AIEO_One_Installer::render('duplicate_simples');
}
}
public function register_hooks() {
add_action('wp_ajax_aieo_dmm_dupsimple_save', array($this, 'ajax_save_settings'));
add_action('wp_ajax_aieo_dmm_dupsimple_preview', array($this, 'ajax_preview'));
add_action('wp_ajax_aieo_dmm_dupsimple_merge', array($this, 'ajax_merge'));
add_action('wp_ajax_aieo_dmm_dupsimple_undo', array($this, 'ajax_undo'));
add_action('wp_ajax_aieo_dmm_dupsimple_purge', array($this, 'ajax_purge'));
add_action('wp_ajax_aieo_dmm_dupsimple_search', array($this, 'ajax_search'));
add_action('wp_ajax_aieo_dmm_dupsimple_bulk_preview', array($this, 'ajax_bulk_preview'));
add_action('wp_ajax_aieo_dmm_dupsimple_bulk_merge', array($this, 'ajax_bulk_merge'));
add_action('wp_ajax_aieo_dmm_dupsimple_attrs', array($this, 'ajax_save_attributes'));
add_action('wp_ajax_aieo_dmm_dupsimple_propose', array($this, 'ajax_propose_attribute'));
add_action('wp_ajax_aieo_dmm_dupsimple_create_attr', array($this, 'ajax_create_attribute'));
add_action('wp_ajax_aieo_dmm_dupsimple_wcfeature', array($this, 'ajax_enable_wc_feature'));
}
public function ajax_propose_attribute() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$ids = $this->posted_ids();
$group = AIEO_Duplicate_Product_Groups::group_for($ids);
if (!$group) {
wp_send_json_error(array('message' => __('At least two products are needed.', 'ai-eshop-optimizer')));
}
$proposal = AIEO_Attribute_Axis_Resolver::propose_terms($group['members']);
wp_send_json_success(array(
'html' => $this->render_propose_html($group, $proposal),
));
}
private function render_propose_html(array $group, array $proposal) {
$by_id = array();
foreach ($group['members'] as $m) { $by_id[$m['id']] = $m; }
$ids_key = substr(md5(implode(',', wp_list_pluck($group['members'], 'id'))), 0, 10);
list($open, $close) = AIEO_DMM_UI::card(array(
'anchor' => 'duplicate-simples-propose-' . $ids_key,
'title' => __('Nothing in your catalogue names this difference yet.', 'ai-eshop-optimizer'),
'level' => 3,
'class' => 'aieo-ds-propose-card',
'flush' => !empty($proposal['ok']),
));
ob_start();
echo $open;
echo '' . esc_html__('Create an attribute for it. These products then become variations of it, and any future group using the same words is recognised on its own.', 'ai-eshop-optimizer') . '
';
if (!$proposal['ok']) {
echo \Rooster\Admin\Ui::notice(esc_html(sprintf(
__('These products cannot be told apart automatically: %s. Group them by hand instead.', 'ai-eshop-optimizer'),
$proposal['reason']
)), 'destructive', array('inline' => true));
} else {
$rows = array();
foreach ((array) $proposal['terms'] as $id => $term) {
$title = isset($by_id[$id]) ? (string) $by_id[$id]['title'] : (string) $id;
$edit = get_edit_post_link((int) $id, 'raw');
$rows[] = array(
'_name' => $title,
'product' => array('html' => $edit ? '' . esc_html($title) . ' ' : esc_html($title)),
'value' => array('html' => \Rooster\Admin\Ui::input('', (string) $term, array(
'id' => 'aieo-ds-term-' . $ids_key . '-' . (int) $id,
'class' => 'aieo-ds-term',
'medium' => true,
'aria-label' => __('Its value', 'ai-eshop-optimizer') . ': ' . $title,
'attrs' => array('data-id' => (string) $id),
))),
);
}
\Rooster\Admin\Markup::out(\Rooster\Admin\Ui::table(
array(
array('key' => 'product', 'label' => __('Product', 'ai-eshop-optimizer'), 'type' => 'html'),
array('key' => 'value', 'label' => __('Its value', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'lg'),
),
$rows,
array('caption' => __('Its value', 'ai-eshop-optimizer'))
));
$kinds = array();
foreach (AIEO_Attribute_Axis_Resolver::kinds() as $kind_key => $kind) {
$kinds[$kind_key] = (string) $kind['label'];
}
echo ''
. AIEO_Kit::row(__('Attribute name', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::input('', '', array('id' => 'aieo-ds-attr-name-' . $ids_key, 'class' => 'aieo-ds-attr-name', 'medium' => true, 'placeholder' => __('e.g. Shade', 'ai-eshop-optimizer'))), '', array('id' => 'aieo-ds-attr-name-' . $ids_key))
. AIEO_Kit::row(__('It describes', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::select('', $kinds, '', array('id' => 'aieo-ds-attr-kind-' . $ids_key, 'attrs' => array('data-aieo-ds-attr-kind' => ''))), '', array('id' => 'aieo-ds-attr-kind-' . $ids_key))
. '
';
echo ''
. AIEO_Kit::button(__('Create attribute', 'ai-eshop-optimizer'), array('variant' => 'primary', 'class' => 'aieo-ds-create-attr', 'attrs' => array('data-ids' => implode(',', wp_list_pluck($group['members'], 'id')))))
. ' '
. '
';
}
echo $close;
return (string) ob_get_clean();
}
public function ajax_create_attribute() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$label = isset($_POST['label']) ? sanitize_text_field(wp_unslash($_POST['label'])) : '';
$kind = isset($_POST['kind']) ? sanitize_key(wp_unslash($_POST['kind'])) : '';
$raw = isset($_POST['terms']) ? wp_unslash($_POST['terms']) : '';
$decoded = json_decode((string) $raw, true);
if (!is_array($decoded)) {
wp_send_json_error(array('message' => __('Could not read the terms.', 'ai-eshop-optimizer')));
}
$terms = array_map('sanitize_text_field', array_values($decoded));
$result = AIEO_Attribute_Axis_Resolver::create_attribute($label, $kind, $terms);
if (is_wp_error($result)) {
wp_send_json_error(array('message' => $result->get_error_message()));
}
AIEO_Duplicate_Product_Groups::flush();
wp_send_json_success(array(
'message' => sprintf(
__('Created "%1$s" with %2$s terms, and switched it on. Reload to see this group resolved.', 'ai-eshop-optimizer'),
$label,
number_format_i18n($result['created'])
),
'taxonomy' => $result['taxonomy'],
));
}
public function ajax_save_attributes() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$raw = isset($_POST['map']) ? wp_unslash($_POST['map']) : '';
$decoded = json_decode((string) $raw, true);
if (!is_array($decoded)) {
wp_send_json_error(array('message' => __('Could not read the selection.', 'ai-eshop-optimizer')));
}
$map = array();
foreach ($decoded as $taxonomy => $axis) {
$map[sanitize_key($taxonomy)] = sanitize_key($axis);
}
AIEO_Attribute_Axis_Resolver::set_attribute_map($map);
AIEO_Duplicate_Product_Groups::flush();
wp_send_json_success(array(
'message' => __('Saved. The detected groups are rebuilt with this vocabulary next time you open them.', 'ai-eshop-optimizer'),
));
}
private function posted_groups() {
$raw = isset($_POST['groups']) ? wp_unslash($_POST['groups']) : '';
$decoded = json_decode((string) $raw, true);
if (!is_array($decoded)) { return array(); }
$out = array();
foreach ($decoded as $list) {
$ids = array_values(array_unique(array_filter(array_map('intval', explode(',', (string) $list)))));
if (count($ids) >= 2) { $out[] = $ids; }
}
return array_slice($out, 0, 50);
}
public function ajax_bulk_preview() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$groups = $this->posted_groups();
if (!$groups) {
wp_send_json_error(array('message' => __('Select at least one group.', 'ai-eshop-optimizer')));
}
$html = '';
$mergeable = array();
$refused = 0;
rooster_admin_builders();
foreach ($groups as $ids) {
$preview = AIEO_Duplicate_Product_Merge::preview($ids);
if (is_wp_error($preview)) {
$refused++;
$html .= \Rooster\Admin\Ui::notice('' . esc_html__('Skipped:', 'ai-eshop-optimizer') . ' ' . esc_html($preview->get_error_message()), 'warning', array('inline' => true));
continue;
}
$mergeable[] = implode(',', $ids);
$html .= $this->render_preview_html($preview, false);
}
wp_send_json_success(array(
'html' => $html,
'icons' => \Rooster\Admin\Ui::icons_of($html),
'mergeable' => $mergeable,
'refused' => $refused,
));
}
public function ajax_bulk_merge() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$groups = $this->posted_groups();
if (!$groups) {
wp_send_json_error(array('message' => __('Nothing to merge.', 'ai-eshop-optimizer')));
}
$merged = 0;
$errors = array();
foreach ($groups as $ids) {
$result = AIEO_Duplicate_Product_Merge::merge($ids);
if (is_wp_error($result)) { $errors[] = $result->get_error_message(); continue; }
$merged++;
}
wp_send_json_success(array(
'merged' => $merged,
'errors' => $errors,
'message' => sprintf(
_n('Merged %1$s group. %2$s could not be merged.', 'Merged %1$s groups. %2$s could not be merged.', $merged, 'ai-eshop-optimizer'),
number_format_i18n($merged),
number_format_i18n(count($errors))
),
));
}
private function posted_ids() {
$raw = isset($_POST['ids']) ? sanitize_text_field(wp_unslash($_POST['ids'])) : '';
$ids = array_filter(array_map('intval', explode(',', $raw)));
return array_values(array_unique($ids));
}
public function ajax_preview() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$primary = isset($_POST['primary_id']) ? (int) $_POST['primary_id'] : 0;
$preview = AIEO_Duplicate_Product_Merge::preview($this->posted_ids(), array(
'primary_id' => $primary,
'values' => $this->posted_values(),
));
if (is_wp_error($preview)) {
wp_send_json_error(array('message' => $preview->get_error_message()));
}
rooster_admin_builders();
$html = $this->render_preview_html($preview);
wp_send_json_success(array(
'html' => $html,
'icons' => \Rooster\Admin\Ui::icons_of($html),
));
}
private function render_preview_html(array $p, $with_confirm = true) {
list($open, $close) = AIEO_DMM_UI::card(array(
'anchor' => 'duplicate-simples-preview-' . (int) $p['primary_id'],
'title' => (string) $p['parent_title'],
'description' => __('is the product that will be created.', 'ai-eshop-optimizer'),
'level' => 3,
'class' => 'aieo-ds-preview-card',
));
ob_start();
echo $open;
?>
0) : ?>
true));
?>
';
foreach ($p['warnings'] as $w) {
$aieo_ds_lost .= '' . esc_html($w) . ' ';
}
echo \Rooster\Admin\Ui::notice('', 'warning', array('inline' => true, 'title' => __('Merging this group would lose something:', 'ai-eshop-optimizer'), 'detail' => $aieo_ds_lost . ''));
?>
' . wp_kses_post(wp_trim_words($p['description']['text'], 120, '…')) . '');
endif; ?>
$v) {
$aieo_ds_rows[] = array(
'_id' => (string) ($i + 1),
'variation' => implode(' / ', array_values($v['attributes'])),
'sku' => $v['sku'] !== '' ? $v['sku'] : '—',
'price' => $v['price'] !== '' ? $v['price'] : '—',
'stock' => $v['stock'] === null ? '—' : (string) $v['stock'],
);
}
\Rooster\Admin\Markup::out( \Rooster\Admin\Ui::table(
array(
array('key' => 'variation', 'label' => __('Variation', 'ai-eshop-optimizer')),
array('key' => 'sku', 'label' => __('SKU', 'ai-eshop-optimizer'), 'type' => 'code'),
array('key' => 'price', 'label' => __('Price', 'ai-eshop-optimizer'), 'type' => 'num'),
array('key' => 'stock', 'label' => __('Stock', 'ai-eshop-optimizer'), 'type' => 'num'),
),
$aieo_ds_rows,
array(
'id' => 'aieo-ds-preview-' . (int) $p['primary_id'],
'caption' => sprintf( __('Variations of %s', 'ai-eshop-optimizer'), $p['parent_title']),
'cards' => true,
)
) );
?>
require_pro();
$primary = isset($_POST['primary_id']) ? (int) $_POST['primary_id'] : 0;
$result = AIEO_Duplicate_Product_Merge::merge($this->posted_ids(), array(
'primary_id' => $primary,
'values' => $this->posted_values(),
));
if (is_wp_error($result)) {
wp_send_json_error(array('message' => $result->get_error_message()));
}
wp_send_json_success(array(
'message' => sprintf(
__('Merged %1$s products. %2$s', 'ai-eshop-optimizer'),
number_format_i18n(count($result['retired'])),
'' . esc_html__('Open the new product', 'ai-eshop-optimizer') . ' '
),
'parent_id' => $result['parent_id'],
));
}
public function ajax_undo() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$run_id = isset($_POST['run_id']) ? (int) $_POST['run_id'] : 0;
global $wpdb;
$was = (string) $wpdb->get_var($wpdb->prepare(
'SELECT status FROM ' . AIEO_Duplicate_Product_Merge::log_table() . ' WHERE id = %d',
$run_id
));
$result = AIEO_Duplicate_Product_Merge::undo($run_id);
if (is_wp_error($result)) {
wp_send_json_error(array('message' => $result->get_error_message()));
}
wp_send_json_success(array(
'message' => (AIEO_Duplicate_Product_Merge::STATUS_REPOINTED === $was)
? __('Done. The sales and views are back on the retired products; the merged product is unchanged.', 'ai-eshop-optimizer')
: __('Undone. The original products are back and the merged product has been removed.', 'ai-eshop-optimizer'),
));
}
public function ajax_purge() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$run_id = isset($_POST['run_id']) ? (int) $_POST['run_id'] : 0;
$confirm = isset($_POST['confirm']) && '1' === (string) $_POST['confirm'];
$plan = AIEO_Duplicate_Product_Merge::purge_preview($run_id);
if (is_wp_error($plan)) {
wp_send_json_error(array('message' => $plan->get_error_message()));
}
if (!empty($plan['blockers'])) {
wp_send_json_error(array(
'message' => implode(' ', $plan['blockers']),
'blockers' => $plan['blockers'],
));
}
if (!$confirm) {
wp_send_json_success(array(
'preview' => true,
'plan' => $plan,
'message' => sprintf(
__('%1$d retired products will be deleted. First: %2$d reviews, %3$d wishlist entries, %4$d saved cart lines and %5$d cross-sell lists move to the merged product, and each old address gets a 301. This cannot be undone.', 'ai-eshop-optimizer'),
count($plan['members']),
(int) $plan['reviews'],
(int) $plan['wishlist'],
(int) $plan['cart_items'],
(int) $plan['crosssell']
),
));
}
$res = AIEO_Duplicate_Product_Merge::purge($run_id, false);
if (is_wp_error($res)) {
wp_send_json_error(array('message' => $res->get_error_message()));
}
wp_send_json_success(array(
'done' => true,
'result' => $res,
'message' => sprintf(
__('%1$d retired products deleted. %2$d reviews and %3$d redirects moved to the merged product.', 'ai-eshop-optimizer'),
(int) $res['deleted'],
(int) $res['reviews'],
(int) $res['redirects']
),
));
}
public function ajax_search() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$term = isset($_POST['term']) ? sanitize_text_field(wp_unslash($_POST['term'])) : '';
if (strlen($term) < 2) { self::send_search_answer(array()); }
$query = new WP_Query(array(
'post_type' => 'product',
'post_status' => 'publish',
'posts_per_page' => 30,
's' => $term,
'fields' => 'ids',
'tax_query' => array(array(
'taxonomy' => 'product_type',
'field' => 'slug',
'terms' => 'simple',
)),
));
$results = array();
foreach ($query->posts as $id) {
$product = wc_get_product($id);
if (!$product) { continue; }
$results[] = array(
'id' => (int) $id,
'title' => $product->get_name(),
'sku' => (string) $product->get_sku(),
'price' => (string) $product->get_regular_price('edit'),
'edit' => (string) get_edit_post_link((int) $id, 'raw'),
);
}
self::send_search_answer($results);
}
private static function send_search_answer(array $results) {
$table = self::manual_results_table($results);
wp_send_json_success(array(
'results' => $results,
'table' => $table,
'icons' => '' !== $table ? \Rooster\Admin\Ui::icons_of($table) : '',
));
}
private static function manual_columns() {
$columns = array(
array('key' => 'product', 'label' => __('Product', 'ai-eshop-optimizer'), 'type' => 'text', 'sort' => 'none', 'sort_type' => 'text', 'sort_url' => '#dsm_orderby=product'),
array('key' => 'sku', 'label' => __('SKU', 'ai-eshop-optimizer'), 'type' => 'code', 'width' => 'sm', 'sort' => 'none', 'sort_type' => 'text', 'sort_url' => '#dsm_orderby=sku'),
);
$row = static function (array $p) {
$title = (string) $p['title'];
$name = '' !== (string) $p['edit']
? '' . esc_html($title) . ' '
: '' . esc_html($title) . ' ';
return array(
'_id' => (string) (int) $p['id'],
'_name' => $title,
'_attrs' => array(
'data-ra-s' => array('product' => $title, 'sku' => (string) $p['sku'], '_id' => (int) $p['id']),
'data-ra-text' => $title . ' ' . (string) $p['sku'],
'data-aieo-title' => $title,
),
'product' => array('html' => $name),
'sku' => '' !== (string) $p['sku'] ? (string) $p['sku'] : '—',
);
};
return array($columns, $row);
}
private static function manual_results_table(array $results) {
if (!function_exists('rooster_admin_builders') || !rooster_admin_builders()) {
return '';
}
list($columns, $row) = self::manual_columns();
return \Rooster\Admin\Ui::table($columns, array_map($row, $results), array(
'id' => 'aieo-ds-results-table',
'caption' => __('Results', 'ai-eshop-optimizer'),
'selectable' => true,
'local' => array('size' => 0, 'prefix' => 'dsm_'),
'empty' => \Rooster\Admin\States::state('no-results', array('compact' => true, 'title' => __('No simple products matched.', 'ai-eshop-optimizer'), 'text' => '')),
));
}
public function ajax_save_settings() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$raw = isset($_POST['threshold']) ? (float) wp_unslash($_POST['threshold']) : 0;
if ($raw < 0.5 || $raw > 1) {
wp_send_json_error(array(
'message' => __('The threshold must be between 0.50 and 1.00. Below 0.50 everything looks like a duplicate.', 'ai-eshop-optimizer'),
));
}
update_option(AIEO_Duplicate_Product_Groups::OPTION_THRESHOLD, $raw, false);
AIEO_Duplicate_Product_Groups::flush();
wp_send_json_success(array(
'threshold' => $raw,
'message' => __('Saved. Reload to rebuild the list at the new threshold.', 'ai-eshop-optimizer'),
));
}
public function ajax_enable_wc_feature() {
AIEO_DMM::verify_ajax_request();
$this->require_pro();
$key = isset($_POST['feature']) ? sanitize_key(wp_unslash($_POST['feature'])) : '';
$result = AIEO_WC_Feature_Check::enable($key);
if (is_wp_error($result)) {
wp_send_json_error(array('message' => $result->get_error_message()));
}
wp_send_json_success(array(
'message' => __('Switched on. Reload the product editor to see it.', 'ai-eshop-optimizer'),
));
}
private function posted_values() {
if (empty($_POST['values']) || !is_array($_POST['values'])) { return array(); }
$out = array();
foreach (wp_unslash($_POST['values']) as $axis => $per_product) {
$axis = sanitize_key($axis);
if ($axis === '' || !is_array($per_product)) { continue; }
foreach ($per_product as $id => $value) {
$value = trim(sanitize_text_field((string) $value));
if ((int) $id > 0 && $value !== '') { $out[$axis][(int) $id] = $value; }
}
}
return $out;
}
public function render_subtab() {
if (!$this->engine_present()) {
$this->render_engine_missing_notice();
return;
}
$section = isset($_GET['ds_section']) ? sanitize_key(wp_unslash($_GET['ds_section'])) : 'detected';
if (!array_key_exists($section, $this->get_tabs())) { $section = 'detected'; }
rooster_admin_table_assets(array('cards'));
AIEO_DMM_UI::tab_strip($this->get_id(), $this->get_tabs(), $section, array(
'arg' => $this->get_tab_arg(), 'wrapper' => 'div', 'margin' => '14px',
));
if ($section === 'manual') {
$this->render_template('duplicate-simples-manual.php', array('module' => $this));
return;
}
if ($section === 'attributes') {
$this->render_template('duplicate-simples-attributes.php', array(
'choices' => AIEO_Attribute_Axis_Resolver::attribute_choices(),
'wc_missing' => class_exists('AIEO_WC_Feature_Check') ? AIEO_WC_Feature_Check::missing() : array(),
'module' => $this,
));
return;
}
if ($section === 'log') {
$this->render_template('duplicate-simples-log.php', array(
'runs' => AIEO_Duplicate_Product_Merge::history(100),
'module' => $this,
));
return;
}
$available = class_exists('AIEO_Duplicate_Product_Groups')
&& AIEO_Duplicate_Product_Groups::available();
$all = $available ? AIEO_Duplicate_Product_Groups::find(array('limit' => 0)) : array();
$buckets = array('all' => array(), 'ready' => array(), 'review' => array(), 'blocked' => array());
foreach ($all as $g) {
$buckets['all'][] = $g;
if (empty($g['convertible'])) { $buckets['blocked'][] = $g; }
elseif (empty($g['warnings'])) { $buckets['ready'][] = $g; }
else { $buckets['review'][] = $g; }
}
$show = isset($_GET['ds_show']) ? sanitize_key(wp_unslash($_GET['ds_show'])) : 'all';
if (!isset($buckets[$show])) { $show = 'all'; }
$matching = $buckets[$show];
if ($show === 'all' && count($matching) > self::RENDER_CAP) {
$reserved = $buckets['blocked'];
$held = array();
foreach ($reserved as $g) { $held[$g['key']] = true; }
$room = max(0, self::RENDER_CAP - count($reserved));
$rest = array();
foreach ($matching as $g) {
if (isset($held[$g['key']])) { continue; }
if (count($rest) >= $room) { break; }
$rest[] = $g;
}
$groups = array_merge($rest, $reserved);
usort($groups, function ($a, $b) {
if ($a['confidence'] === $b['confidence']) { return count($b['members']) - count($a['members']); }
return ($a['confidence'] < $b['confidence']) ? 1 : -1;
});
} else {
$groups = array_slice($matching, 0, self::RENDER_CAP);
}
$this->render_template('duplicate-simples.php', array(
'groups' => $groups,
'available' => $available,
'threshold' => class_exists('AIEO_Duplicate_Product_Groups')
? AIEO_Duplicate_Product_Groups::threshold()
: 0.95,
'show' => $show,
'counts' => array_map('count', $buckets),
'hidden' => max(0, count($matching) - count($groups)),
'module' => $this,
));
}
}