'Version' ] ); define( 'AIASS_PLUGIN_VERSION', $aia_camacam_aia_data['Version'] ); unset( $aia_camacam_aia_data ); define( 'AIASS_PLUGIN_FILE', __FILE__ ); function aiass_get_token(){ return get_option('aia_camacam_token', ''); } function aiass_errors_table() { global $wpdb; if ( ! is_multisite() || is_main_site() ) { return 'aiaerrorlogs'; } return $wpdb->prefix . 'aiaerrorlogs'; } function aiass_authenticate_request() { $incoming = isset( $_SERVER['HTTP_AIATOKEN'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_AIATOKEN'] ) ) : ''; $stored = aiass_get_token(); if ( empty( $stored ) || empty( $incoming ) || ! hash_equals( $stored, $incoming ) ) { header( 'Content-Type: application/json' ); http_response_code( 401 ); echo json_encode( array( 'error' => 'Unauthorized' ) ); exit; } } add_filter('query_vars', 'aiass_add_query_vars'); function aiass_add_query_vars($vars) { $vars[] = 'aiass_endpoint'; return $vars; } function aiass_get_json_data(){ $jsonData = file_get_contents('php://input'); if ( empty( $jsonData ) ) { return null; } $data = json_decode($jsonData, true); if ( json_last_error() !== JSON_ERROR_NONE ) { return null; } return $data; } function aiass_send_data( $url, $payload ){ $args = array( 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8', ), 'body' => $payload, 'timeout' => '45', 'redirection' => '5', 'httpversion' => '1.0', 'data_format' => 'body', 'blocking' => true, 'headers' => array( 'aiatoken' => aiass_get_token() ), 'cookies' => array(), ); return wp_remote_post($url, $args); } function aiass_insert_exception_record($exception) { aiass_send_error($exception); global $wpdb; $table_name = aiass_errors_table(); $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $table_name, array( 'content' => json_encode($exception), ), array( '%s', ) ); } function aiass_send_error( $error ){ $args = array( 'body' => json_encode($error), 'timeout' => '45', 'redirection' => '5', 'httpversion' => '1.0', 'blocking' => true, 'headers' => array( 'aiatoken' => aiass_get_token() ), 'cookies' => array(), ); return wp_remote_post(aiass_get_url().'/api/v1/error', $args); } function aiass_get_order_statuses() { try { if (!function_exists('wc_get_order_statuses')) { throw new Exception("WooCommerce is not active."); } $installed_languages = get_available_languages(); $locales = array('en_US'); foreach ($installed_languages as $lang_file) { $locales[] = basename($lang_file, '.mo'); } $locales = array_unique($locales); $statuses_by_locale = array(); $original_locale = get_locale(); foreach ($locales as $locale) { switch_to_locale($locale); $statuses_by_locale[$locale] = wc_get_order_statuses(); switch_to_locale($original_locale); } header('Content-Type: application/json'); http_response_code(200); echo json_encode($statuses_by_locale); } catch (Exception $e) { header('Content-Type: application/json'); http_response_code(500); echo json_encode(["error" => $e->getMessage()]); } } function aiass_get_payment_methods() { try { if ( ! function_exists( 'WC' ) || ! WC()->payment_gateways() ) { throw new Exception( "WooCommerce is not active." ); } $installed_languages = get_available_languages(); $locales = array( 'en_US' ); foreach ( $installed_languages as $lang_file ) { $locales[] = basename( $lang_file, '.mo' ); } $locales = array_unique( $locales ); $methods_by_locale = array(); $original_locale = get_locale(); foreach ( $locales as $locale ) { switch_to_locale( $locale ); WC()->payment_gateways()->init(); $gateways = WC()->payment_gateways()->payment_gateways(); $translated = array(); foreach ( $gateways as $gateway ) { $translated[ $gateway->id ] = $gateway->get_method_title(); } $methods_by_locale[ $locale ] = $translated; switch_to_locale( $original_locale ); } header( 'Content-Type: application/json' ); http_response_code( 200 ); echo json_encode( $methods_by_locale ); } catch ( Exception $e ) { header( 'Content-Type: application/json' ); http_response_code( 500 ); echo json_encode( array( 'error' => $e->getMessage() ) ); } } function aiass_apply_product_update(){ $data = aiass_get_json_data(); if( is_null( $data ) || $data['command'] !== 'productupdate' ) return; if( ! isset( $data['data'] ) || ! is_array( $data['data'] ) ) { header( 'Content-Type: application/json' ); http_response_code( 400 ); echo json_encode( array( 'error' => 'Missing or invalid data payload' ) ); return; } $pid = absint( isset( $data['data']['pid'] ) ? $data['data']['pid'] : 0 ); if( $pid === 0 ) { header( 'Content-Type: application/json' ); http_response_code( 400 ); echo json_encode( array( 'error' => 'Invalid product ID' ) ); return; } try { $aiausr = get_user_by('email', 'aia@aissistant.shop'); if( !$aiausr ){ $email = 'aia@aissistant.shop'; $password = wp_generate_password(); $userdata = array( 'user_login' => $email, 'user_email' => $email, 'first_name' => 'Aia', 'last_name' => 'Assistant', 'user_pass' => $password, ); $user_id = wp_insert_user($userdata); } $pname = isset( $data['data']['pname'] ) ? sanitize_text_field( $data['data']['pname'] ) : ''; $pshortd = isset( $data['data']['pshortd'] ) ? wp_kses_post( $data['data']['pshortd'] ) : ''; $pdescr = isset( $data['data']['pdescr'] ) ? wp_kses_post( $data['data']['pdescr'] ) : ''; $seotitle = isset( $data['data']['seotitle'] ) ? sanitize_text_field( $data['data']['seotitle'] ) : ''; $seodescr = isset( $data['data']['seodescr'] ) ? sanitize_text_field( $data['data']['seodescr'] ) : ''; $focuskeywords = isset( $data['data']['focuskeywords'] ) ? sanitize_text_field( $data['data']['focuskeywords'] ) : ''; $focuskw_text_input = isset( $data['data']['focuskw_text_input'] ) ? sanitize_text_field( $data['data']['focuskw_text_input'] ) : ''; $focuskw = isset( $data['data']['focuskw'] ) ? sanitize_text_field( $data['data']['focuskw'] ) : ''; $product = wc_get_product( $pid ); if( $product ){ $is_update = false; if( $pname ) { $product->set_name( $pname ); $is_update = true; } if( $pshortd ) { $product->set_short_description( $pshortd ); $is_update = true; } if( $pdescr ){ $product->set_description( $pdescr ); $is_update = true; } if( $is_update){ $product->save(); } if( $seotitle ) { update_post_meta($product->get_id(), '_yoast_wpseo_title', $seotitle); $is_update = true; } if( $seodescr ) { update_post_meta($product->get_id(), '_yoast_wpseo_metadesc', $seodescr); $is_update = true; } if( $focuskeywords ){ update_post_meta($product->get_id(), '_yoast_wpseo_focuskeywords', $focuskeywords); $is_update = true; } if( $focuskw_text_input ){ update_post_meta($product->get_id(), '_yoast_wpseo_focuskw_text_input', $focuskw_text_input); $is_update = true; } if( $focuskw ){ update_post_meta($product->get_id(), '_yoast_wpseo_focuskw', $focuskw); $is_update = true; } if (class_exists('WC_Data_Store') && $is_update ) { $product_data_store = WC_Data_Store::load('product'); $product_data_store->read($product); $product_data_store->create_revision($product); } }else{ aiass_send_error( "no such product: " . json_encode($data) ); } header('Content-Type: application/json'); http_response_code(200); echo json_encode("OK"); } catch (Exception $e) { aiass_insert_exception_record($e); } } function aiass_validate_select_query( $query ) { global $wpdb; if ( preg_match( '/\b(UNION(\s+ALL)?\s+SELECT|INTO\s+OUTFILE|LOAD_FILE|INFORMATION_SCHEMA|mysql\.user)\b/i', $query ) ) { throw new Exception( 'Query contains a disallowed SQL construct' ); } preg_match_all( '/\b(?:FROM|JOIN)\s+`?(\w+)`?/i', $query, $matches ); $tables = array_filter( $matches[1] ); foreach ( $tables as $table ) { if ( 'dual' === strtolower( $table ) ) { continue; } if ( strpos( $table, $wpdb->prefix ) !== 0 ) { throw new Exception( 'Query references a disallowed table: ' . esc_html( $table ) ); } } } function aiass_gather_analytics(){ $data = aiass_get_json_data(); if( is_null( $data ) || $data['command'] !== 'sql' ) return; if( ! isset( $data['action'] ) || ! is_string( $data['action'] ) || empty( trim( $data['action'] ) ) ) { header( 'Content-Type: application/json' ); http_response_code( 400 ); echo json_encode( array( 'error' => 'Missing or invalid action' ) ); return; } $query = trim( $data['action'] ); if ( ! preg_match( '/^\s*SELECT\b/i', $query ) ) { header( 'Content-Type: application/json' ); http_response_code( 400 ); echo json_encode( array( 'error' => 'Only SELECT queries are permitted' ) ); return; } $params = isset( $data['params'] ) && is_array( $data['params'] ) ? array_values( $data['params'] ) : array(); try { global $wpdb; aiass_validate_select_query( $query ); if ( ! empty( $params ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter $res = $wpdb->get_results( $wpdb->prepare( $query, ...$params ) ); } else { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter $res = $wpdb->get_results( $query ); } $return_url = isset( $data['returnUrl'] ) ? $data['returnUrl'] : ''; if ( ! filter_var( $return_url, FILTER_VALIDATE_URL ) ) { throw new Exception( 'Invalid returnUrl' ); } $rr = aiass_send_data( $return_url, json_encode($res) ); header('Content-Type: application/json'); http_response_code(200); } catch (Exception $e) { aiass_insert_exception_record($e); } } function aiass_gather_analytics_directly(){ $data = aiass_get_json_data(); if( is_null( $data ) || $data['command'] !== 'sql' ) return; if( ! isset( $data['action'] ) || ! is_string( $data['action'] ) || empty( trim( $data['action'] ) ) ) { header( 'Content-Type: application/json' ); http_response_code( 400 ); echo json_encode( array( 'error' => 'Missing or invalid action' ) ); return; } $query = trim( $data['action'] ); if ( ! preg_match( '/^\s*SELECT\b/i', $query ) ) { header( 'Content-Type: application/json' ); http_response_code( 400 ); echo json_encode( array( 'error' => 'Only SELECT queries are permitted' ) ); return; } $params = isset( $data['params'] ) && is_array( $data['params'] ) ? array_values( $data['params'] ) : array(); try { global $wpdb; aiass_validate_select_query( $query ); if ( ! empty( $params ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter $res = $wpdb->get_results( $wpdb->prepare( $query, ...$params ) ); } else { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter $res = $wpdb->get_results( $query ); } header('Content-Type: application/json'); http_response_code(200); echo json_encode( json_encode($res) ); } catch (Exception $e) { aiass_insert_exception_record($e); } } function aiass_get_errors(){ try { global $wpdb; $res = $wpdb->get_results( 'SELECT * FROM ' . aiass_errors_table() . ' ORDER BY id DESC LIMIT 100' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching if( $res ){ header('Content-Type: text/plain'); foreach ($res as $line) { echo wp_json_encode( $line ) . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } exit; } } catch (Exception $e) { echo "No errors logged or table doesn't exist"; } } function aiass_apply_order_update() { $data = aiass_get_json_data(); if( is_null( $data ) ) return; $order_id = isset($data['order_id']) ? intval($data['order_id']) : 0; $previous_status = isset($data['previous_status']) ? sanitize_text_field($data['previous_status']) : ''; $new_status = isset($data['new_status']) ? sanitize_text_field($data['new_status']) : ''; if ( ! $order_id || ! $previous_status || ! $new_status ) { header('Content-Type: application/json'); http_response_code(400); echo json_encode(["error" => "Missing required fields: order_id, previous_status, new_status"]); return; } try { $order = wc_get_order($order_id); if ( ! $order ) { header('Content-Type: application/json'); http_response_code(400); echo json_encode(["error" => "Order not found: " . $order_id]); return; } $current_status = $order->get_status(); $previous_status_clean = preg_replace('/^wc-/', '', $previous_status); $new_status_clean = preg_replace('/^wc-/', '', $new_status); $valid_statuses = array_keys(wc_get_order_statuses()); $valid_statuses_clean = array_map(function($s) { return preg_replace('/^wc-/', '', $s); }, $valid_statuses); if ( ! in_array($new_status_clean, $valid_statuses_clean, true) ) { header('Content-Type: application/json'); http_response_code(400); echo json_encode(["error" => "Invalid status: " . $new_status_clean, "valid_statuses" => $valid_statuses_clean]); return; } if ( $current_status !== $previous_status_clean ) { header('Content-Type: application/json'); http_response_code(400); echo json_encode([ "error" => "Status mismatch", "expected" => $previous_status_clean, "current" => $current_status, ]); return; } $order->set_status($new_status_clean); $order->save(); header('Content-Type: application/json'); http_response_code(200); echo json_encode(["success" => true, "order_id" => $order_id, "status" => $new_status_clean]); } catch (Exception $e) { aiass_insert_exception_record($e); header('Content-Type: application/json'); http_response_code(500); echo json_encode(["error" => $e->getMessage()]); } } function aiass_get_reg_info(){ header('Content-Type: text/plain'); echo "AIA Client: "; echo get_option('aia_camacam_client', ''); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } define( 'AIASS_PROMOTION_META', '_aiass_promotion_id' ); define( 'AIASS_PROMOTION_MAX_PRODUCTS', 100 ); function aiass_apply_promotion() { if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) { wp_send_json( array( 'error' => 'POST required' ), 405 ); } if ( ! function_exists( 'wc_get_product' ) || ! function_exists( 'wc_get_price_decimals' ) ) { wp_send_json( array( 'error' => 'WooCommerce is not active' ), 500 ); } $data = aiass_get_json_data(); if ( ! is_array( $data ) ) { wp_send_json( array( 'error' => 'Invalid JSON payload' ), 400 ); } $promo = aiass_promotion_validate( $data ); if ( isset( $promo['error'] ) ) { wp_send_json( array( 'error' => $promo['error'] ), 400 ); } $result = array( 'promotion_id' => $promo['promotion_id'], 'mode' => $promo['mode'], 'applied' => array(), 'skipped' => array(), 'errors' => array(), ); $seen = array(); foreach ( $promo['products'] as $entry ) { $pid = $entry['pid']; if ( isset( $seen[ $pid ] ) ) { $result['skipped'][] = aiass_promotion_skip( $pid, 'duplicate' ); continue; } $seen[ $pid ] = true; $product = wc_get_product( $pid ); if ( ! $product || 'trash' === $product->get_status() ) { $result['skipped'][] = aiass_promotion_skip( $pid, 'not_found' ); continue; } $targets = aiass_promotion_targets( $product ); if ( empty( $targets ) ) { $result['skipped'][] = aiass_promotion_skip( $pid, 'no_variations' ); continue; } foreach ( $targets as $target ) { $target_id = $target->get_id(); if ( $target_id !== $pid ) { if ( isset( $seen[ $target_id ] ) ) { continue; } $seen[ $target_id ] = true; } try { $outcome = ( 'stop' === $promo['mode'] ) ? aiass_promotion_stop( $target, $promo['promotion_id'] ) : aiass_promotion_start( $target, $promo, $entry['expected_price'] ); } catch ( Exception $e ) { aiass_insert_exception_record( $e ); $outcome = array( 'bucket' => 'errors', 'product_id' => $target_id, 'parent_id' => $target->get_parent_id(), 'message' => 'The product could not be saved', ); } $bucket = $outcome['bucket']; unset( $outcome['bucket'] ); $result[ $bucket ][] = $outcome; } if ( $product->is_type( 'variable' ) && class_exists( 'WC_Product_Variable' ) ) { WC_Product_Variable::sync( $product->get_id() ); } } wp_send_json( $result, 200 ); } function aiass_promotion_validate( $data ) { $promotion_id = ( isset( $data['promotion_id'] ) && is_string( $data['promotion_id'] ) ) ? trim( $data['promotion_id'] ) : ''; if ( ! preg_match( '/^[A-Za-z0-9:@._-]{1,96}$/', $promotion_id ) ) { return array( 'error' => 'Invalid promotion_id' ); } $mode = isset( $data['mode'] ) ? $data['mode'] : 'start'; if ( ! in_array( $mode, array( 'start', 'stop' ), true ) ) { return array( 'error' => 'mode must be "start" or "stop"' ); } if ( empty( $data['products'] ) || ! is_array( $data['products'] ) ) { return array( 'error' => 'products must be a non-empty list' ); } if ( count( $data['products'] ) > AIASS_PROMOTION_MAX_PRODUCTS ) { return array( 'error' => 'Too many products (max ' . AIASS_PROMOTION_MAX_PRODUCTS . ')' ); } $products = array(); foreach ( $data['products'] as $row ) { $pid = ( is_array( $row ) && isset( $row['pid'] ) ) ? absint( $row['pid'] ) : 0; if ( 0 === $pid ) { return array( 'error' => 'Every product needs a positive integer pid' ); } $expected = ( isset( $row['expected_price'] ) && is_numeric( $row['expected_price'] ) ) ? (float) $row['expected_price'] : null; $products[] = array( 'pid' => $pid, 'expected_price' => $expected ); } $promo = array( 'promotion_id' => $promotion_id, 'mode' => $mode, 'products' => $products ); if ( 'stop' === $mode ) { return $promo; } $pct = ( isset( $data['discount_pct'] ) && is_numeric( $data['discount_pct'] ) ) ? (float) $data['discount_pct'] : null; $price = ( isset( $data['promo_price'] ) && is_numeric( $data['promo_price'] ) ) ? (float) $data['promo_price'] : null; if ( ( null === $pct ) === ( null === $price ) ) { return array( 'error' => 'Exactly one of discount_pct or promo_price is required' ); } if ( null !== $pct && ( $pct <= 0 || $pct >= 100 ) ) { return array( 'error' => 'discount_pct must be between 0 and 100' ); } if ( null !== $price && $price <= 0 ) { return array( 'error' => 'promo_price must be positive' ); } $from = aiass_promotion_date( isset( $data['date_from'] ) ? $data['date_from'] : '' ); $to = aiass_promotion_date( isset( $data['date_to'] ) ? $data['date_to'] : '' ); if ( '' === $from || '' === $to ) { return array( 'error' => 'date_from and date_to must be YYYY-MM-DD' ); } if ( $to < $from ) { return array( 'error' => 'date_to is before date_from' ); } if ( $to < current_time( 'Y-m-d' ) ) { return array( 'error' => 'date_to is in the past' ); } $promo['discount_pct'] = $pct; $promo['promo_price'] = $price; $promo['date_from'] = $from; $promo['date_to'] = $to; return $promo; } function aiass_promotion_date( $raw ) { if ( ! is_string( $raw ) || ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $raw ) ) { return ''; } $parsed = DateTime::createFromFormat( '!Y-m-d', $raw ); return ( $parsed && $parsed->format( 'Y-m-d' ) === $raw ) ? $raw : ''; } function aiass_promotion_targets( $product ) { if ( ! $product->is_type( 'variable' ) ) { return array( $product ); } $targets = array(); foreach ( $product->get_children() as $child_id ) { $variation = wc_get_product( $child_id ); if ( $variation && 'trash' !== $variation->get_status() ) { $targets[] = $variation; } } return $targets; } function aiass_promotion_skip( $product_id, $reason, $parent_id = 0, $extra = array() ) { return array_merge( array( 'bucket' => 'skipped', 'product_id' => $product_id, 'parent_id' => $parent_id, 'reason' => $reason ), $extra ); } function aiass_promotion_start( $product, $promo, $expected_price ) { $pid = $product->get_id(); $parent_id = $product->get_parent_id(); $decimals = wc_get_price_decimals(); if ( $product->get_meta( AIASS_PROMOTION_META, true, 'edit' ) === $promo['promotion_id'] ) { return array( 'bucket' => 'applied', 'product_id' => $pid, 'parent_id' => $parent_id, 'regular_price' => wc_format_decimal( $product->get_regular_price( 'edit' ), $decimals ), 'sale_price' => wc_format_decimal( $product->get_sale_price( 'edit' ), $decimals ), 'already_applied' => true, ); } if ( '' !== (string) $product->get_sale_price( 'edit' ) || $product->is_on_sale( 'edit' ) ) { return aiass_promotion_skip( $pid, 'already_on_sale', $parent_id ); } $regular_raw = $product->get_regular_price( 'edit' ); if ( '' === (string) $regular_raw || ! is_numeric( $regular_raw ) || (float) $regular_raw <= 0 ) { return aiass_promotion_skip( $pid, 'no_regular_price', $parent_id ); } $regular = (float) $regular_raw; if ( null !== $expected_price && wc_format_decimal( $regular, $decimals ) !== wc_format_decimal( $expected_price, $decimals ) ) { return aiass_promotion_skip( $pid, 'price_changed', $parent_id, array( 'current_price' => wc_format_decimal( $regular, $decimals ) ) ); } $sale = ( null !== $promo['discount_pct'] ) ? $regular * ( 1 - $promo['discount_pct'] / 100 ) : $promo['promo_price']; $sale = round( $sale, $decimals ); if ( $sale <= 0 || $sale >= $regular ) { return aiass_promotion_skip( $pid, 'not_a_discount', $parent_id, array( 'current_price' => wc_format_decimal( $regular, $decimals ) ) ); } $product->set_sale_price( wc_format_decimal( $sale, $decimals ) ); $product->set_date_on_sale_from( $promo['date_from'] . ' 00:00:00' ); $product->set_date_on_sale_to( $promo['date_to'] . ' 23:59:59' ); $product->update_meta_data( AIASS_PROMOTION_META, $promo['promotion_id'] ); $product->save(); return array( 'bucket' => 'applied', 'product_id' => $pid, 'parent_id' => $parent_id, 'regular_price' => wc_format_decimal( $regular, $decimals ), 'sale_price' => wc_format_decimal( $sale, $decimals ), ); } function aiass_promotion_stop( $product, $promotion_id ) { $pid = $product->get_id(); $parent_id = $product->get_parent_id(); if ( $product->get_meta( AIASS_PROMOTION_META, true, 'edit' ) !== $promotion_id ) { return aiass_promotion_skip( $pid, 'not_ours', $parent_id ); } $product->set_sale_price( '' ); $product->set_date_on_sale_from( '' ); $product->set_date_on_sale_to( '' ); $product->delete_meta_data( AIASS_PROMOTION_META ); $product->save(); return array( 'bucket' => 'applied', 'product_id' => $pid, 'parent_id' => $parent_id, 'regular_price' => wc_format_decimal( $product->get_regular_price( 'edit' ), wc_get_price_decimals() ), 'sale_price' => '', ); } define( 'AIASS_PROMOCODE_FROM_META', '_aiass_date_from' ); define( 'AIASS_PROMOCODE_STOPPED_META', '_aiass_stopped_at' ); define( 'AIASS_PROMOCODE_CODE_MAX', 50 ); add_filter( 'woocommerce_coupon_is_valid', 'aiass_promocode_check_start', 10, 2 ); function aiass_promocode_check_start( $valid, $coupon ) { if ( ! $valid || ! is_a( $coupon, 'WC_Coupon' ) ) { return $valid; } $from = (string) $coupon->get_meta( AIASS_PROMOCODE_FROM_META, true, 'edit' ); if ( '' !== $from && current_time( 'Y-m-d' ) < $from ) { throw new Exception( 'This coupon is not active yet.', 100 ); } return $valid; } function aiass_apply_promo_code() { if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) { wp_send_json( array( 'error' => 'POST required' ), 405 ); } if ( ! class_exists( 'WC_Coupon' ) || ! function_exists( 'wc_get_coupon_types' ) ) { wp_send_json( array( 'error' => 'WooCommerce is not active' ), 500 ); } $data = aiass_get_json_data(); if ( ! is_array( $data ) ) { wp_send_json( array( 'error' => 'Invalid JSON payload' ), 400 ); } $req = aiass_promocode_validate( $data ); if ( isset( $req['error'] ) ) { wp_send_json( array( 'error' => $req['error'] ), 400 ); } try { if ( 'create' === $req['action'] ) { $reply = aiass_promocode_create( $req ); } else { $coupon = aiass_promocode_load( $req ); $reply = ( 'stop' === $req['action'] ) ? aiass_promocode_stop( $coupon ) : aiass_promocode_update( $coupon, $req ); } } catch ( Exception $e ) { aiass_insert_exception_record( $e ); wp_send_json( array( 'error' => 'The coupon could not be saved' ), 500 ); } $status = isset( $reply['http'] ) ? $reply['http'] : 200; unset( $reply['http'] ); if ( 200 === $status ) { $reply = array_merge( array( 'action' => $req['action'] ), $reply ); } wp_send_json( $reply, $status ); } function aiass_promocode_validate( $data ) { $action = isset( $data['action'] ) ? $data['action'] : ''; if ( ! in_array( $action, array( 'create', 'update', 'stop' ), true ) ) { return array( 'error' => 'action must be "create", "update" or "stop"' ); } $promotion_id = ( isset( $data['promotion_id'] ) && is_string( $data['promotion_id'] ) ) ? trim( $data['promotion_id'] ) : ''; if ( ! preg_match( '/^[A-Za-z0-9:@._-]{1,96}$/', $promotion_id ) ) { return array( 'error' => 'Invalid promotion_id' ); } $coupon_id = isset( $data['coupon_id'] ) ? absint( $data['coupon_id'] ) : 0; if ( isset( $data['coupon_id'] ) && 0 === $coupon_id ) { return array( 'error' => 'coupon_id must be a positive integer' ); } $code = null; if ( isset( $data['code'] ) && '' !== $data['code'] ) { $code = aiass_promocode_code( $data['code'] ); if ( '' === $code ) { return array( 'error' => 'Invalid code (no spaces, at most ' . AIASS_PROMOCODE_CODE_MAX . ' characters)' ); } } if ( 'create' !== $action && 0 === $coupon_id && null === $code ) { return array( 'error' => 'coupon_id or code is required' ); } $req = array( 'action' => $action, 'promotion_id' => $promotion_id, 'coupon_id' => $coupon_id, 'code' => $code, 'fields' => array(), ); if ( 'stop' === $action ) { return $req; } $fields = aiass_promocode_fields( $data ); if ( isset( $fields['error'] ) ) { return $fields; } if ( 'create' === $action ) { foreach ( array( 'discount_type', 'amount', 'date_from', 'date_to' ) as $key ) { if ( ! isset( $fields[ $key ] ) ) { return array( 'error' => $key . ' is required' ); } } } $req['fields'] = $fields; return $req; } function aiass_promocode_code( $raw ) { if ( ! is_string( $raw ) && ! is_numeric( $raw ) ) { return ''; } $raw = trim( (string) $raw ); if ( '' === $raw || preg_match( '/\s/u', $raw ) || mb_strlen( $raw ) > AIASS_PROMOCODE_CODE_MAX ) { return ''; } $code = wc_format_coupon_code( $raw ); return ( '' !== $code && wc_strtolower( $raw ) === $code ) ? $code : ''; } function aiass_promocode_fields( $data ) { $fields = array(); if ( array_key_exists( 'discount_type', $data ) ) { if ( ! in_array( $data['discount_type'], array( 'percent', 'fixed_cart', 'fixed_product' ), true ) ) { return array( 'error' => 'discount_type must be "percent", "fixed_cart" or "fixed_product"' ); } $fields['discount_type'] = $data['discount_type']; } if ( array_key_exists( 'amount', $data ) ) { if ( ! is_numeric( $data['amount'] ) || (float) $data['amount'] <= 0 ) { return array( 'error' => 'amount must be a positive number' ); } $fields['amount'] = (float) $data['amount']; } foreach ( array( 'date_from', 'date_to' ) as $key ) { if ( array_key_exists( $key, $data ) ) { $date = aiass_promotion_date( $data[ $key ] ); if ( '' === $date ) { return array( 'error' => $key . ' must be YYYY-MM-DD' ); } $fields[ $key ] = $date; } } if ( isset( $fields['date_to'] ) && $fields['date_to'] < current_time( 'Y-m-d' ) ) { return array( 'error' => 'date_to is in the past' ); } if ( array_key_exists( 'description', $data ) ) { if ( ! is_string( $data['description'] ) ) { return array( 'error' => 'description must be a string' ); } $fields['description'] = sanitize_textarea_field( $data['description'] ); } foreach ( array( 'free_shipping', 'individual_use', 'exclude_sale_items' ) as $key ) { if ( array_key_exists( $key, $data ) ) { $flag = is_bool( $data[ $key ] ) ? $data[ $key ] : filter_var( $data[ $key ], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); if ( null === $flag ) { return array( 'error' => $key . ' must be true or false' ); } $fields[ $key ] = $flag; } } foreach ( array( 'minimum_amount', 'maximum_amount' ) as $key ) { if ( array_key_exists( $key, $data ) ) { $value = $data[ $key ]; if ( null === $value || '' === $value ) { $fields[ $key ] = ''; } elseif ( is_numeric( $value ) && (float) $value >= 0 ) { $fields[ $key ] = (float) $value; } else { return array( 'error' => $key . ' must be a non-negative number or empty' ); } } } foreach ( array( 'usage_limit', 'usage_limit_per_user', 'limit_usage_to_x_items' ) as $key ) { if ( array_key_exists( $key, $data ) ) { $value = ( null === $data[ $key ] || '' === $data[ $key ] ) ? 0 : filter_var( $data[ $key ], FILTER_VALIDATE_INT, array( 'options' => array( 'min_range' => 0 ) ) ); if ( false === $value ) { return array( 'error' => $key . ' must be a non-negative integer' ); } $fields[ $key ] = $value; } } foreach ( array( 'product_ids', 'excluded_product_ids', 'product_categories', 'excluded_product_categories' ) as $key ) { if ( ! array_key_exists( $key, $data ) ) { continue; } $ids = aiass_promocode_id_list( $data[ $key ] ); if ( null === $ids ) { return array( 'error' => $key . ' must be a list of positive integers' ); } foreach ( $ids as $id ) { $exists = in_array( $key, array( 'product_ids', 'excluded_product_ids' ), true ) ? aiass_promocode_product_exists( $id ) : (bool) term_exists( $id, 'product_cat' ); if ( ! $exists ) { return array( 'error' => $key . ' contains an unknown id: ' . $id ); } } $fields[ $key ] = $ids; } if ( ! array_key_exists( 'email_restrictions', $data ) && ! empty( $data['customer_email'] ) ) { $data['email_restrictions'] = array( (string) $data['customer_email'] ); } if ( array_key_exists( 'email_restrictions', $data ) ) { if ( ! is_array( $data['email_restrictions'] ) ) { return array( 'error' => 'email_restrictions must be a list of email addresses' ); } $emails = array(); foreach ( $data['email_restrictions'] as $email ) { $clean = is_string( $email ) ? sanitize_email( strtolower( trim( $email ) ) ) : ''; if ( '' === $clean || ! is_email( $clean ) ) { return array( 'error' => 'email_restrictions contains an invalid address' ); } $emails[] = $clean; } $fields['email_restrictions'] = array_values( array_unique( $emails ) ); } return $fields; } function aiass_promocode_id_list( $raw ) { if ( null === $raw ) { return array(); } if ( ! is_array( $raw ) ) { return null; } $ids = array(); foreach ( $raw as $value ) { $id = filter_var( $value, FILTER_VALIDATE_INT, array( 'options' => array( 'min_range' => 1 ) ) ); if ( false === $id ) { return null; } $ids[ $id ] = $id; } return array_values( $ids ); } function aiass_promocode_product_exists( $id ) { $product = wc_get_product( $id ); return $product && 'trash' !== $product->get_status(); } function aiass_promocode_ids_by_code( $code, $exclude = 0 ) { global $wpdb; $ids = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_title = %s AND post_type = 'shop_coupon' AND post_status NOT IN ('trash', 'auto-draft') ORDER BY ID DESC", wc_sanitize_coupon_code( $code ) ) ); return array_values( array_diff( array_map( 'absint', $ids ), array( absint( $exclude ) ) ) ); } function aiass_promocode_id_by_promotion( $promotion_id ) { global $wpdb; return absint( $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} m ON m.post_id = p.ID AND m.meta_key = %s AND m.meta_value = %s WHERE p.post_type = 'shop_coupon' AND p.post_status NOT IN ('trash', 'auto-draft') ORDER BY p.ID DESC LIMIT 1", AIASS_PROMOTION_META, $promotion_id ) ) ); } function aiass_promocode_generate() { $alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; for ( $attempt = 0; $attempt < 10; $attempt++ ) { $code = 'AIA-'; for ( $i = 0; $i < 8; $i++ ) { $code .= $alphabet[ random_int( 0, strlen( $alphabet ) - 1 ) ]; } $code = wc_format_coupon_code( $code ); if ( empty( aiass_promocode_ids_by_code( $code ) ) ) { return $code; } } throw new Exception( 'Could not generate a unique coupon code' ); } function aiass_promocode_load( $req ) { $id = $req['coupon_id']; if ( 0 === $id ) { $ids = aiass_promocode_ids_by_code( $req['code'] ); $id = $ids ? $ids[0] : 0; } $post = $id ? get_post( $id ) : null; if ( ! $post || 'shop_coupon' !== $post->post_type || in_array( $post->post_status, array( 'trash', 'auto-draft' ), true ) ) { wp_send_json( array( 'error' => 'Coupon not found' ), 404 ); } $coupon = new WC_Coupon( $id ); if ( $coupon->get_meta( AIASS_PROMOTION_META, true, 'edit' ) !== $req['promotion_id'] ) { wp_send_json( array( 'error' => 'not_ours' ), 403 ); } return $coupon; } function aiass_promocode_check( $coupon, $fields ) { $type = isset( $fields['discount_type'] ) ? $fields['discount_type'] : $coupon->get_discount_type( 'edit' ); $amount = isset( $fields['amount'] ) ? $fields['amount'] : (float) $coupon->get_amount( 'edit' ); if ( 'percent' === $type && $amount > 100 ) { return 'amount must be at most 100 for a percent discount'; } $stored_to = $coupon->get_date_expires( 'edit' ); $from = isset( $fields['date_from'] ) ? $fields['date_from'] : (string) $coupon->get_meta( AIASS_PROMOCODE_FROM_META, true, 'edit' ); $to = isset( $fields['date_to'] ) ? $fields['date_to'] : ( $stored_to ? $stored_to->date( 'Y-m-d' ) : '' ); if ( '' !== $from && '' !== $to && $to < $from ) { return 'date_to is before date_from'; } $min = array_key_exists( 'minimum_amount', $fields ) ? $fields['minimum_amount'] : $coupon->get_minimum_amount( 'edit' ); $max = array_key_exists( 'maximum_amount', $fields ) ? $fields['maximum_amount'] : $coupon->get_maximum_amount( 'edit' ); if ( '' !== (string) $min && '' !== (string) $max && (float) $max > 0 && (float) $max < (float) $min ) { return 'maximum_amount is below minimum_amount'; } return ''; } function aiass_promocode_apply( $coupon, $fields ) { $setters = array( 'discount_type' => 'set_discount_type', 'amount' => 'set_amount', 'description' => 'set_description', 'free_shipping' => 'set_free_shipping', 'individual_use' => 'set_individual_use', 'exclude_sale_items' => 'set_exclude_sale_items', 'minimum_amount' => 'set_minimum_amount', 'maximum_amount' => 'set_maximum_amount', 'product_ids' => 'set_product_ids', 'excluded_product_ids' => 'set_excluded_product_ids', 'product_categories' => 'set_product_categories', 'excluded_product_categories' => 'set_excluded_product_categories', 'email_restrictions' => 'set_email_restrictions', 'usage_limit' => 'set_usage_limit', 'usage_limit_per_user' => 'set_usage_limit_per_user', 'limit_usage_to_x_items' => 'set_limit_usage_to_x_items', ); foreach ( $setters as $key => $setter ) { if ( array_key_exists( $key, $fields ) ) { $coupon->$setter( $fields[ $key ] ); } } if ( isset( $fields['date_from'] ) ) { $coupon->update_meta_data( AIASS_PROMOCODE_FROM_META, $fields['date_from'] ); } if ( isset( $fields['date_to'] ) ) { $coupon->set_date_expires( $fields['date_to'] . ' 23:59:59' ); } } function aiass_promocode_create( $req ) { $code = $req['code']; $existing = $code ? aiass_promocode_ids_by_code( $code ) : array(); if ( ! $code ) { $own = aiass_promocode_id_by_promotion( $req['promotion_id'] ); $existing = $own ? array( $own ) : array(); } if ( $existing ) { $coupon = new WC_Coupon( $existing[0] ); if ( $coupon->get_meta( AIASS_PROMOTION_META, true, 'edit' ) === $req['promotion_id'] ) { return array_merge( aiass_promocode_reply( $existing[0] ), array( 'already_exists' => true ) ); } return array( 'http' => 409, 'error' => 'Code already exists' ); } $coupon = new WC_Coupon(); $error = aiass_promocode_check( $coupon, $req['fields'] ); if ( '' !== $error ) { return array( 'http' => 400, 'error' => $error ); } $fields = $req['fields']; if ( ! isset( $fields['description'] ) ) { $fields['description'] = 'Created by AIssistant (promotion ' . $req['promotion_id'] . ')'; } $coupon->set_code( $code ? $code : aiass_promocode_generate() ); aiass_promocode_apply( $coupon, $fields ); $coupon->update_meta_data( AIASS_PROMOTION_META, $req['promotion_id'] ); $coupon->save(); return aiass_promocode_reply( $coupon->get_id() ); } function aiass_promocode_update( $coupon, $req ) { $fields = $req['fields']; $rename = ( $req['coupon_id'] && $req['code'] && $req['code'] !== $coupon->get_code( 'edit' ) ); if ( $rename && aiass_promocode_ids_by_code( $req['code'], $coupon->get_id() ) ) { return array( 'http' => 409, 'error' => 'Code already exists' ); } $error = aiass_promocode_check( $coupon, $fields ); if ( '' !== $error ) { return array( 'http' => 400, 'error' => $error ); } if ( $rename ) { $coupon->set_code( $req['code'] ); } aiass_promocode_apply( $coupon, $fields ); $coupon->save(); return aiass_promocode_reply( $coupon->get_id() ); } function aiass_promocode_stop( $coupon ) { $id = $coupon->get_id(); if ( 'publish' !== get_post_status( $id ) ) { return array_merge( aiass_promocode_reply( $id ), array( 'already_stopped' => true ) ); } $coupon->update_meta_data( AIASS_PROMOCODE_STOPPED_META, current_time( 'mysql' ) ); $coupon->save(); $updated = wp_update_post( array( 'ID' => $id, 'post_status' => 'draft' ), true ); if ( is_wp_error( $updated ) || ! $updated ) { throw new Exception( 'Could not unpublish coupon ' . $id ); } if ( class_exists( 'WC_Cache_Helper' ) && method_exists( 'WC_Cache_Helper', 'invalidate_cache_group' ) ) { WC_Cache_Helper::invalidate_cache_group( 'coupons' ); } return aiass_promocode_reply( $id ); } function aiass_promocode_reply( $id ) { $coupon = new WC_Coupon( $id ); $decimals = wc_get_price_decimals(); $expires = $coupon->get_date_expires( 'edit' ); $from = (string) $coupon->get_meta( AIASS_PROMOCODE_FROM_META, true, 'edit' ); $today = current_time( 'Y-m-d' ); if ( 'publish' !== get_post_status( $id ) ) { $status = 'stopped'; } elseif ( $expires && time() > $expires->getTimestamp() ) { $status = 'expired'; } elseif ( '' !== $from && $today < $from ) { $status = 'scheduled'; } else { $status = 'active'; } $money = function ( $value ) use ( $decimals ) { return '' === (string) $value ? '' : wc_format_decimal( $value, $decimals ); }; return array( 'promotion_id' => (string) $coupon->get_meta( AIASS_PROMOTION_META, true, 'edit' ), 'coupon_id' => $coupon->get_id(), 'code' => $coupon->get_code( 'edit' ), 'status' => $status, 'discount_type' => $coupon->get_discount_type( 'edit' ), 'amount' => $money( $coupon->get_amount( 'edit' ) ), 'date_from' => $from, 'date_to' => $expires ? $expires->date( 'Y-m-d' ) : '', 'description' => $coupon->get_description( 'edit' ), 'free_shipping' => $coupon->get_free_shipping( 'edit' ), 'individual_use' => $coupon->get_individual_use( 'edit' ), 'exclude_sale_items' => $coupon->get_exclude_sale_items( 'edit' ), 'minimum_amount' => $money( $coupon->get_minimum_amount( 'edit' ) ), 'maximum_amount' => $money( $coupon->get_maximum_amount( 'edit' ) ), 'product_ids' => array_values( $coupon->get_product_ids( 'edit' ) ), 'excluded_product_ids' => array_values( $coupon->get_excluded_product_ids( 'edit' ) ), 'product_categories' => array_values( $coupon->get_product_categories( 'edit' ) ), 'excluded_product_categories' => array_values( $coupon->get_excluded_product_categories( 'edit' ) ), 'email_restrictions' => array_values( $coupon->get_email_restrictions( 'edit' ) ), 'usage_limit' => (int) $coupon->get_usage_limit( 'edit' ), 'usage_limit_per_user' => (int) $coupon->get_usage_limit_per_user( 'edit' ), 'limit_usage_to_x_items' => (int) $coupon->get_limit_usage_to_x_items( 'edit' ), 'usage_count' => (int) $coupon->get_usage_count( 'edit' ), ); } define( 'AIASS_TAXONOMY_MAX_OPERATIONS', 50 ); define( 'AIASS_TAXONOMY', 'product_cat' ); function aiass_apply_taxonomy() { if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) { wp_send_json( array( 'error' => 'POST required' ), 405 ); } if ( ! function_exists( 'wc_get_product' ) ) { wp_send_json( array( 'error' => 'WooCommerce is not active' ), 500 ); } $data = aiass_get_json_data(); if ( ! is_array( $data ) ) { wp_send_json( array( 'error' => 'Invalid JSON payload' ), 400 ); } $change = aiass_taxonomy_validate( $data ); if ( isset( $change['error'] ) ) { wp_send_json( array( 'error' => $change['error'] ), 400 ); } $result = array( 'change_id' => $change['change_id'], 'mode' => $change['mode'], 'applied' => array(), 'skipped' => array(), 'errors' => array(), ); foreach ( $change['operations'] as $index => $op ) { try { $outcome = aiass_taxonomy_run( $op ); } catch ( Exception $e ) { aiass_insert_exception_record( $e ); $outcome = array( 'bucket' => 'errors', 'message' => 'The change could not be saved' ); } $outcome['index'] = $index; $outcome['op'] = $op['op']; $bucket = $outcome['bucket']; unset( $outcome['bucket'] ); $result[ $bucket ][] = $outcome; } delete_transient( 'wc_term_counts' ); if ( function_exists( 'wc_delete_product_transients' ) ) { wc_delete_product_transients(); } wp_send_json( $result, 200 ); } function aiass_taxonomy_validate( $data ) { $change_id = isset( $data['change_id'] ) ? (string) $data['change_id'] : ''; if ( ! preg_match( '/^[A-Za-z0-9:@._-]{1,96}$/', $change_id ) ) { return array( 'error' => 'Invalid change_id' ); } $mode = isset( $data['mode'] ) ? (string) $data['mode'] : 'apply'; if ( ! in_array( $mode, array( 'apply', 'revert' ), true ) ) { return array( 'error' => 'Invalid mode' ); } $raw = isset( $data['operations'] ) && is_array( $data['operations'] ) ? $data['operations'] : array(); if ( empty( $raw ) || count( $raw ) > AIASS_TAXONOMY_MAX_OPERATIONS ) { return array( 'error' => 'operations must hold 1 to ' . AIASS_TAXONOMY_MAX_OPERATIONS . ' entries' ); } $ops = array(); foreach ( $raw as $entry ) { if ( ! is_array( $entry ) || empty( $entry['op'] ) ) { return array( 'error' => 'Every operation needs an op' ); } $op = (string) $entry['op']; switch ( $op ) { case 'move_product': $pid = isset( $entry['product_id'] ) ? (int) $entry['product_id'] : 0; if ( $pid <= 0 ) { return array( 'error' => 'move_product needs a product_id' ); } $ops[] = array( 'op' => $op, 'product_id' => $pid, 'from' => aiass_taxonomy_ids( isset( $entry['from'] ) ? $entry['from'] : array() ), 'to' => aiass_taxonomy_ids( isset( $entry['to'] ) ? $entry['to'] : array() ), ); break; case 'reparent': $cid = isset( $entry['category_id'] ) ? (int) $entry['category_id'] : 0; if ( $cid <= 0 ) { return array( 'error' => 'reparent needs a category_id' ); } $ops[] = array( 'op' => $op, 'category_id' => $cid, 'parent_id' => isset( $entry['parent_id'] ) ? max( 0, (int) $entry['parent_id'] ) : 0, 'expected_parent' => isset( $entry['expected_parent'] ) && null !== $entry['expected_parent'] ? max( 0, (int) $entry['expected_parent'] ) : null, ); break; case 'merge': $cid = isset( $entry['category_id'] ) ? (int) $entry['category_id'] : 0; $into = isset( $entry['into_id'] ) ? (int) $entry['into_id'] : 0; if ( $cid <= 0 || $into <= 0 || $cid === $into ) { return array( 'error' => 'merge needs distinct category_id and into_id' ); } $ops[] = array( 'op' => $op, 'category_id' => $cid, 'into_id' => $into ); break; case 'unmerge': $name = isset( $entry['name'] ) ? sanitize_text_field( (string) $entry['name'] ) : ''; $into = isset( $entry['into_id'] ) ? (int) $entry['into_id'] : 0; if ( '' === $name || $into <= 0 ) { return array( 'error' => 'unmerge needs a name and into_id' ); } $products = array(); foreach ( isset( $entry['products'] ) && is_array( $entry['products'] ) ? $entry['products'] : array() as $row ) { $pid = is_array( $row ) && isset( $row['product_id'] ) ? (int) $row['product_id'] : 0; if ( $pid > 0 ) { $products[] = array( 'product_id' => $pid, 'had_into' => ! empty( $row['had_into'] ) ); } } $ops[] = array( 'op' => $op, 'name' => $name, 'slug' => isset( $entry['slug'] ) ? sanitize_title( (string) $entry['slug'] ) : '', 'parent' => isset( $entry['parent'] ) ? max( 0, (int) $entry['parent'] ) : 0, 'into_id' => $into, 'products' => $products, ); break; case 'rename': $cid = isset( $entry['category_id'] ) ? (int) $entry['category_id'] : 0; $name = isset( $entry['name'] ) ? sanitize_text_field( (string) $entry['name'] ) : ''; if ( $cid <= 0 || '' === $name ) { return array( 'error' => 'rename needs a category_id and a name' ); } $ops[] = array( 'op' => $op, 'category_id' => $cid, 'name' => $name, 'expected_name' => isset( $entry['expected_name'] ) ? (string) $entry['expected_name'] : null, ); break; default: return array( 'error' => 'Unknown op ' . $op ); } } return array( 'change_id' => $change_id, 'mode' => $mode, 'operations' => $ops ); } function aiass_taxonomy_ids( $raw ) { $ids = array(); foreach ( is_array( $raw ) ? $raw : array() as $value ) { $id = (int) $value; if ( $id > 0 ) { $ids[] = $id; } } $ids = array_values( array_unique( $ids ) ); sort( $ids ); return $ids; } function aiass_taxonomy_skip( $reason, $extra = array() ) { return array_merge( array( 'bucket' => 'skipped', 'reason' => $reason ), $extra ); } function aiass_taxonomy_term( $id ) { $term = get_term( (int) $id, AIASS_TAXONOMY ); return ( $term && ! is_wp_error( $term ) ) ? $term : null; } function aiass_taxonomy_product_cats( $product_id ) { $ids = wp_get_object_terms( $product_id, AIASS_TAXONOMY, array( 'fields' => 'ids' ) ); return is_wp_error( $ids ) ? array() : aiass_taxonomy_ids( $ids ); } function aiass_taxonomy_is_ancestor( $ancestor, $term_id ) { $ancestor = (int) $ancestor; if ( $ancestor <= 0 ) { return false; } if ( $ancestor === (int) $term_id ) { return true; } return in_array( $ancestor, array_map( 'intval', get_ancestors( (int) $term_id, AIASS_TAXONOMY ) ), true ); } function aiass_taxonomy_run( $op ) { switch ( $op['op'] ) { case 'move_product': return aiass_taxonomy_move_product( $op ); case 'reparent': return aiass_taxonomy_reparent( $op ); case 'merge': return aiass_taxonomy_merge( $op ); case 'unmerge': return aiass_taxonomy_unmerge( $op ); case 'rename': return aiass_taxonomy_rename( $op ); } return aiass_taxonomy_skip( 'unknown_op' ); } function aiass_taxonomy_move_product( $op ) { $pid = $op['product_id']; $product = wc_get_product( $pid ); if ( ! $product || 'trash' === $product->get_status() ) { return aiass_taxonomy_skip( 'not_found', array( 'product_id' => $pid ) ); } $current = aiass_taxonomy_product_cats( $pid ); if ( ! empty( $op['from'] ) && $current !== $op['from'] ) { return aiass_taxonomy_skip( 'categories_changed', array( 'product_id' => $pid, 'current' => $current ) ); } foreach ( $op['to'] as $tid ) { if ( ! aiass_taxonomy_term( $tid ) ) { return aiass_taxonomy_skip( 'category_not_found', array( 'product_id' => $pid, 'category_id' => $tid ) ); } } if ( $current === $op['to'] ) { return array( 'bucket' => 'applied', 'product_id' => $pid, 'previous' => array( 'from' => $current ), 'to' => $op['to'], 'unchanged' => true ); } $set = wp_set_object_terms( $pid, $op['to'], AIASS_TAXONOMY, false ); if ( is_wp_error( $set ) ) { return array( 'bucket' => 'errors', 'product_id' => $pid, 'message' => 'The product could not be saved' ); } if ( function_exists( 'wc_delete_product_transients' ) ) { wc_delete_product_transients( $pid ); } return array( 'bucket' => 'applied', 'product_id' => $pid, 'previous' => array( 'from' => $current ), 'to' => $op['to'] ); } function aiass_taxonomy_reparent( $op ) { $cid = $op['category_id']; $term = aiass_taxonomy_term( $cid ); if ( ! $term ) { return aiass_taxonomy_skip( 'category_not_found', array( 'category_id' => $cid ) ); } $old_parent = (int) $term->parent; if ( null !== $op['expected_parent'] && $old_parent !== $op['expected_parent'] ) { return aiass_taxonomy_skip( 'parent_changed', array( 'category_id' => $cid, 'current_parent' => $old_parent ) ); } $parent = $op['parent_id']; if ( $parent > 0 ) { if ( ! aiass_taxonomy_term( $parent ) ) { return aiass_taxonomy_skip( 'category_not_found', array( 'category_id' => $parent ) ); } if ( aiass_taxonomy_is_ancestor( $cid, $parent ) ) { return aiass_taxonomy_skip( 'would_loop', array( 'category_id' => $cid, 'parent_id' => $parent ) ); } } if ( $parent === $old_parent ) { return array( 'bucket' => 'applied', 'category_id' => $cid, 'previous' => array( 'parent' => $old_parent ), 'parent_id' => $parent, 'unchanged' => true ); } $updated = wp_update_term( $cid, AIASS_TAXONOMY, array( 'parent' => $parent ) ); if ( is_wp_error( $updated ) ) { return array( 'bucket' => 'errors', 'category_id' => $cid, 'message' => 'The category could not be saved' ); } return array( 'bucket' => 'applied', 'category_id' => $cid, 'previous' => array( 'parent' => $old_parent ), 'parent_id' => $parent ); } function aiass_taxonomy_merge( $op ) { $cid = $op['category_id']; $into = $op['into_id']; $term = aiass_taxonomy_term( $cid ); $dest = aiass_taxonomy_term( $into ); if ( ! $term ) { return aiass_taxonomy_skip( 'category_not_found', array( 'category_id' => $cid ) ); } if ( ! $dest ) { return aiass_taxonomy_skip( 'category_not_found', array( 'category_id' => $into ) ); } if ( aiass_taxonomy_is_ancestor( $cid, $into ) ) { return aiass_taxonomy_skip( 'would_loop', array( 'category_id' => $cid, 'into_id' => $into ) ); } $product_ids = get_objects_in_term( $cid, AIASS_TAXONOMY ); $product_ids = is_wp_error( $product_ids ) ? array() : array_map( 'intval', $product_ids ); $moved = array(); foreach ( $product_ids as $pid ) { $had_into = in_array( $into, aiass_taxonomy_product_cats( $pid ), true ); $add = wp_set_object_terms( $pid, array( $into ), AIASS_TAXONOMY, true ); if ( is_wp_error( $add ) ) { continue; } wp_remove_object_terms( $pid, array( $cid ), AIASS_TAXONOMY ); if ( function_exists( 'wc_delete_product_transients' ) ) { wc_delete_product_transients( $pid ); } $moved[] = array( 'product_id' => $pid, 'had_into' => $had_into ); } $children = get_terms( array( 'taxonomy' => AIASS_TAXONOMY, 'parent' => $cid, 'hide_empty' => false, 'fields' => 'ids' ) ); $children = is_wp_error( $children ) ? array() : array_map( 'intval', $children ); foreach ( $children as $child ) { wp_update_term( $child, AIASS_TAXONOMY, array( 'parent' => $into ) ); } $previous = array( 'name' => $term->name, 'slug' => $term->slug, 'parent' => (int) $term->parent, 'products' => $moved, 'children' => $children, ); $deleted = wp_delete_term( $cid, AIASS_TAXONOMY ); if ( is_wp_error( $deleted ) || ! $deleted ) { return array( 'bucket' => 'errors', 'category_id' => $cid, 'message' => 'The category could not be removed', 'previous' => $previous ); } return array( 'bucket' => 'applied', 'category_id' => $cid, 'into_id' => $into, 'previous' => $previous ); } function aiass_taxonomy_unmerge( $op ) { if ( ! aiass_taxonomy_term( $op['into_id'] ) ) { return aiass_taxonomy_skip( 'category_not_found', array( 'category_id' => $op['into_id'] ) ); } $args = array( 'parent' => $op['parent'] ); if ( '' !== $op['slug'] ) { $args['slug'] = $op['slug']; } $created = wp_insert_term( $op['name'], AIASS_TAXONOMY, $args ); if ( is_wp_error( $created ) ) { if ( 'term_exists' === $created->get_error_code() ) { $existing = $created->get_error_data(); $new_id = is_array( $existing ) && isset( $existing['term_id'] ) ? (int) $existing['term_id'] : (int) $existing; } else { return array( 'bucket' => 'errors', 'message' => 'The category could not be recreated' ); } } else { $new_id = (int) $created['term_id']; } $returned = array(); foreach ( $op['products'] as $row ) { $pid = $row['product_id']; if ( ! wc_get_product( $pid ) ) { continue; } wp_set_object_terms( $pid, array( $new_id ), AIASS_TAXONOMY, true ); if ( ! $row['had_into'] ) { wp_remove_object_terms( $pid, array( $op['into_id'] ), AIASS_TAXONOMY ); } if ( function_exists( 'wc_delete_product_transients' ) ) { wc_delete_product_transients( $pid ); } $returned[] = $pid; } return array( 'bucket' => 'applied', 'category_id' => $new_id, 'into_id' => $op['into_id'], 'products' => $returned, 'previous' => array( 'merged' => true ) ); } function aiass_taxonomy_rename( $op ) { $cid = $op['category_id']; $term = aiass_taxonomy_term( $cid ); if ( ! $term ) { return aiass_taxonomy_skip( 'category_not_found', array( 'category_id' => $cid ) ); } if ( null !== $op['expected_name'] && $term->name !== $op['expected_name'] ) { return aiass_taxonomy_skip( 'name_changed', array( 'category_id' => $cid, 'current_name' => $term->name ) ); } if ( $term->name === $op['name'] ) { return array( 'bucket' => 'applied', 'category_id' => $cid, 'previous' => array( 'name' => $term->name ), 'name' => $op['name'], 'unchanged' => true ); } $updated = wp_update_term( $cid, AIASS_TAXONOMY, array( 'name' => $op['name'] ) ); if ( is_wp_error( $updated ) ) { return array( 'bucket' => 'errors', 'category_id' => $cid, 'message' => 'The category could not be renamed' ); } return array( 'bucket' => 'applied', 'category_id' => $cid, 'previous' => array( 'name' => $term->name ), 'name' => $op['name'] ); } define( 'AIASS_CONTENT_MAX_PRODUCTS', 50 ); function aiass_apply_content() { if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) { wp_send_json( array( 'error' => 'POST required' ), 405 ); } if ( ! function_exists( 'wc_get_product' ) ) { wp_send_json( array( 'error' => 'WooCommerce is not active' ), 500 ); } $data = aiass_get_json_data(); $rows = is_array( $data ) && isset( $data['products'] ) && is_array( $data['products'] ) ? $data['products'] : array(); if ( empty( $rows ) || count( $rows ) > AIASS_CONTENT_MAX_PRODUCTS ) { wp_send_json( array( 'error' => 'products must hold 1 to ' . AIASS_CONTENT_MAX_PRODUCTS . ' entries' ), 400 ); } $result = array( 'applied' => array(), 'skipped' => array(), 'errors' => array() ); foreach ( $rows as $row ) { $pid = is_array( $row ) && isset( $row['product_id'] ) ? absint( $row['product_id'] ) : 0; if ( $pid <= 0 ) { $result['skipped'][] = array( 'product_id' => $pid, 'reason' => 'invalid_id' ); continue; } $product = wc_get_product( $pid ); if ( ! $product || 'trash' === $product->get_status() ) { $result['skipped'][] = array( 'product_id' => $pid, 'reason' => 'not_found' ); continue; } try { $fields = aiass_content_write( $product, $row ); } catch ( Exception $e ) { aiass_insert_exception_record( $e ); $result['errors'][] = array( 'product_id' => $pid, 'message' => 'The product could not be saved' ); continue; } if ( empty( $fields ) ) { $result['skipped'][] = array( 'product_id' => $pid, 'reason' => 'nothing_to_write' ); continue; } $result['applied'][] = array( 'product_id' => $pid, 'fields' => $fields ); } wp_send_json( $result, 200 ); } function aiass_content_seo_keys() { if ( class_exists( 'RankMath' ) ) { return array( 'rank_math_title', 'rank_math_description', 'rank_math_focus_keyword' ); } if ( defined( 'AIOSEO_VERSION' ) ) { return array( '_aioseo_title', '_aioseo_description', null ); } return array( '_yoast_wpseo_title', '_yoast_wpseo_metadesc', '_yoast_wpseo_focuskw' ); } function aiass_content_write( $product, $row ) { $fields = array(); if ( array_key_exists( 'description', $row ) ) { $product->set_description( wp_kses_post( (string) $row['description'] ) ); $fields[] = 'description'; } if ( array_key_exists( 'short_description', $row ) ) { $product->set_short_description( wp_kses_post( (string) $row['short_description'] ) ); $fields[] = 'short_description'; } if ( ! empty( $fields ) ) { $product->save(); } list( $title_key, $meta_key, $keyphrase_key ) = aiass_content_seo_keys(); if ( array_key_exists( 'seo_keyphrase', $row ) && $keyphrase_key ) { update_post_meta( $product->get_id(), $keyphrase_key, sanitize_text_field( (string) $row['seo_keyphrase'] ) ); $fields[] = 'seo_keyphrase'; } if ( array_key_exists( 'seo_title', $row ) ) { update_post_meta( $product->get_id(), $title_key, sanitize_text_field( (string) $row['seo_title'] ) ); $fields[] = 'seo_title'; } if ( array_key_exists( 'seo_meta', $row ) ) { update_post_meta( $product->get_id(), $meta_key, sanitize_text_field( (string) $row['seo_meta'] ) ); $fields[] = 'seo_meta'; } return $fields; } define( 'AIASS_PRICE_MAX_PRODUCTS', 100 ); function aiass_apply_price() { if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) { wp_send_json( array( 'error' => 'POST required' ), 405 ); } if ( ! function_exists( 'wc_get_product' ) || ! function_exists( 'wc_get_price_decimals' ) ) { wp_send_json( array( 'error' => 'WooCommerce is not active' ), 500 ); } $data = aiass_get_json_data(); if ( ! is_array( $data ) ) { wp_send_json( array( 'error' => 'Invalid JSON payload' ), 400 ); } $change_id = isset( $data['change_id'] ) ? (string) $data['change_id'] : ''; if ( ! preg_match( '/^[A-Za-z0-9:@._-]{1,96}$/', $change_id ) ) { wp_send_json( array( 'error' => 'Invalid change_id' ), 400 ); } $mode = isset( $data['mode'] ) ? (string) $data['mode'] : 'apply'; if ( ! in_array( $mode, array( 'apply', 'revert' ), true ) ) { wp_send_json( array( 'error' => 'Invalid mode' ), 400 ); } $rows = isset( $data['products'] ) && is_array( $data['products'] ) ? $data['products'] : array(); if ( empty( $rows ) || count( $rows ) > AIASS_PRICE_MAX_PRODUCTS ) { wp_send_json( array( 'error' => 'products must hold 1 to ' . AIASS_PRICE_MAX_PRODUCTS . ' entries' ), 400 ); } $decimals = wc_get_price_decimals(); $result = array( 'change_id' => $change_id, 'mode' => $mode, 'applied' => array(), 'skipped' => array(), 'errors' => array() ); $seen = array(); foreach ( $rows as $row ) { $pid = is_array( $row ) && isset( $row['product_id'] ) ? absint( $row['product_id'] ) : 0; if ( $pid <= 0 || ! is_array( $row ) || ! isset( $row['new_price'] ) || ! is_numeric( $row['new_price'] ) || (float) $row['new_price'] <= 0 ) { $result['skipped'][] = array( 'product_id' => $pid, 'reason' => 'invalid_row' ); continue; } if ( isset( $seen[ $pid ] ) ) { $result['skipped'][] = array( 'product_id' => $pid, 'reason' => 'duplicate' ); continue; } $seen[ $pid ] = true; $product = wc_get_product( $pid ); if ( ! $product || 'trash' === $product->get_status() ) { $result['skipped'][] = array( 'product_id' => $pid, 'reason' => 'not_found' ); continue; } $targets = aiass_promotion_targets( $product ); if ( empty( $targets ) ) { $result['skipped'][] = array( 'product_id' => $pid, 'reason' => 'no_variations' ); continue; } $expected = ( isset( $row['expected_price'] ) && is_numeric( $row['expected_price'] ) ) ? (float) $row['expected_price'] : null; $new_price = round( (float) $row['new_price'], $decimals ); foreach ( $targets as $target ) { $target_id = $target->get_id(); if ( $target_id !== $pid && isset( $seen[ $target_id ] ) ) { continue; } $seen[ $target_id ] = true; try { $outcome = aiass_price_step( $target, $expected, $new_price, $decimals ); } catch ( Exception $e ) { aiass_insert_exception_record( $e ); $outcome = array( 'bucket' => 'errors', 'product_id' => $target_id, 'parent_id' => $target->get_parent_id(), 'message' => 'The product could not be saved' ); } $bucket = $outcome['bucket']; unset( $outcome['bucket'] ); $result[ $bucket ][] = $outcome; } if ( $product->is_type( 'variable' ) && class_exists( 'WC_Product_Variable' ) ) { WC_Product_Variable::sync( $product->get_id() ); } } wp_send_json( $result, 200 ); } function aiass_price_step( $product, $expected, $new_price, $decimals ) { $pid = $product->get_id(); $parent_id = $product->get_parent_id(); $raw = $product->get_regular_price( 'edit' ); if ( '' === $raw || null === $raw ) { return aiass_promotion_skip( $pid, 'no_regular_price', $parent_id ); } $current = (float) $raw; if ( null !== $expected && wc_format_decimal( $current, $decimals ) !== wc_format_decimal( $expected, $decimals ) ) { return aiass_promotion_skip( $pid, 'price_changed', $parent_id, array( 'current_price' => wc_format_decimal( $current, $decimals ) ) ); } if ( $product->is_on_sale( 'edit' ) ) { return aiass_promotion_skip( $pid, 'on_sale', $parent_id ); } if ( wc_format_decimal( $current, $decimals ) === wc_format_decimal( $new_price, $decimals ) ) { return array( 'bucket' => 'applied', 'product_id' => $pid, 'parent_id' => $parent_id, 'previous_price' => wc_format_decimal( $current, $decimals ), 'new_price' => wc_format_decimal( $new_price, $decimals ), 'unchanged' => true ); } $product->set_regular_price( wc_format_decimal( $new_price, $decimals ) ); $product->set_price( wc_format_decimal( $new_price, $decimals ) ); $product->save(); return array( 'bucket' => 'applied', 'product_id' => $pid, 'parent_id' => $parent_id, 'name' => $product->get_name(), 'previous_price' => wc_format_decimal( $current, $decimals ), 'new_price' => wc_format_decimal( $new_price, $decimals ) ); } define( 'AIASS_SENDMAIL_MAX_RECIPIENTS', 10 ); function aiass_send_mail() { if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) { wp_send_json( array( 'error' => 'POST required' ), 405 ); } $data = aiass_get_json_data(); if ( ! is_array( $data ) ) { wp_send_json( array( 'error' => 'Invalid JSON payload' ), 400 ); } $raw_to = isset( $data['to'] ) ? $data['to'] : array(); $to = array(); foreach ( is_array( $raw_to ) ? $raw_to : array( $raw_to ) as $address ) { $address = sanitize_email( (string) $address ); if ( $address && is_email( $address ) ) { $to[] = $address; } } $to = array_values( array_unique( $to ) ); if ( empty( $to ) ) { wp_send_json( array( 'error' => 'to must hold at least one valid email address' ), 400 ); } if ( count( $to ) > AIASS_SENDMAIL_MAX_RECIPIENTS ) { wp_send_json( array( 'error' => 'to may hold at most ' . AIASS_SENDMAIL_MAX_RECIPIENTS . ' addresses' ), 400 ); } $subject = isset( $data['subject'] ) ? sanitize_text_field( (string) $data['subject'] ) : ''; if ( '' === $subject || mb_strlen( $subject ) > 250 ) { wp_send_json( array( 'error' => 'subject is required (max 250 characters)' ), 400 ); } $html = ! empty( $data['html'] ); $body = isset( $data['body'] ) ? (string) $data['body'] : ''; $body = $html ? wp_kses_post( $body ) : wp_strip_all_tags( $body ); if ( '' === trim( $body ) ) { wp_send_json( array( 'error' => 'body is required' ), 400 ); } $headers = array(); if ( $html ) { $headers[] = 'Content-Type: text/html; charset=UTF-8'; } if ( ! empty( $data['reply_to'] ) && is_email( (string) $data['reply_to'] ) ) { $headers[] = 'Reply-To: ' . sanitize_email( (string) $data['reply_to'] ); } $failure = null; $catch = function ( $error ) use ( &$failure ) { $failure = is_wp_error( $error ) ? $error->get_error_message() : 'unknown'; }; add_action( 'wp_mail_failed', $catch ); try { $sent = (bool) wp_mail( $to, $subject, $body, $headers ); } catch ( Exception $e ) { aiass_insert_exception_record( $e ); $sent = false; } remove_action( 'wp_mail_failed', $catch ); if ( ! $sent ) { aiass_send_error( 'sendmail failed: ' . ( $failure ? $failure : 'wp_mail returned false' ) ); } wp_send_json( array( 'sent' => $sent, 'to' => $to ), 200 ); } function aiass_get_shop_branding() { wp_send_json( array( 'logo_url' => aiass_shop_logo_url(), 'blogname' => (string) get_option( 'blogname' ), 'home_url' => get_home_url(), ), 200 ); } $aiass_plugin_basename = plugin_basename(__DIR__ . '/aissistant-plugin.php'); function aiass_check_plugin_update() { global $wp_version, $aiass_plugin_basename; $plugin_data = get_plugin_data(__DIR__ . '/aissistant-plugin.php'); $plugin_version = $plugin_data['Version']; $compatible_wp_version = '6.0'; if (version_compare($wp_version, $compatible_wp_version, '<')) { add_action('admin_notices', 'aiass_compat_notice'); return; } $update_check_url = 'https://download.aissistant.site/woo-plugin-updates.php'; $response = wp_remote_get($update_check_url); if (is_wp_error($response)) { return; } $body = wp_remote_retrieve_body($response); $update_data = json_decode($body, true); if ($update_data && version_compare($update_data['new_version'], $plugin_version, '>')) { add_action('admin_notices', 'aiass_update_notice'); add_action('after_plugin_row_' . $aiass_plugin_basename, 'aiass_plugin_update_row'); } } function aiass_compat_notice() { echo '
Your current WordPress version is not compatible with Aissistant Plugin.
'; echo 'Your Virtual Aissistant has new skills!! Update now.
'; echo '' . esc_html( 'This site could not be connected. Please try again in a few minutes.' ) . '
' . esc_html( 'WooCommerce is not active on this site. Activate WooCommerce, then connect the site to AIssistant.' ) . '
' . esc_html( 'This site is not connected to AIssistant yet.' ) . '
'; echo '' . esc_html( 'Connecting sends this site\'s address, language, currency and database table names to AIssistant. The site becomes its own shop, with its own services, billing and trial.' ) . '
'; echo ''; } add_action( 'admin_post_aiass_connect_site', 'aiass_handle_connect_site' ); function aiass_handle_connect_site() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'You are not allowed to connect this site.', 403 ); } check_admin_referer( 'aiass_connect_site' ); $state = aiass_site_state(); if ( $state === 'pending' || $state === 'failed' ) { $state = aiass_enroll_current_site(); } wp_safe_redirect( add_query_arg( 'aia_connect', $state === 'connected' ? 'ok' : 'failed', admin_url( 'admin.php?page=aissistant' ) ) ); exit; } add_action( 'network_admin_menu', 'aiass_network_admin_menu' ); function aiass_network_admin_menu() { if ( aiass_is_network_active() ) { add_menu_page( 'AIssistant Shop Manager', 'AIssistant', 'manage_network_options', 'aissistant-network', 'aiass_render_network_page', 'dashicons-admin-generic' ); } } function aiass_render_network_page() { if ( ! current_user_can( 'manage_network_options' ) ) { return; } $result = isset( $_GET['aia_connect'] ) ? sanitize_key( wp_unslash( $_GET['aia_connect'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended echo '' . esc_html( 'Every site of this network is its own shop in AIssistant, with its own services, billing and trial.' ) . '
'; if ( $result === 'failed' ) { echo '' . esc_html( 'Some sites could not be connected. Please try again in a few minutes.' ) . '
' . esc_html( 'Done.' ) . '
| Site | Status | |
|---|---|---|
| ' . esc_html( $url ) . ' | ' . esc_html( aiass_state_label( $state ) ) . ' | '; if ( $state === 'pending' || $state === 'failed' ) { $waiting++; echo ''; } echo ' |