'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 '
'; echo '

Your current WordPress version is not compatible with Aissistant Plugin.

'; echo '
'; } function aiass_update_notice() { global $aiass_plugin_basename; $base_url = admin_url('update.php?action=upgrade-plugin&plugin=' . urlencode($aiass_plugin_basename)); $update_url = wp_nonce_url($base_url, 'upgrade-plugin_' . $aiass_plugin_basename); echo '
'; echo '

Your Virtual Aissistant has new skills!! Update now.

'; echo '
'; } function aiass_plugin_update_row($plugin_file) { $update_url = wp_nonce_url(admin_url('update.php?action=upgrade-plugin&plugin=' . $plugin_file), 'upgrade-plugin_' . dirname($plugin_file)); echo ''; echo '

Your Virtual Aissistant has new skills!! Update now.

'; echo ''; } add_action('admin_init', 'aiass_check_plugin_update'); add_action('admin_enqueue_scripts', 'aiass_enqueue_admin_assets'); function aiass_enqueue_admin_assets( $hook_suffix ) { if ( 'toplevel_page_aissistant' !== $hook_suffix || ! aiass_get_token() ) { return; } wp_enqueue_script('aiass-admin-js', plugin_dir_url(__FILE__) . 'assets/js/aia.js', array('jquery'), AIASS_PLUGIN_VERSION, true); wp_enqueue_style('aiass-admin-css', plugin_dir_url(__FILE__) . 'assets/css/aia.css', array(), AIASS_PLUGIN_VERSION); wp_localize_script( 'aiass-admin-js', 'aiassData', array( 'url' => aiass_get_url(), 'token' => aiass_get_token(), 'client' => get_option( 'aia_camacam_client', '' ), 'locale' => get_user_locale(), ) ); } add_action('admin_menu', 'aiass_admin_menu', 20); function aiass_admin_menu() { global $menu; $woo_position = null; foreach ( $menu as $pos => $item ) { if ( isset( $item[2] ) && $item[2] === 'woocommerce' ) { $woo_position = (float) $pos; break; } } $position = ( $woo_position !== null ) ? $woo_position - 1 : 54; add_menu_page( 'AIssistant Shop Manager', 'AIssistant Shop Manager', 'manage_options', 'aissistant', 'aiass_admin_page', 'dashicons-admin-generic', $position ); } function aiass_admin_page() { if ( ! aiass_get_token() ) { aiass_render_connect_page(); } } define( 'AIASS_NETWORK_BATCH', 25 ); define( 'AIASS_ADMIN_BATCH', 3 ); define( 'AIASS_SCAN_EVERY', HOUR_IN_SECONDS ); define( 'AIASS_RETRY_AFTER', HOUR_IN_SECONDS ); function aiass_initialize( $token, $client ) { update_option( 'aia_camacam_token', $token ); update_option( 'aia_camacam_client', $client ); } function aiass_is_switched() { return is_multisite() && function_exists( 'ms_is_switched' ) && ms_is_switched(); } function aiass_site_locale() { if ( ! aiass_is_switched() ) { return get_locale(); } $locale = get_option( 'WPLANG' ); if ( ! $locale ) { $locale = get_site_option( 'WPLANG' ); } if ( ! $locale && defined( 'WPLANG' ) ) { $locale = WPLANG; } return ( is_string( $locale ) && $locale !== '' ) ? $locale : 'en_US'; } function aiass_is_network_active() { if ( ! is_multisite() ) { return false; } if ( ! function_exists( 'is_plugin_active_for_network' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } return is_plugin_active_for_network( plugin_basename( AIASS_PLUGIN_FILE ) ); } function aiass_site_ids() { if ( ! is_multisite() ) { return array( get_current_blog_id() ); } if ( function_exists( 'get_sites' ) ) { return array_map( 'intval', get_sites( array( 'fields' => 'ids', 'number' => 0, 'network_id' => get_current_network_id(), 'archived' => 0, 'deleted' => 0, 'spam' => 0, ) ) ); } $ids = array(); foreach ( wp_get_sites( array( 'limit' => 0 ) ) as $site ) { if ( empty( $site['archived'] ) && empty( $site['deleted'] ) && empty( $site['spam'] ) ) { $ids[] = (int) $site['blog_id']; } } return $ids; } function aiass_woocommerce_active_here() { $wc = 'woocommerce/woocommerce.php'; if ( in_array( $wc, (array) get_option( 'active_plugins', array() ), true ) ) { return true; } if ( is_multisite() ) { $network = (array) get_site_option( 'active_sitewide_plugins', array() ); return isset( $network[ $wc ] ); } return false; } function aiass_hpos_enabled_here() { if ( ! aiass_is_switched() && class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) ) { return \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); } return get_option( 'woocommerce_custom_orders_table_enabled' ) === 'yes'; } function aiass_site_state() { if ( get_option( 'aia_camacam_token' ) ) { return 'connected'; } if ( ! aiass_woocommerce_active_here() ) { return 'no_woocommerce'; } return in_array( get_option( 'aia_enroll_state' ), array( 'failed', 'invalid' ), true ) ? 'failed' : 'pending'; } function aiass_retry_due() { return time() - (int) get_option( 'aia_enroll_attempt', 0 ) >= AIASS_RETRY_AFTER; } function aiass_set_enroll_state( $state ) { update_option( 'aia_enroll_state', $state ); update_option( 'aia_enroll_attempt', time() ); return $state; } function aiass_ensure_errors_table() { global $wpdb; $table_name = aiass_errors_table(); if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) ) ) != $table_name ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, date datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, content text NOT NULL, PRIMARY KEY (id) ) $charset_collate;"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( $sql ); } } function aiass_ensure_service_user() { $email = 'aia@aissistant.shop'; $aiausr = get_user_by( 'email', $email ); if ( ! $aiausr ) { wp_insert_user( array( 'user_login' => $email, 'user_email' => $email, 'first_name' => 'Aia', 'last_name' => 'Assistant', 'user_pass' => wp_generate_password(), ) ); return; } if ( is_multisite() && ! is_user_member_of_blog( $aiausr->ID, get_current_blog_id() ) ) { add_user_to_blog( get_current_blog_id(), $aiausr->ID, get_option( 'default_role', 'subscriber' ) ); } } function aiass_send_enroll_data() { global $wpdb; $current_user = wp_get_current_user(); aiass_ensure_errors_table(); aiass_ensure_service_user(); if ( aiass_hpos_enabled_here() ) { $table_name = $wpdb->prefix . 'wc_orders'; $date_column = 'date_created_gmt'; $status_column = 'status'; } else { $table_name = $wpdb->prefix . 'posts'; $date_column = 'post_date_gmt'; $status_column = 'post_status'; } $curr_sites = array(); if ( is_multisite() ) { foreach ( aiass_site_ids() as $blog_id ) { switch_to_blog( $blog_id ); $curr_sites[] = array( 'blog_id' => $blog_id, 'prefix' => $wpdb->prefix, 'domain' => wp_parse_url( home_url(), PHP_URL_HOST ) . ( wp_parse_url( home_url(), PHP_URL_PATH ) ?: '/' ), ); restore_current_blog(); } } $locale = aiass_site_locale(); $data = array( 'dbprefix' => $wpdb->prefix, 'db_prefix' => $wpdb->prefix, 'system' => 'wooCommerce', 'orders_table' => $table_name, 'date_column' => $date_column, 'status_column' => $status_column, 'is_multisite' => is_multisite(), 'multisites_prefixes' => json_encode( $curr_sites ), 'blog_id' => get_current_blog_id(), 'base_prefix' => $wpdb->base_prefix, 'users_table' => $wpdb->users, 'usermeta_table' => $wpdb->usermeta, 'network_home' => is_multisite() ? network_home_url() : home_url(), 'subdomain_install' => is_multisite() && is_subdomain_install(), 'home_url' => get_home_url(), 'site_home' => get_option( 'home' ), 'site_url' => get_option( 'siteurl' ), 'woocommerce_email' => get_option( 'woocommerce_email_from' ), 'current_user_id' => get_current_user_id(), 'current_user_email' => $current_user->user_email, 'userLocale' => get_user_locale(), 'siteLocale' => $locale, 'shop_locale' => $locale, 'blogname' => get_option( 'blogname' ), 'shop_logo_url' => aiass_shop_logo_url(), 'blogdescription' => get_option( 'blogdescription' ), 'admin_email' => get_option( 'admin_email' ), 'users_can_register' => get_option( 'users_can_register' ), 'default_category' => get_option( 'default_category' ), 'timezone_string' => get_option( 'timezone_string' ), 'woocommerce_default_country' => get_option( 'woocommerce_default_country' ), 'woocommerce_currency' => get_option( 'woocommerce_currency' ), 'woocommerce_allowed_countries' => get_option( 'woocommerce_allowed_countries' ), ); return aiass_send_data( aiass_get_url() . '/api/v1/enroll', $data ); } function aiass_refresh_rewrite_rules() { if ( aiass_is_switched() ) { delete_option( 'rewrite_rules' ); delete_option( 'aia_rules_for' ); return; } aiass_add_rewrite_rules_perm(); flush_rewrite_rules(); update_option( 'aia_rules_for', get_option( 'aia_camacam_client' ) ); } add_action( 'wp_loaded', 'aiass_maybe_rebuild_rules' ); function aiass_maybe_rebuild_rules() { $client = get_option( 'aia_camacam_client' ); if ( $client && get_option( 'aia_rules_for' ) !== $client ) { flush_rewrite_rules( false ); update_option( 'aia_rules_for', $client ); } } function aiass_shop_logo_url() { $logo_id = (int) get_theme_mod( 'custom_logo' ); if ( $logo_id ) { $url = wp_get_attachment_image_url( $logo_id, 'full' ); if ( $url ) { return (string) $url; } } if ( function_exists( 'get_site_icon_url' ) ) { return (string) get_site_icon_url( 512 ); } return ''; } function aiass_enroll_current_site() { $response = aiass_send_enroll_data(); if ( is_wp_error( $response ) ) { aiass_insert_exception_record( array( 'enroll' => $response->get_error_message() ) ); return aiass_set_enroll_state( 'failed' ); } $body = json_decode( wp_remote_retrieve_body( $response ) ); if ( ! is_object( $body ) || empty( $body->client ) || empty( $body->token ) ) { aiass_insert_exception_record( array( 'enroll' => 'invalid response', 'status' => wp_remote_retrieve_response_code( $response ), ) ); return aiass_set_enroll_state( 'invalid' ); } aiass_initialize( sanitize_text_field( $body->token ), sanitize_text_field( $body->client ) ); aiass_refresh_rewrite_rules(); return aiass_set_enroll_state( 'connected' ); } function aiass_enroll_network( $limit = AIASS_NETWORK_BATCH, $retry_failed = false ) { $done = 0; $left = 0; foreach ( aiass_site_ids() as $blog_id ) { switch_to_blog( $blog_id ); $state = aiass_site_state(); $due = $state === 'pending' || ( $state === 'failed' && ( $retry_failed || aiass_retry_due() ) ); if ( $due ) { if ( $done < $limit ) { aiass_enroll_current_site(); $done++; } else { $left++; } } restore_current_blog(); } if ( $left === 0 ) { update_site_option( 'aia_network_scan_at', time() ); } elseif ( ! wp_next_scheduled( 'aiass_enroll_network_event' ) ) { wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'aiass_enroll_network_event' ); } return $left; } add_action( 'aiass_enroll_network_event', 'aiass_enroll_network_cron' ); function aiass_enroll_network_cron() { if ( aiass_is_network_active() ) { aiass_enroll_network( AIASS_NETWORK_BATCH ); } } add_action( 'admin_init', 'aiass_network_catch_up' ); function aiass_network_catch_up() { if ( wp_doing_ajax() || ! aiass_is_network_active() ) { return; } if ( time() - (int) get_site_option( 'aia_network_scan_at', 0 ) < AIASS_SCAN_EVERY ) { return; } if ( get_site_transient( 'aia_network_scan_lock' ) ) { return; } set_site_transient( 'aia_network_scan_lock', 1, 5 * MINUTE_IN_SECONDS ); aiass_enroll_network( AIASS_ADMIN_BATCH ); delete_site_transient( 'aia_network_scan_lock' ); } function aiass_new_site_added() { if ( aiass_is_network_active() ) { delete_site_option( 'aia_network_scan_at' ); } } if ( version_compare( $GLOBALS['wp_version'], '5.1', '>=' ) ) { add_action( 'wp_initialize_site', 'aiass_new_site_added', 200 ); } else { add_action( 'wpmu_new_blog', 'aiass_new_site_added' ); } function aiass_activate( $network_wide = false ) { if ( is_multisite() && $network_wide ) { aiass_enroll_network( AIASS_NETWORK_BATCH, true ); return; } if ( ! class_exists( 'WooCommerce' ) ) { wp_die( 'Aia Assistant requires WooCommerce to be installed and active. Please install WooCommerce first.' ); } $state = aiass_enroll_current_site(); if ( $state === 'failed' ) { wp_die( 'AIA is under repair work at the moment. Coming back in a tick!' ); } if ( $state === 'invalid' ) { wp_die( 'AIA enrollment response was invalid. Please try again.' ); } } function aiass_send_deactivation() { $data = array( 'system' => 'woocommerce', 'home_url' => get_home_url(), 'site_home' => get_option( 'home' ), 'site_url' => get_option( 'siteurl' ), ); return aiass_send_data( aiass_get_url() . '/api/v1/enroll/deactivate', $data ); } function aiass_plugin_deactivate( $network_wide = false ) { if ( is_multisite() && $network_wide ) { foreach ( aiass_site_ids() as $blog_id ) { switch_to_blog( $blog_id ); if ( get_option( 'aia_camacam_token' ) ) { aiass_send_deactivation(); delete_option( 'rewrite_rules' ); delete_option( 'aia_rules_for' ); } restore_current_blog(); } delete_site_option( 'aia_network_scan_at' ); wp_clear_scheduled_hook( 'aiass_enroll_network_event' ); return; } $result = aiass_send_deactivation(); flush_rewrite_rules(); delete_option( 'aia_rules_for' ); return $result; } function aiass_state_label( $state ) { $labels = array( 'connected' => 'Connected', 'pending' => 'Waiting to connect', 'failed' => 'Connection failed', 'no_woocommerce' => 'WooCommerce is not active', ); return isset( $labels[ $state ] ) ? $labels[ $state ] : $state; } function aiass_render_connect_page() { $state = aiass_site_state(); $result = isset( $_GET['aia_connect'] ) ? sanitize_key( wp_unslash( $_GET['aia_connect'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended echo '

AIssistant Shop Manager

'; if ( $result === 'failed' ) { echo '

' . esc_html( 'This site could not be connected. Please try again in a few minutes.' ) . '

'; } if ( $state === 'no_woocommerce' ) { echo '

' . esc_html( 'WooCommerce is not active on this site. Activate WooCommerce, then connect the site to AIssistant.' ) . '

'; return; } echo '

' . 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 '
'; echo ''; wp_nonce_field( 'aiass_connect_site' ); submit_button( 'Connect this site', 'primary', 'submit', false ); 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 '

AIssistant Shop Manager

'; 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.' ) . '

'; } elseif ( $result === 'ok' ) { echo '

' . esc_html( 'Done.' ) . '

'; } $waiting = 0; echo ''; foreach ( aiass_site_ids() as $blog_id ) { switch_to_blog( $blog_id ); $url = home_url(); $state = aiass_site_state(); restore_current_blog(); echo ''; } echo '
SiteStatus
' . esc_html( $url ) . '' . esc_html( aiass_state_label( $state ) ) . ''; if ( $state === 'pending' || $state === 'failed' ) { $waiting++; echo '
'; echo ''; echo ''; wp_nonce_field( 'aiass_connect_network_site' ); submit_button( 'Connect', 'secondary small', 'submit', false ); echo '
'; } echo '
'; if ( $waiting > 1 ) { echo '
'; echo ''; echo ''; wp_nonce_field( 'aiass_connect_network_site' ); submit_button( 'Connect all waiting sites', 'primary', 'submit', false ); echo '
'; } echo '
'; } add_action( 'admin_post_aiass_connect_network_site', 'aiass_handle_connect_network_site' ); function aiass_handle_connect_network_site() { if ( ! is_multisite() || ! current_user_can( 'manage_network_options' ) ) { wp_die( 'You are not allowed to connect sites of this network.', 403 ); } check_admin_referer( 'aiass_connect_network_site' ); $target = isset( $_POST['blog_id'] ) ? sanitize_key( wp_unslash( $_POST['blog_id'] ) ) : ''; $ok = true; if ( $target === 'all' ) { aiass_enroll_network( AIASS_NETWORK_BATCH, true ); foreach ( aiass_site_ids() as $blog_id ) { switch_to_blog( $blog_id ); $ok = $ok && ! in_array( aiass_site_state(), array( 'pending', 'failed' ), true ); restore_current_blog(); } } elseif ( in_array( (int) $target, aiass_site_ids(), true ) ) { switch_to_blog( (int) $target ); $state = aiass_site_state(); if ( $state === 'pending' || $state === 'failed' ) { $state = aiass_enroll_current_site(); } restore_current_blog(); $ok = $state === 'connected'; } else { $ok = false; } wp_safe_redirect( add_query_arg( 'aia_connect', $ok ? 'ok' : 'failed', network_admin_url( 'admin.php?page=aissistant-network' ) ) ); exit; } if ( ! defined( 'ABSPATH' ) ) { exit; } define( 'AIASS_WAREHOUSE_MAX_ITEMS', 500 ); define( 'AIASS_ORDER_NOTIFY_META', '_aiass_order_notified' ); define( 'AIASS_ORDER_NOTIFY_RETRIES', 5 ); function aiass_warehouse_payload( $command ) { 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 ) || ! isset( $data['command'] ) || $data['command'] !== $command || ! isset( $data['data'] ) || ! is_array( $data['data'] ) ) { wp_send_json( array( 'error' => 'Invalid JSON payload' ), 400 ); } return $data['data']; } function aiass_warehouse_items( $payload ) { $items = isset( $payload['items'] ) && is_array( $payload['items'] ) ? $payload['items'] : array(); if ( empty( $items ) || count( $items ) > AIASS_WAREHOUSE_MAX_ITEMS ) { wp_send_json( array( 'error' => 'items must hold 1 to ' . AIASS_WAREHOUSE_MAX_ITEMS . ' entries' ), 400 ); } return $items; } function aiass_warehouse_sku( $row ) { return is_array( $row ) && isset( $row['sku'] ) ? trim( sanitize_text_field( (string) $row['sku'] ) ) : ''; } function aiass_update_stocks() { $items = aiass_warehouse_items( aiass_warehouse_payload( 'updatestocks' ) ); $result = array( 'updated' => 0, 'unchanged' => 0, 'not_found' => 0, 'errors' => 0 ); foreach ( $items as $row ) { $sku = aiass_warehouse_sku( $row ); if ( $sku === '' || ! isset( $row['qty'] ) || ! is_numeric( $row['qty'] ) ) { $result['errors']++; continue; } $product_id = wc_get_product_id_by_sku( $sku ); $product = $product_id ? wc_get_product( $product_id ) : null; if ( ! $product ) { $result['not_found']++; continue; } $qty = wc_stock_amount( $row['qty'] ); if ( true === $product->get_manage_stock() && (float) $product->get_stock_quantity() === (float) $qty ) { $result['unchanged']++; continue; } try { $product->set_manage_stock( true ); $product->set_stock_quantity( $qty ); $product->save(); $result['updated']++; } catch ( Exception $e ) { $result['errors']++; } } wp_send_json( $result ); } function aiass_create_draft_products() { $items = aiass_warehouse_items( aiass_warehouse_payload( 'createdraftproducts' ) ); $result = array( 'created' => 0, 'skipped_existing' => 0, 'errors' => 0, 'created_skus' => array() ); foreach ( $items as $row ) { $sku = aiass_warehouse_sku( $row ); if ( $sku === '' ) { $result['errors']++; continue; } if ( wc_get_product_id_by_sku( $sku ) ) { $result['skipped_existing']++; continue; } $name = isset( $row['name'] ) ? sanitize_text_field( (string) $row['name'] ) : ''; $description = isset( $row['description'] ) ? wp_kses_post( (string) $row['description'] ) : ''; $category_id = isset( $row['category_id'] ) ? absint( $row['category_id'] ) : 0; try { $product = new WC_Product_Simple(); $product->set_name( $name !== '' ? $name : $sku ); $product->set_sku( $sku ); if ( $description !== '' ) { $product->set_description( $description ); } if ( isset( $row['price'] ) && is_numeric( $row['price'] ) && (float) $row['price'] > 0 ) { $product->set_regular_price( wc_format_decimal( $row['price'] ) ); } $product->set_manage_stock( true ); $product->set_stock_quantity( isset( $row['qty'] ) && is_numeric( $row['qty'] ) ? wc_stock_amount( $row['qty'] ) : 0 ); if ( $category_id > 0 && term_exists( $category_id, 'product_cat' ) ) { $product->set_category_ids( array( $category_id ) ); } $product->set_status( 'draft' ); $product->save(); $result['created']++; $result['created_skus'][] = $sku; } catch ( Exception $e ) { $result['errors']++; } } wp_send_json( $result ); } function aiass_get_categories() { aiass_warehouse_payload( 'getcategories' ); $terms = get_terms( array( 'taxonomy' => 'product_cat', 'hide_empty' => false, 'number' => 0 ) ); $rows = array(); if ( ! is_wp_error( $terms ) ) { foreach ( $terms as $term ) { $rows[] = array( 'id' => (int) $term->term_id, 'name' => $term->name, 'parent_id' => (int) $term->parent, 'count' => (int) $term->count, ); } } wp_send_json( array( 'categories' => $rows ) ); } function aiass_create_categories() { $items = aiass_warehouse_items( aiass_warehouse_payload( 'createcategories' ) ); $result = array( 'created' => array(), 'existing' => array(), 'errors' => array() ); foreach ( $items as $row ) { $key = is_array( $row ) && isset( $row['key'] ) ? (string) $row['key'] : ''; $name = is_array( $row ) && isset( $row['name'] ) ? trim( sanitize_text_field( (string) $row['name'] ) ) : ''; $parent = is_array( $row ) && isset( $row['parent_id'] ) ? absint( $row['parent_id'] ) : 0; $google = is_array( $row ) && isset( $row['google_path'] ) ? sanitize_text_field( (string) $row['google_path'] ) : ''; if ( $key === '' || $name === '' ) { $result['errors'][] = array( 'key' => $key, 'message' => 'key and name are required' ); continue; } if ( $parent > 0 && ! term_exists( $parent, 'product_cat' ) ) { $parent = 0; } $existing = term_exists( $name, 'product_cat', $parent ); if ( is_array( $existing ) && ! empty( $existing['term_id'] ) ) { $term = get_term( (int) $existing['term_id'], 'product_cat' ); $result['existing'][] = array( 'key' => $key, 'id' => (int) $term->term_id, 'name' => $term->name, 'parent_id' => (int) $term->parent ); continue; } $inserted = wp_insert_term( $name, 'product_cat', array( 'parent' => $parent ) ); if ( is_wp_error( $inserted ) ) { $result['errors'][] = array( 'key' => $key, 'message' => $inserted->get_error_message() ); continue; } if ( $google !== '' ) { update_term_meta( (int) $inserted['term_id'], '_aiass_google_taxonomy', $google ); } $result['created'][] = array( 'key' => $key, 'id' => (int) $inserted['term_id'], 'name' => $name, 'parent_id' => $parent ); } delete_transient( 'wc_term_counts' ); wp_send_json( $result ); } function aiass_get_order_for_warehouse() { $payload = aiass_warehouse_payload( 'getorder' ); $order_id = isset( $payload['order_id'] ) ? absint( $payload['order_id'] ) : 0; $order = $order_id ? wc_get_order( $order_id ) : false; if ( ! $order || ! is_a( $order, 'WC_Order' ) ) { wp_send_json( array( 'error' => 'Order not found' ), 404 ); } $items = array(); foreach ( $order->get_items() as $item ) { $product = $item->get_product(); $items[] = array( 'sku' => $product ? $product->get_sku() : '', 'qty' => $item->get_quantity(), 'subtotal' => (float) $item->get_subtotal(), 'total' => (float) $item->get_total(), ); } wp_send_json( array( 'order' => array( 'id' => $order->get_id(), 'number' => (string) $order->get_order_number(), 'status' => $order->get_status(), 'total' => (float) $order->get_total(), 'shipping_total' => (float) $order->get_shipping_total(), 'shipping_tax' => (float) $order->get_shipping_tax(), 'discount_total' => (float) $order->get_total_discount(), 'payment_method_title' => $order->get_payment_method_title(), 'customer_ip' => $order->get_customer_ip_address(), 'billing' => array( 'first_name' => $order->get_billing_first_name(), 'last_name' => $order->get_billing_last_name(), 'email' => $order->get_billing_email(), 'phone' => $order->get_billing_phone(), 'address_1' => $order->get_billing_address_1(), 'address_2' => $order->get_billing_address_2(), ), 'items' => $items, ) ) ); } add_action( 'woocommerce_new_order', 'aiass_queue_order_notification', 20, 1 ); add_action( 'woocommerce_order_status_changed', 'aiass_queue_order_notification', 20, 1 ); add_action( 'aiass_send_order_notification', 'aiass_send_order_notification', 10, 2 ); function aiass_queue_order_notification( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order || ! is_a( $order, 'WC_Order' ) || 'checkout-draft' === $order->get_status() ) { return; } if ( $order->get_meta( AIASS_ORDER_NOTIFY_META ) || ! aiass_get_token() ) { return; } $order->update_meta_data( AIASS_ORDER_NOTIFY_META, 'queued' ); $order->save_meta_data(); if ( function_exists( 'as_enqueue_async_action' ) ) { as_enqueue_async_action( 'aiass_send_order_notification', array( $order->get_id(), 0 ), 'aiassistant' ); } else { aiass_send_order_notification( $order->get_id(), AIASS_ORDER_NOTIFY_RETRIES ); } } function aiass_send_order_notification( $order_id, $attempt = 0 ) { $order = wc_get_order( $order_id ); if ( ! $order ) { return; } $response = wp_remote_post( aiass_get_url() . '/api/v1/warehouse/orders', array( 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8', 'aiatoken' => aiass_get_token(), ), 'body' => wp_json_encode( array( 'order_id' => $order->get_id(), 'order_number' => (string) $order->get_order_number(), ) ), 'timeout' => 15, ) ); $code = is_wp_error( $response ) ? 0 : (int) wp_remote_retrieve_response_code( $response ); if ( $code >= 200 && $code < 300 ) { $order->update_meta_data( AIASS_ORDER_NOTIFY_META, 'sent' ); $order->save_meta_data(); } elseif ( $attempt < AIASS_ORDER_NOTIFY_RETRIES && function_exists( 'as_schedule_single_action' ) ) { as_schedule_single_action( time() + 300, 'aiass_send_order_notification', array( $order->get_id(), $attempt + 1 ), 'aiassistant' ); } } register_activation_hook(__FILE__, 'aiass_activate'); register_deactivation_hook(__FILE__, 'aiass_plugin_deactivate'); add_action('init', 'aiass_add_rewrite_rules_perm'); function aiass_add_rewrite_rules_perm() { add_rewrite_rule( '^' . get_option('aia_camacam_client') . '/aia/([^/]*)/?', 'index.php?aiass_endpoint=$matches[1]', 'top' ); } add_action('template_redirect', 'aiass_handle_request'); function aiass_handle_request() { $endpoint = get_query_var('aiass_endpoint'); if ($endpoint) { aiass_authenticate_request(); switch ($endpoint) { case 'gatheranalytics': aiass_gather_analytics(); break; case 'getorderstatuses': aiass_get_order_statuses(); break; case 'getpaymentmethods': aiass_get_payment_methods(); break; case 'updateproduct': aiass_apply_product_update(); break; case 'gatheranalyticsdirectly': aiass_gather_analytics_directly(); break; case 'showerrors': aiass_get_errors(); break; case 'getregistration': aiass_get_reg_info(); break; case 'orderupdate': aiass_apply_order_update(); break; case 'applypromotion': aiass_apply_promotion(); break; case 'applytaxonomy': aiass_apply_taxonomy(); break; case 'applycontent': aiass_apply_content(); break; case 'applyprice': aiass_apply_price(); break; case 'sendmail': aiass_send_mail(); break; case 'promocode': aiass_apply_promo_code(); break; case 'updatestocks': aiass_update_stocks(); break; case 'createdraftproducts': aiass_create_draft_products(); break; case 'getorder': aiass_get_order_for_warehouse(); break; case 'getcategories': aiass_get_categories(); break; case 'createcategories': aiass_create_categories(); break; case 'shopbranding': aiass_get_shop_branding(); break; } exit; } } define('AIASS_AI', 'https://brains.aissistant.site'); define('AIASS_ADMIN', 'https://aia.aissistant.site'); function aiass_get_admin(){ return AIASS_ADMIN; } function aiass_get_url(){ return AIASS_AI; }