public function maybe_handle_lookup() { if ( isset( $_POST['shccl_lookup_nonce'], $_POST['shccl_search'] ) && wp_verify_nonce( $_POST['shccl_lookup_nonce'], 'shccl_lookup' ) ) { if ( ! $this->shccl_user_can_access() ) { return; } global $wpdb; $term_raw = trim( wp_unslash( $_POST['shccl_search'] ) ); $term_like = '%' . $wpdb->esc_like( $term_raw ) . '%'; self::$search_term = $term_raw; // Table names (respect your prefix, e.g. wpbx_) $orders_table = $wpdb->prefix . 'wc_orders'; $ordermeta_table = $wpdb->prefix . 'wc_orders_meta'; // ------------------------- // 1. HPOS SEARCH // ------------------------- // A) Direct match in wc_orders columns $sql_main = $wpdb->prepare( "SELECT o.id, o.status, o.type, o.date_created_gmt, o.billing_first_name, o.billing_last_name, o.billing_phone, o.billing_email, o.total_amount FROM {$orders_table} o WHERE ( o.id = %s OR o.billing_email LIKE %s OR o.billing_phone LIKE %s OR o.billing_first_name LIKE %s OR o.billing_last_name LIKE %s ) AND o.type IN ('shop_order','shop_subscription') ORDER BY o.date_created_gmt DESC LIMIT 25", $term_raw, $term_like, $term_like, $term_like, $term_like ); $main_matches = $wpdb->get_results( $sql_main, ARRAY_A ); // B) Match against IMEI / MAC / etc stored in wc_orders_meta $meta_keys_to_check = array( '_imei', '_mac', '_crc', '_panel_serial', '_account_number', '_location_name', '_service_address_phone', '_billing_last_name', // catch weird legacy imports that landed in meta instead of columns '_billing_phone', '_billing_email' ); $placeholders = implode( ',', array_fill( 0, count( $meta_keys_to_check ), '%s' ) ); $params = $meta_keys_to_check; $params[] = $term_like; $sql_meta = $wpdb->prepare( "SELECT DISTINCT o.id, o.status, o.type, o.date_created_gmt, o.billing_first_name, o.billing_last_name, o.billing_phone, o.billing_email, o.total_amount FROM {$orders_table} o INNER JOIN {$ordermeta_table} om ON om.order_id = o.id WHERE om.meta_key IN ($placeholders) AND om.meta_value LIKE %s AND o.type IN ('shop_order','shop_subscription') ORDER BY o.date_created_gmt DESC LIMIT 25", ...$params ); $meta_matches = $wpdb->get_results( $sql_meta, ARRAY_A ); // ------------------------- // 2. LEGACY FALLBACK SEARCH (wp_posts/wp_postmeta) // ------------------------- // We try to pull any subscription/order posts that match name/phone/email/etc. // We'll reconstruct a row shaped like HPOS rows so we can merge. $posts_table = $wpdb->posts; $postmeta_table = $wpdb->postmeta; // A) Match by postmeta (billing fields or alarm IDs) // We'll pull limited info (ID, status, type, date, billing_*) // NOTE: _billing_first_name, _billing_last_name, _billing_phone, _billing_email are standard WC postmeta keys. $legacy_meta_keys = array( '_billing_first_name', '_billing_last_name', '_billing_phone', '_billing_email', '_imei', '_mac', '_crc', '_account_number', '_panel_serial', '_location_name', '_service_address_phone' ); $legacy_placeholders = implode( ',', array_fill( 0, count( $legacy_meta_keys ), '%s' ) ); $legacy_params = $legacy_meta_keys; $legacy_params[] = $term_like; // This query: // - find any posts (orders or subs) whose meta matches the search term // - grab the common billing fields via conditional subselects $sql_legacy = $wpdb->prepare( "SELECT DISTINCT p.ID AS id, p.post_status AS status, p.post_type AS type, p.post_date_gmt AS date_created_gmt, -- pull billing_* fields from postmeta MAX(CASE WHEN pm2.meta_key = '_billing_first_name' THEN pm2.meta_value END) AS billing_first_name, MAX(CASE WHEN pm2.meta_key = '_billing_last_name' THEN pm2.meta_value END) AS billing_last_name, MAX(CASE WHEN pm2.meta_key = '_billing_phone' THEN pm2.meta_value END) AS billing_phone, MAX(CASE WHEN pm2.meta_key = '_billing_email' THEN pm2.meta_value END) AS billing_email, -- total_amount doesn't exist in legacy the same way. -- We'll try _order_total and convert to cents. MAX(CASE WHEN pm2.meta_key = '_order_total' THEN pm2.meta_value END) AS legacy_total FROM {$posts_table} p INNER JOIN {$postmeta_table} pm_match ON pm_match.post_id = p.ID INNER JOIN {$postmeta_table} pm2 ON pm2.post_id = p.ID WHERE p.post_type IN ('shop_order','shop_subscription') AND ( p.ID = %s OR pm_match.meta_key IN ($legacy_placeholders) AND pm_match.meta_value LIKE %s ) GROUP BY p.ID ORDER BY p.post_date_gmt DESC LIMIT 25", $term_raw, ...$legacy_params ); $legacy_matches = $wpdb->get_results( $sql_legacy, ARRAY_A ); // Normalize legacy total into cents like HPOS total_amount foreach ( $legacy_matches as &$lm ) { if ( isset( $lm['legacy_total'] ) && $lm['legacy_total'] !== '' ) { // legacy _order_total is typically a decimal string in dollars (e.g. "17.95") $dollars = floatval( $lm['legacy_total'] ); $lm['total_amount'] = (string) round( $dollars * 100 ); // store like HPOS cents } else { $lm['total_amount'] = ''; } unset( $lm['legacy_total'] ); } unset( $lm ); // ------------------------- // 3. MERGE RESULTS + ENRICH SUBSCRIPTIONS // ------------------------- $combined = array(); foreach ( array_merge( $main_matches, $meta_matches, $legacy_matches ) as $row ) { // Key by ID. Important: legacy and HPOS can technically share an ID number space // because HPOS order ID == WP post ID for migrated orders, so this is fine. $combined[ $row['id'] ] = $row; } // Now pull subscription-specific meta for any rows that are 'shop_subscription' $subs_info = array(); if ( ! empty( $combined ) ) { $ids = implode( ',', array_map( 'intval', array_keys( $combined ) ) ); // Subscription details from HPOS (wc_orders_meta) // plus legacy (wp_postmeta) in one go, unioned. // We'll get next bill date, billing interval, IMEI, MAC, CRC, etc. // HPOS sub meta $sql_sub_meta_hpos = " SELECT om.order_id AS oid, om.meta_key, om.meta_value FROM {$ordermeta_table} om INNER JOIN {$orders_table} o ON o.id = om.order_id WHERE o.id IN ({$ids}) AND o.type = 'shop_subscription' AND om.meta_key IN ( '_schedule_next_payment', '_schedule_trial_end', '_billing_period', '_billing_interval', '_imei', '_mac', '_crc', '_account_number' ) "; $sub_meta_rows_hpos = $wpdb->get_results( $sql_sub_meta_hpos, ARRAY_A ); // Legacy sub meta $sql_sub_meta_legacy = " SELECT pm.post_id AS oid, pm.meta_key, pm.meta_value FROM {$postmeta_table} pm INNER JOIN {$posts_table} p ON p.ID = pm.post_id WHERE p.ID IN ({$ids}) AND p.post_type = 'shop_subscription' AND pm.meta_key IN ( '_schedule_next_payment', '_schedule_trial_end', '_billing_period', '_billing_interval', '_imei', '_mac', '_crc', '_account_number' ) "; $sub_meta_rows_legacy = $wpdb->get_results( $sql_sub_meta_legacy, ARRAY_A ); $all_meta_rows = array_merge( $sub_meta_rows_hpos, $sub_meta_rows_legacy ); foreach ( $all_meta_rows as $m ) { $oid = $m['oid']; if ( ! isset( $subs_info[ $oid ] ) ) { $subs_info[ $oid ] = array(); } $subs_info[ $oid ][ $m['meta_key'] ] = $m['meta_value']; } } foreach ( $combined as $id => $row ) { $combined[ $id ]['subscription_meta'] = isset( $subs_info[ $id ] ) ? $subs_info[ $id ] : array(); } self::$results = $combined; } }