settings = $settings; $this->redirects = $redirects; $this->router = $router; } public function hooks() { add_filter( 'aooshop_admin_tabs', array( $this, 'tab' ) ); add_action( 'aooshop_admin_tab_' . self::TAB, array( $this, 'render' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) ); foreach ( array( 'save', 'delete', 'to_shop', 'settings', 'export', 'import', 'clear', 'budget_map' ) as $a ) { add_action( 'admin_post_aooshop_rd_' . $a, array( $this, 'action_' . $a ) ); } add_filter( 'aooshop_status_rows', array( $this, 'status_row' ) ); } public function tab( $tabs ) { $tabs[ self::TAB ] = __( 'Redirects (old links)', 'aoo-shop' ); return $tabs; } public function status_row( $rows ) { $s = AooShop_Redirects::summary(); $rows[] = array( __( 'Old link redirects', 'aoo-shop' ), $s['total'] ? sprintf( /* translators: 1: number of redirect rows, 2: rows with a target, 3: total hits */ __( '%1$d links · %2$d with a target · used %3$d times', 'aoo-shop' ), $s['total'], $s['matched'], $s['hits'] ) . ( $this->settings->is_preview() ? ' · ' . __( '(live mode only)', 'aoo-shop' ) : '' ) : __( 'None yet — see the Redirects tab', 'aoo-shop' ), ); return $rows; } private function page_url( array $args = array() ) { return add_query_arg( array_merge( array( 'page' => AooShop_Admin::tab_slug( self::TAB ), 'tab' => self::TAB ), $args ), admin_url( 'admin.php' ) ); } public function enqueue( $hook ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- screen check only. if ( false === strpos( (string) $hook, AooShop_Admin::SLUG ) || ! isset( $_GET['tab'] ) || self::TAB !== $_GET['tab'] ) { return; } wp_enqueue_script( 'aooshop-admin', AOOSHOP_URL . 'assets/aoo-shop-admin.js', array(), AOOSHOP_VERSION . '-' . (string) @filemtime( AOOSHOP_DIR . 'assets/aoo-shop-admin.js' ), true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged wp_localize_script( 'aooshop-admin', 'aooShopAdmin', array( 'ajax' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'aooshop_woo_import' ), 'search' => rest_url( AooShop_Rest::NS . '/search' ), 'restNonce' => wp_create_nonce( 'wp_rest' ), 'i18n' => array( 'products' => __( 'Products', 'aoo-shop' ), 'terms' => __( 'Categories/brands', 'aoo-shop' ), 'done' => __( 'Import finished — reloading…', 'aoo-shop' ), 'error' => __( 'Import failed:', 'aoo-shop' ), 'matched' => __( 'Matched', 'aoo-shop' ), 'none' => __( 'No products found', 'aoo-shop' ), 'confirm' => __( 'Import all product, category and brand links from WooCommerce? (Rows you edited yourself will not be overwritten.)', 'aoo-shop' ), ), ) ); } // ── actions ─────────────────────────────────────────────────────── /** Capability gate for every admin-post action; each action verifies its own nonce right after. */ private function require_cap() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to do this.', 'aoo-shop' ), 403 ); } } private function back( $msg, array $extra = array() ) { $keep = array(); foreach ( array( 'filter', 's', 'paged' ) as $k ) { if ( isset( $_REQUEST[ $k ] ) && '' !== $_REQUEST[ $k ] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce checked by the calling action. $keep[ $k ] = sanitize_text_field( wp_unslash( $_REQUEST[ $k ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } } wp_safe_redirect( $this->page_url( array_merge( $keep, array( 'aoo_msg' => $msg ), $extra ) ) ); exit; } public function action_save() { $this->require_cap(); check_admin_referer( 'aooshop_rd_save' ); $id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0; $old = $id ? AooShop_Redirects::get( $id ) : null; $in = array( 'source_path' => $old ? $old->source_path : ( isset( $_POST['source_path'] ) ? sanitize_text_field( wp_unslash( $_POST['source_path'] ) ) : '' ), 'target_type' => isset( $_POST['target_type'] ) ? sanitize_key( wp_unslash( $_POST['target_type'] ) ) : 'none', 'target_slug' => isset( $_POST['target_slug'] ) ? sanitize_text_field( wp_unslash( $_POST['target_slug'] ) ) : '', 'target_url' => isset( $_POST['target_url'] ) ? esc_url_raw( wp_unslash( $_POST['target_url'] ) ) : '', 'status' => isset( $_POST['status'] ) ? absint( $_POST['status'] ) : 301, 'origin' => 'manual', 'source_kind' => $old ? $old->source_kind : 'other', 'source_name' => $old ? $old->source_name : '', 'matched_by' => 'manual', ); if ( 'page' === $in['target_type'] && isset( $_POST['target_page'] ) ) { $in['target_slug'] = (string) absint( $_POST['target_page'] ); } if ( 'budget' === $in['target_type'] ) { $in['target_slug'] = isset( $_POST['target_budget'] ) ? sanitize_text_field( wp_unslash( $_POST['target_budget'] ) ) : ''; } if ( 'url' === $in['target_type'] && isset( $_POST['target_url'] ) ) { $raw = trim( sanitize_text_field( wp_unslash( $_POST['target_url'] ) ) ); $in['target_url'] = 0 === strpos( $raw, '/' ) ? $raw : esc_url_raw( $raw ); } $row = AooShop_Redirects::clean_row( $in ); if ( is_wp_error( $row ) ) { $this->back( 'error', array( 'aoo_err' => rawurlencode( $row->get_error_message() ) ) ); } if ( $old && $old->source_hash !== $row['source_hash'] ) { AooShop_Redirects::delete( $old->id ); } AooShop_Redirects::upsert( $row, false ); AooShop_Redirects::refresh_count(); $this->back( 'saved' ); } public function action_delete() { $this->require_cap(); check_admin_referer( 'aooshop_rd_delete' ); AooShop_Redirects::delete( isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0 ); AooShop_Redirects::refresh_count(); $this->back( 'deleted' ); } public function action_to_shop() { $this->require_cap(); check_admin_referer( 'aooshop_rd_to_shop' ); $old = AooShop_Redirects::get( isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0 ); if ( $old ) { $row = AooShop_Redirects::clean_row( array( 'source_path' => $old->source_path, 'source_kind' => $old->source_kind, 'source_name' => $old->source_name, 'target_type' => 'shop', 'origin' => 'manual', 'matched_by' => 'manual', 'status' => (int) $old->status, ) ); if ( ! is_wp_error( $row ) ) { AooShop_Redirects::upsert( $row, false ); AooShop_Redirects::refresh_count(); } } $this->back( 'saved' ); } public function action_clear() { $this->require_cap(); check_admin_referer( 'aooshop_rd_clear' ); AooShop_Redirects::delete_generated(); AooShop_Redirects::refresh_count(); $this->back( 'cleared' ); } /** "จับคู่หมวดงบ": one manual row per old Woo budget category the owner mapped (budget page / shop). */ public function action_budget_map() { $this->require_cap(); check_admin_referer( 'aooshop_rd_budget_map' ); // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized per item. $paths = isset( $_POST['bm_path'] ) ? (array) wp_unslash( $_POST['bm_path'] ) : array(); $names = isset( $_POST['bm_name'] ) ? (array) wp_unslash( $_POST['bm_name'] ) : array(); $targets = isset( $_POST['bm_target'] ) ? (array) wp_unslash( $_POST['bm_target'] ) : array(); // phpcs:enable $slugs = wp_list_pluck( $this->settings->price_buckets(), 'slug' ); $n = 0; foreach ( $paths as $i => $path ) { $to = isset( $targets[ $i ] ) ? sanitize_text_field( (string) $targets[ $i ] ) : ''; if ( '' === $to ) { continue; // Unchanged. } if ( 'shop' !== $to && ! in_array( $to, $slugs, true ) ) { continue; } $row = AooShop_Redirects::clean_row( array( 'source_path' => sanitize_text_field( (string) $path ), 'source_kind' => 'category', 'source_name' => isset( $names[ $i ] ) ? sanitize_text_field( (string) $names[ $i ] ) : '', 'target_type' => 'shop' === $to ? 'shop' : 'budget', 'target_slug' => 'shop' === $to ? '' : $to, 'origin' => 'manual', 'matched_by' => 'budget', ) ); if ( ! is_wp_error( $row ) ) { AooShop_Redirects::upsert( $row, false ); $n++; } } AooShop_Redirects::refresh_count(); $this->back( 'budget_saved', array( 'aoo_ok' => $n ) ); } public function action_settings() { $this->require_cap(); check_admin_referer( 'aooshop_rd_settings' ); $landing = array(); $slugs = isset( $_POST['landing_slug'] ) ? (array) wp_unslash( $_POST['landing_slug'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized per item. $pages = isset( $_POST['landing_page'] ) ? array_map( 'absint', (array) $_POST['landing_page'] ) : array(); foreach ( $slugs as $i => $slug ) { $slug = trim( rawurldecode( sanitize_text_field( (string) $slug ) ), '/ ' ); $slug = function_exists( 'mb_strtolower' ) ? mb_strtolower( $slug, 'UTF-8' ) : strtolower( $slug ); $page = isset( $pages[ $i ] ) ? (int) $pages[ $i ] : 0; if ( '' !== $slug && $page && 'page' === get_post_type( $page ) ) { $landing[ $slug ] = $page; } } AooShop_Redirects::save_rd_settings( array( 'fallback_category' => empty( $_POST['fallback_category'] ) ? 0 : 1, 'fallback_brand' => empty( $_POST['fallback_brand'] ) ? 0 : 1, 'brand_landing' => $landing, ) ); $this->back( 'settings_saved' ); } public function action_export() { $this->require_cap(); check_admin_referer( 'aooshop_rd_export' ); global $wpdb; $table = AooShop_Redirects::table(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $rows = $wpdb->get_results( $wpdb->prepare( "SELECT source_path, target_type, target_slug, target_url, status, source_kind, source_name, origin, matched_by, hits FROM %i ORDER BY source_path", $table ), ARRAY_A ); nocache_headers(); header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="aoo-redirects-' . gmdate( 'Ymd-His' ) . '.csv"' ); $out = fopen( 'php://output', 'w' ); fwrite( $out, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Excel needs the BOM for Thai. fputcsv( $out, array_merge( self::CSV_COLS, array( 'source_kind', 'source_name', 'origin', 'matched_by', 'hits' ) ) ); foreach ( (array) $rows as $r ) { $line = array(); foreach ( array( 'source_path', 'target_type', 'target_slug', 'target_url', 'status', 'source_kind', 'source_name', 'origin', 'matched_by', 'hits' ) as $c ) { $v = (string) $r[ $c ]; // Spreadsheet formula injection guard. if ( '' !== $v && in_array( $v[0], array( '=', '+', '-', '@' ), true ) ) { $v = "'" . $v; } $line[] = $v; } fputcsv( $out, $line ); } fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose exit; } public function action_import() { $this->require_cap(); check_admin_referer( 'aooshop_rd_import' ); // phpcs:disable WordPress.Security.ValidatedSanitizedInput -- uploaded file, validated below. if ( empty( $_FILES['csv']['tmp_name'] ) || ! is_uploaded_file( $_FILES['csv']['tmp_name'] ) ) { $this->back( 'error', array( 'aoo_err' => rawurlencode( __( 'No file selected.', 'aoo-shop' ) ) ) ); } if ( (int) $_FILES['csv']['size'] > self::CSV_MAX ) { $this->back( 'error', array( 'aoo_err' => rawurlencode( __( 'The file is larger than 5 MB.', 'aoo-shop' ) ) ) ); } $fh = fopen( $_FILES['csv']['tmp_name'], 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen // phpcs:enable $head = $fh ? fgetcsv( $fh ) : false; if ( ! $head ) { $this->back( 'error', array( 'aoo_err' => rawurlencode( __( 'Could not read the CSV file.', 'aoo-shop' ) ) ) ); } $head[0] = preg_replace( '/^\xEF\xBB\xBF/', '', (string) $head[0] ); $cols = array_flip( array_map( 'trim', array_map( 'strtolower', $head ) ) ); if ( ! isset( $cols['source_path'], $cols['target_type'] ) ) { $this->back( 'error', array( 'aoo_err' => rawurlencode( __( 'The file must have source_path and target_type columns.', 'aoo-shop' ) ) ) ); } $ok = 0; $bad = 0; $n = 0; while ( ( $line = fgetcsv( $fh ) ) !== false && $n < 20000 ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition $n++; $get = function ( $c ) use ( $cols, $line ) { if ( ! isset( $cols[ $c ], $line[ $cols[ $c ] ] ) ) { return ''; } $v = trim( (string) $line[ $cols[ $c ] ] ); return ( '' !== $v && "'" === $v[0] && isset( $v[1] ) && in_array( $v[1], array( '=', '+', '-', '@' ), true ) ) ? substr( $v, 1 ) : $v; }; $row = AooShop_Redirects::clean_row( array( 'source_path' => $get( 'source_path' ), 'target_type' => '' !== $get( 'target_type' ) ? $get( 'target_type' ) : 'none', 'target_slug' => $get( 'target_slug' ), 'target_url' => $get( 'target_url' ), 'status' => '' !== $get( 'status' ) ? (int) $get( 'status' ) : 301, 'source_kind' => $get( 'source_kind' ), 'source_name' => $get( 'source_name' ), 'origin' => 'manual', 'matched_by' => 'csv', ) ); if ( is_wp_error( $row ) ) { $bad++; continue; } AooShop_Redirects::upsert( $row, false ); $ok++; } fclose( $fh ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose AooShop_Redirects::refresh_count(); $this->back( 'imported', array( 'aoo_ok' => $ok, 'aoo_bad' => $bad ) ); } // ── screen ──────────────────────────────────────────────────────── private function notice() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- display only. $msg = isset( $_GET['aoo_msg'] ) ? sanitize_key( wp_unslash( $_GET['aoo_msg'] ) ) : ''; $map = array( 'saved' => __( 'Saved.', 'aoo-shop' ), 'deleted' => __( 'Deleted.', 'aoo-shop' ), 'cleared' => __( 'Removed imported and auto-generated rows (rows you edited yourself are kept).', 'aoo-shop' ), 'settings_saved' => __( 'Fallback rules saved.', 'aoo-shop' ), ); if ( 'budget_saved' === $msg ) { $map[ $msg ] = sprintf( /* translators: %d: number of categories saved */ __( 'Budget category mapping saved: %d categories.', 'aoo-shop' ), isset( $_GET['aoo_ok'] ) ? absint( $_GET['aoo_ok'] ) : 0 ); } if ( isset( $map[ $msg ] ) ) { echo '

' . esc_html( $map[ $msg ] ) . '

'; } elseif ( 'imported' === $msg ) { $ok = isset( $_GET['aoo_ok'] ) ? absint( $_GET['aoo_ok'] ) : 0; $bad = isset( $_GET['aoo_bad'] ) ? absint( $_GET['aoo_bad'] ) : 0; echo '

' . esc_html( sprintf( /* translators: 1: number of imported rows, 2: number of skipped rows */ __( 'Imported %1$d CSV rows · skipped %2$d invalid rows.', 'aoo-shop' ), $ok, $bad ) ) . '

'; } elseif ( 'error' === $msg ) { $err = isset( $_GET['aoo_err'] ) ? sanitize_text_field( wp_unslash( $_GET['aoo_err'] ) ) : ''; echo '

' . esc_html( $err ) . '

'; } // phpcs:enable } private static function type_label( $type ) { $labels = array( 'product' => __( 'Product', 'aoo-shop' ), 'category' => __( 'Category', 'aoo-shop' ), 'brand' => __( 'Brand', 'aoo-shop' ), 'collection' => __( 'Collection', 'aoo-shop' ), 'budget' => __( 'Budget page', 'aoo-shop' ), 'shop' => __( 'Shop', 'aoo-shop' ), 'page' => __( 'WordPress page', 'aoo-shop' ), 'url' => __( 'Other URL', 'aoo-shop' ), 'none' => __( 'Not found (404)', 'aoo-shop' ), 'other' => __( 'Other', 'aoo-shop' ), ); return isset( $labels[ $type ] ) ? $labels[ $type ] : $type; } public function render() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( ! AooShop_Redirects::table_exists() ) { AooShop_Redirects::install(); } $this->notice(); $sum = AooShop_Redirects::summary(); ?>

settings->is_preview() ) : ?>

render_import(); $this->render_test(); $this->render_edit(); $this->render_table(); $this->render_budget_map(); $this->render_csv(); $this->render_fallback(); } private function render_import() { echo '

' . esc_html__( 'Import from WooCommerce', 'aoo-shop' ) . '

'; $last = get_option( AooShop_Woo_Import::STATS_OPTION ); if ( is_array( $last ) && ! empty( $last['finished'] ) ) { $p = $last['products']; $t = $last['terms']; echo '

' . esc_html( sprintf( /* translators: 1: date and time of the last import, 2: matched products, 3: products sent, 4: match percentage, 5: matched categories/brands, 6: categories/brands sent */ __( 'Last import %1$s · products matched %2$d/%3$d (%4$s%%) · categories/brands %5$d/%6$d', 'aoo-shop' ), wp_date( 'j M Y H:i', (int) $last['finished'] ), $p['matched'], $p['sent'], $p['sent'] ? number_format_i18n( 100 * $p['matched'] / $p['sent'], 1 ) : '0', $t['matched'], $t['sent'] ) ); $by = array(); foreach ( array_merge_recursive( (array) $p['by'], (array) $t['by'] ) as $k => $v ) { $by[] = $k . ' ' . ( is_array( $v ) ? array_sum( $v ) : (int) $v ); } if ( $by ) { echo ' · ' . esc_html__( 'Matched by', 'aoo-shop' ) . ' ' . esc_html( implode( ', ', $by ) ); } echo '

'; } if ( ! AooShop_Woo_Import::available() ) { echo '

' . esc_html__( 'WooCommerce is not active — importing only works while WooCommerce is still active (do it before moving the shop). You can use CSV instead.', 'aoo-shop' ) . '

'; return; } if ( ! $this->settings->is_configured() ) { echo '

' . esc_html__( 'Enter the API key first (Settings tab).', 'aoo-shop' ) . '

'; return; } ?>

redirects->resolve( $norm, true ) : null; $rule = array( 'row' => __( 'Matches a row in the table', 'aoo-shop' ), 'brand_landing' => __( 'Brand landing page', 'aoo-shop' ), 'fallback_category' => __( 'Fallback rule: old category → AOO category', 'aoo-shop' ), 'fallback_brand' => __( 'Fallback rule: old brand → AOO brand', 'aoo-shop' ), 'fallback_shop' => __( 'Fallback rule: not found in AOO → shop', 'aoo-shop' ), ); echo '

' . esc_html( '' !== $norm ? $norm : $test ) . ' → '; if ( ! $hit ) { echo esc_html__( 'No redirect (WordPress handles it — 404 if the page does not exist)', 'aoo-shop' ); } elseif ( '' === $hit['target'] ) { echo esc_html( $rule[ $hit['rule'] ] ) . ' · ' . esc_html__( 'no target → 404', 'aoo-shop' ); } else { echo '' . (int) $hit['status'] . ' ' . esc_html( rawurldecode( $hit['target'] ) ) . ' · ' . esc_html( $rule[ $hit['rule'] ] ); if ( ! empty( $hit['note'] ) ) { echo ' (' . esc_html( $hit['note'] ) . ')'; } } echo '

'; } private function render_edit() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only. $id = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : 0; $row = $id ? AooShop_Redirects::get( $id ) : null; $t = $row ? $row->target_type : 'product'; ?>

__( 'All', 'aoo-shop' ), 'matched' => __( 'With a target', 'aoo-shop' ), 'unmatched' => __( 'No target', 'aoo-shop' ), 'manual' => __( 'Manual', 'aoo-shop' ), 'auto' => __( 'Automatic', 'aoo-shop' ), 'product' => __( 'Products', 'aoo-shop' ), 'category' => __( 'Categories', 'aoo-shop' ), 'brand' => __( 'Brands', 'aoo-shop' ), ); switch ( $filter ) { case 'matched': $where[] = "target_type <> 'none'"; break; case 'unmatched': $where[] = "target_type = 'none'"; break; case 'manual': case 'auto': $where[] = $wpdb->prepare( 'origin = %s', $filter ); break; case 'product': case 'category': case 'brand': $where[] = $wpdb->prepare( 'source_kind = %s', $filter ); break; } if ( '' !== $search ) { $like = '%' . $wpdb->esc_like( function_exists( 'mb_strtolower' ) ? mb_strtolower( rawurldecode( $search ), 'UTF-8' ) : strtolower( $search ) ) . '%'; $where[] = $wpdb->prepare( '(source_path LIKE %s OR source_name LIKE %s OR target_slug LIKE %s)', $like, '%' . $wpdb->esc_like( $search ) . '%', $like ); } $w = implode( ' AND ', $where ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- $w built from prepare() pieces. $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM %i WHERE {$w}", $table ) ); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM %i WHERE {$w} ORDER BY target_type = 'none' DESC, hits DESC, source_path ASC LIMIT %d OFFSET %d", $table, self::PER_PAGE, ( $paged - 1 ) * self::PER_PAGE ) ); // phpcs:enable ?>

'WooCommerce', 'manual' => __( 'Manual', 'aoo-shop' ), 'auto' => __( 'Automatic', 'aoo-shop' ), ); $rd = AooShop_Redirects::rd_settings(); foreach ( (array) $rows as $r ) : $target = $this->redirects->target_url( $r ); ?>
source_path ); ?>
source_kind ) . ( '' !== $r->source_name ? ' · ' . $r->source_name : '' ) ); ?>

target_type ) . ( 301 !== (int) $r->status ? ' · ' . (int) $r->status : '' ) ); ?> origin && in_array( $r->source_kind, array( 'category', 'brand' ), true ) && ! empty( $rd[ 'fallback_' . $r->source_kind ] ) ) : ?>
matched_by ? $r->matched_by : '–' ); ?> origin ] ) ? $origins[ $r->origin ] : $r->origin ); ?> hits; ?> target_type ) : ?> mini_form( 'to_shop', (int) $r->id, __( '→ Shop', 'aoo-shop' ) ); ?> mini_form( 'delete', (int) $r->id, __( 'Delete', 'aoo-shop' ), true ); ?>
1 ) { echo '

'; for ( $i = 1; $i <= $pages; $i++ ) { $args = array( 'paged' => $i ); if ( '' !== $filter ) { $args['filter'] = $filter; } if ( '' !== $search ) { $args['s'] = $search; } echo $i === $paged ? '' . (int) $i . '' : '' . (int) $i . ''; } echo '

'; } } private function mini_form( $action, $id, $label, $confirm = false ) { ?>
>
of the shop's budget pages (ping price buckets). No buckets yet → text box for the slug. * * @param string $name Field name. * @param string $selected Bucket slug. * @param bool $with_keep Add "— ไม่เปลี่ยน —" + "หน้าร้าน" (budget-map table). */ private function budget_select( $name, $selected, $with_keep ) { $buckets = $this->settings->price_buckets(); if ( ! $buckets && ! $with_keep ) { printf( ' ', esc_attr( $name ), esc_attr( $selected ) ); echo '' . esc_html__( 'The shop has no price ranges in AOO yet (or "Test connection" has not been clicked) — type a range yourself, e.g. 0-1500 · 1500-2000 · 3000-', 'aoo-shop' ) . ''; return; } echo ''; } /** * Old Woo categories for the budget table: live product_cat terms (Woo still on) or imported category rows. * Budget-looking ones only unless `$all` (name/slug has งบ · budget · budjet · ราคา · price, or a 3+ digit number). * * @return array */ private static function budget_terms( $all ) { $out = array(); if ( taxonomy_exists( 'product_cat' ) ) { $terms = get_terms( array( 'taxonomy' => 'product_cat', 'hide_empty' => false, 'orderby' => 'name', ) ); foreach ( is_wp_error( $terms ) ? array() : $terms as $t ) { $link = get_term_link( $t ); $path = is_wp_error( $link ) ? '' : AooShop_Redirects::normalize_path( (string) $link ); if ( '' !== $path ) { $out[] = array( 'path' => $path, 'name' => html_entity_decode( (string) $t->name, ENT_QUOTES, 'UTF-8' ), 'slug' => rawurldecode( (string) $t->slug ), ); } } } elseif ( AooShop_Redirects::table_exists() ) { global $wpdb; $table = AooShop_Redirects::table(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $rows = (array) $wpdb->get_results( $wpdb->prepare( "SELECT source_path, source_name FROM %i WHERE source_kind = 'category' ORDER BY source_name LIMIT 500", $table ) ); foreach ( $rows as $r ) { $out[] = array( 'path' => (string) $r->source_path, 'name' => (string) $r->source_name, 'slug' => AooShop_Redirects::last_segment( $r->source_path ), ); } } if ( ! $all ) { $out = array_values( array_filter( $out, function ( $t ) { return (bool) preg_match( '/งบ|budget|budjet|ราคา|price|\d{3,}/iu', $t['name'] . ' ' . $t['slug'] ); } ) ); } return $out; } /** * Bucket a Woo budget term most likely means ('' = no guess): "งบ 1500-2000" → 1500-2000 · * "งบไม่เกิน 1500" / less-than-1500 → 0-1500 · "งบมากกว่า 3000" / more-than-3000 → 3000-. */ private static function guess_bucket( array $term, array $buckets ) { $text = str_replace( ',', '', $term['name'] . ' ' . $term['slug'] ); preg_match_all( '/\d{3,}/', $text, $m ); $nums = array_values( array_unique( array_map( 'intval', $m[0] ) ) ); if ( ! $nums ) { return ''; } foreach ( $buckets as $b ) { $min = (int) $b['min']; $max = null === $b['max'] ? null : (int) $b['max']; if ( 2 <= count( $nums ) && $min === $nums[0] && $max === $nums[1] ) { return $b['slug']; } if ( 1 === count( $nums ) ) { $up = (bool) preg_match( '/มากกว่า|ขึ้นไป|เกินกว่า|more|over|above|plus/iu', $text ); $down = (bool) preg_match( '/ไม่เกิน|ต่ำกว่า|น้อยกว่า|less|under|below/iu', $text ); if ( $up && null === $max && $min === $nums[0] ) { return $b['slug']; } if ( $down && 0 === $min && $max === $nums[0] ) { return $b['slug']; } } } return ''; } private function render_budget_map() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display toggle. $all = ! empty( $_GET['aoo_budget_all'] ); $terms = self::budget_terms( $all ); $buckets = $this->settings->price_buckets(); ?>

$t ) : $row = AooShop_Redirects::find( $t['path'] ); $current = $row ? $this->redirects->target_url( $row ) : ''; $pick = ''; if ( $row && 'manual' === $row->origin && 'budget' === $row->target_type ) { $pick = $row->target_slug; } elseif ( ! $row || 'manual' !== $row->origin ) { $pick = self::guess_bucket( $t, $buckets ); } ?>


target_type ) ); ?>
budget_select( 'bm_target[' . $i . ']', $pick, true ); ?>

get_col( $wpdb->prepare( "SELECT source_path FROM %i WHERE source_kind = 'brand' ORDER BY source_path LIMIT 200", $table ) ); foreach ( $paths as $p ) { $slug = AooShop_Redirects::last_segment( $p ); if ( '' !== $slug && ! isset( $brand_rows[ $slug ] ) ) { $brand_rows[ $slug ] = 0; } } $brand_rows[''] = 0; // One empty row to add. ?>

$page ) : ?>
'landing_page[' . $i . ']', 'selected' => (int) $page, 'show_option_none' => esc_html__( '— Use the AOO brand page —', 'aoo-shop' ), 'option_none_value' => '0', ) ); ?>