diff --git a/composer.json b/composer.json
index 0651cc5e238..e2926b9cebe 100644
--- a/composer.json
+++ b/composer.json
@@ -3,7 +3,7 @@
"description": "WooCommerce blocks for the Gutenberg editor.",
"homepage": "https://woocommerce.com/",
"type": "wordpress-plugin",
- "version": "11.5.1",
+ "version": "11.5.2",
"keywords": [
"gutenberg",
"woocommerce",
diff --git a/docs/internal-developers/testing/releases/1152.md b/docs/internal-developers/testing/releases/1152.md
new file mode 100644
index 00000000000..b0ee1965800
--- /dev/null
+++ b/docs/internal-developers/testing/releases/1152.md
@@ -0,0 +1,19 @@
+# Testing notes and ZIP for release 11.5.2
+
+Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github.com/woocommerce/woocommerce-blocks/files/13300708/woocommerce-gutenberg-products-block.zip)
+
+## WooCommerce Core
+
+### Bug Fixes
+
+#### Rename the Centered Header Menu with Search to Centered Header Menu. [11637](https://github.com/woocommerce/woocommerce-blocks/pull/11637)
+
+1. Create a new page or post.
+2. Confirm that the `Centered Header Menu` pattern appears under the WooCommerce category dropdown and is NOT named `Centered Header Menu with Search`.
+3. Insert in and confirm everything appears as expected.
+
+#### Fix decoding issue and pattern. [11681](https://github.com/woocommerce/woocommerce-blocks/pull/11681)
+
+1. Ensure you are on fresh install (it should not have the `patterns_ai_data` post created).
+2. Insert the `Featured Products: Fresh & Tasty` pattern and make sure it has all the default text content, nothing is empty.
+3. Insert a few other WooCommerce Blocks patterns and check all of them have all the text content as well.
diff --git a/docs/internal-developers/testing/releases/README.md b/docs/internal-developers/testing/releases/README.md
index 2f7a91fae67..bee4b2a8908 100644
--- a/docs/internal-developers/testing/releases/README.md
+++ b/docs/internal-developers/testing/releases/README.md
@@ -187,3 +187,5 @@ Every release includes specific testing instructions for new features and bug fi
- [11.4.3](./1143.md)
- [11.5.0](./1150.md)
- [11.5.1](./1151.md)
+ - [11.5.2](./1152.md)
+
diff --git a/package-lock.json b/package-lock.json
index 708613e751b..ec5a77d1fe0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@woocommerce/block-library",
- "version": "11.5.1",
+ "version": "11.5.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@woocommerce/block-library",
- "version": "11.5.1",
+ "version": "11.5.2",
"hasInstallScript": true,
"license": "GPL-3.0+",
"dependencies": {
diff --git a/package.json b/package.json
index 140ed141f95..9319a71f52c 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "@woocommerce/block-library",
"title": "WooCommerce Blocks",
"author": "Automattic",
- "version": "11.5.1",
+ "version": "11.5.2",
"description": "WooCommerce blocks for the Gutenberg editor.",
"homepage": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/",
"keywords": [
diff --git a/patterns/featured-products-fresh-and-tasty.php b/patterns/featured-products-fresh-and-tasty.php
index 60b814a6f2a..818893c0ddb 100644
--- a/patterns/featured-products-fresh-and-tasty.php
+++ b/patterns/featured-products-fresh-and-tasty.php
@@ -18,6 +18,7 @@
$first_description = $content['descriptions'][0]['default'] ?? '';
$second_description = $content['descriptions'][1]['default'] ?? '';
$third_description = $content['descriptions'][2]['default'] ?? '';
+$fourth_description = $content['descriptions'][3]['default'] ?? '';
?>
@@ -129,7 +130,7 @@
diff --git a/patterns/header-centered-search-pattern.php b/patterns/header-centered-pattern.php
similarity index 96%
rename from patterns/header-centered-search-pattern.php
rename to patterns/header-centered-pattern.php
index eaebf5fbee9..517bc1672d4 100644
--- a/patterns/header-centered-search-pattern.php
+++ b/patterns/header-centered-pattern.php
@@ -1,7 +1,7 @@
array(
+ 'name' => __( 'Patterns AI Data', 'woo-gutenberg-products-block' ),
+ 'singular_name' => __( 'Patterns AI Data', 'woo-gutenberg-products-block' ),
+ ),
+ 'public' => false,
+ 'hierarchical' => false,
+ 'rewrite' => false,
+ 'query_var' => false,
+ 'delete_with_user' => false,
+ 'can_export' => true,
+ )
+ );
+
$default_headers = array(
'title' => 'Title',
'slug' => 'Slug',
@@ -250,7 +267,7 @@ public function schedule_on_option_update( $option, $value ) {
* @param array $options Array of bulk item update data.
*/
public function schedule_on_plugin_update( $upgrader_object, $options ) {
- if ( 'update' === $options['action'] && 'plugin' === $options['type'] ) {
+ if ( 'update' === $options['action'] && 'plugin' === $options['type'] && isset( $options['plugins'] ) ) {
foreach ( $options['plugins'] as $plugin ) {
if ( str_contains( $plugin, 'woocommerce-gutenberg-products-block.php' ) || str_contains( $plugin, 'woocommerce.php' ) ) {
$business_description = get_option( 'woo_ai_describe_store_description' );
diff --git a/src/Package.php b/src/Package.php
index 192c675fc7c..568cb3d5eef 100644
--- a/src/Package.php
+++ b/src/Package.php
@@ -109,7 +109,7 @@ public static function container( $reset = false ) {
NewPackage::class,
function ( $container ) {
// leave for automated version bumping.
- $version = '11.5.1';
+ $version = '11.5.2';
return new NewPackage(
$version,
dirname( __DIR__ ),
diff --git a/src/Patterns/PatternUpdater.php b/src/Patterns/PatternUpdater.php
index 67568e8ba5a..affbd20cbeb 100644
--- a/src/Patterns/PatternUpdater.php
+++ b/src/Patterns/PatternUpdater.php
@@ -10,11 +10,6 @@
*/
class PatternUpdater {
- /**
- * The patterns content option name.
- */
- const WC_BLOCKS_PATTERNS_CONTENT = 'wc_blocks_patterns_content';
-
/**
* Creates the patterns content for the given vertical.
*
@@ -42,13 +37,15 @@ public function generate_content( $ai_connection, $token, $images, $business_des
return new WP_Error( 'failed_to_set_pattern_content', __( 'Failed to set the pattern content.', 'woo-gutenberg-products-block' ) );
}
- if ( get_option( self::WC_BLOCKS_PATTERNS_CONTENT ) === $patterns_with_images_and_content ) {
+ $patterns_ai_data_post = PatternsHelper::get_patterns_ai_data_post();
+
+ if ( isset( $patterns_ai_data_post->post_content ) && json_decode( $patterns_ai_data_post->post_content ) === $patterns_with_images_and_content ) {
return true;
}
- $updated_content = update_option( self::WC_BLOCKS_PATTERNS_CONTENT, $patterns_with_images_and_content );
+ $updated_content = PatternsHelper::upsert_patterns_ai_data_post( $patterns_with_images_and_content );
- if ( ! $updated_content ) {
+ if ( is_wp_error( $updated_content ) ) {
return new WP_Error( 'failed_to_update_patterns_content', __( 'Failed to update patterns content.', 'woo-gutenberg-products-block' ) );
}
diff --git a/src/Patterns/PatternsHelper.php b/src/Patterns/PatternsHelper.php
index 2ac827f030d..591db624374 100644
--- a/src/Patterns/PatternsHelper.php
+++ b/src/Patterns/PatternsHelper.php
@@ -74,6 +74,48 @@ public static function get_image_url( array $images, int $index, string $default
return $image;
}
+ /**
+ * Returns the post that has the generated data by the AI for the patterns.
+ *
+ * @return WP_Post|null
+ */
+ public static function get_patterns_ai_data_post() {
+ $arg = array(
+ 'post_type' => 'patterns_ai_data',
+ 'posts_per_page' => 1,
+ 'no_found_rows' => true,
+ 'cache_results' => true,
+ );
+
+ $query = new \WP_Query( $arg );
+
+ $posts = $query->get_posts();
+ return isset( $posts[0] ) ? $posts[0] : null;
+ }
+
+ /**
+ * Upsert the patterns AI data.
+ *
+ * @param array $patterns_dictionary The patterns dictionary.
+ *
+ * @return WP_Error|null
+ */
+ public static function upsert_patterns_ai_data_post( $patterns_dictionary ) {
+ $patterns_ai_data_post = self::get_patterns_ai_data_post();
+
+ if ( isset( $patterns_ai_data_post ) ) {
+ $patterns_ai_data_post->post_content = wp_json_encode( $patterns_dictionary );
+ return wp_update_post( $patterns_ai_data_post, true );
+ } else {
+ $patterns_ai_data_post = array(
+ 'post_title' => 'Patterns AI Data',
+ 'post_content' => wp_json_encode( $patterns_dictionary ),
+ 'post_status' => 'publish',
+ 'post_type' => 'patterns_ai_data',
+ );
+ return wp_insert_post( $patterns_ai_data_post, true );
+ }
+ }
/**
* Get the Patterns Dictionary.
@@ -83,9 +125,11 @@ public static function get_image_url( array $images, int $index, string $default
* @return mixed|WP_Error|null
*/
private static function get_patterns_dictionary( $pattern_slug = null ) {
- $patterns_dictionary = get_option( PatternUpdater::WC_BLOCKS_PATTERNS_CONTENT );
- if ( ! empty( $patterns_dictionary ) ) {
+ $patterns_ai_data_post = self::get_patterns_ai_data_post();
+
+ if ( isset( $patterns_ai_data_post ) ) {
+ $patterns_dictionary = json_decode( $patterns_ai_data_post->post_content, true );
if ( empty( $pattern_slug ) ) {
return $patterns_dictionary;
}
@@ -103,7 +147,7 @@ private static function get_patterns_dictionary( $pattern_slug = null ) {
return new WP_Error( 'missing_patterns_dictionary', __( 'The patterns dictionary is missing.', 'woo-gutenberg-products-block' ) );
}
- $patterns_dictionary = wp_json_file_decode( $patterns_dictionary_file, array( 'associative' => true ) );
+ $patterns_dictionary = json_decode( $patterns_dictionary_file, true );
if ( ! empty( $pattern_slug ) ) {
foreach ( $patterns_dictionary as $pattern_dictionary ) {
diff --git a/src/Patterns/ProductUpdater.php b/src/Patterns/ProductUpdater.php
index 1184fc75d68..ee13c515e49 100644
--- a/src/Patterns/ProductUpdater.php
+++ b/src/Patterns/ProductUpdater.php
@@ -17,7 +17,7 @@ class ProductUpdater {
* @param array $images The array of images.
* @param string $business_description The business description.
*
- * @return bool|WP_Error True if the content was generated successfully, WP_Error otherwise.
+ * @return array|WP_Error The generated content for the products. An error if the content could not be generated.
*/
public function generate_content( $ai_connection, $token, $images, $business_description ) {
if ( empty( $business_description ) ) {
@@ -28,16 +28,48 @@ public function generate_content( $ai_connection, $token, $images, $business_des
if ( $last_business_description === $business_description ) {
if ( is_string( $business_description ) && is_string( $last_business_description ) ) {
- return true;
+ return array(
+ 'product_content' => array(),
+ );
} else {
return new \WP_Error( 'business_description_not_found', __( 'No business description provided for generating AI content.', 'woo-gutenberg-products-block' ) );
}
}
+ $ai_selected_products_images = $this->get_images_information( $images );
+ $products_information_list = $this->assign_ai_selected_images_to_dummy_products_information_list( $ai_selected_products_images );
+
+ $response = $this->generate_product_content( $ai_connection, $token, $products_information_list );
+
+ if ( is_wp_error( $response ) ) {
+ $error_msg = $response;
+ } elseif ( empty( $response ) || ! isset( $response['completion'] ) ) {
+ $error_msg = new \WP_Error( 'missing_completion_key', __( 'The response from the AI service is empty or missing the completion key.', 'woo-gutenberg-products-block' ) );
+ }
+
+ if ( isset( $error_msg ) ) {
+ return $error_msg;
+ }
+
+ $product_content = json_decode( $response['completion'], true );
+
+ return array(
+ 'product_content' => $product_content,
+ );
+ }
+
+ /**
+ * Return all dummy products that were not modified by the store owner.
+ *
+ * @return array|WP_Error An array with the dummy products that need to have their content updated by AI.
+ */
+ public function fetch_dummy_products_to_update() {
$real_products = $this->fetch_product_ids();
if ( is_array( $real_products ) && count( $real_products ) > 0 ) {
- return true;
+ return array(
+ 'product_content' => array(),
+ );
}
$dummy_products = $this->fetch_product_ids( 'dummy' );
@@ -82,62 +114,7 @@ function ( $product ) {
}
}
- if ( empty( $dummy_products_to_update ) ) {
- return true;
- }
-
- $ai_selected_products_images = $this->get_images_information( $images );
- $products_information_list = $this->assign_ai_selected_images_to_dummy_products_information_list( $ai_selected_products_images );
-
- $response = $this->generate_product_content( $ai_connection, $token, $products_information_list );
-
- if ( is_wp_error( $response ) ) {
- $error_msg = $response;
- } elseif ( empty( $response ) || ! isset( $response['completion'] ) ) {
- $error_msg = new \WP_Error( 'missing_completion_key', __( 'The response from the AI service is empty or missing the completion key.', 'woo-gutenberg-products-block' ) );
- }
-
- if ( isset( $error_msg ) ) {
- $this->update_dummy_products( $dummy_products_to_update, $products_information_list );
-
- return $error_msg;
- }
-
- $product_content = json_decode( $response['completion'], true );
-
- if ( is_null( $product_content ) ) {
- $this->update_dummy_products( $dummy_products_to_update, $products_information_list );
-
- return new \WP_Error( 'invalid_json', __( 'The response from the AI service is not a valid JSON.', 'woo-gutenberg-products-block' ) );
- }
-
- // This is required to allow the usage of the media_sideload_image function outside the context of /wp-admin/.
- // See https://developer.wordpress.org/reference/functions/media_sideload_image/ for more details.
- require_once ABSPATH . 'wp-admin/includes/media.php';
- require_once ABSPATH . 'wp-admin/includes/file.php';
- require_once ABSPATH . 'wp-admin/includes/image.php';
-
- $this->update_dummy_products( $dummy_products_to_update, $product_content );
-
- return true;
- }
-
- /**
- * Update the dummy products with the content from the information list.
- *
- * @param array $dummy_products_to_update The dummy products to update.
- * @param array $products_information_list The products information list.
- */
- public function update_dummy_products( $dummy_products_to_update, $products_information_list ) {
- $i = 0;
- foreach ( $dummy_products_to_update as $dummy_product ) {
- if ( ! isset( $products_information_list[ $i ] ) ) {
- continue;
- }
-
- $this->update_product_content( $dummy_product, $products_information_list[ $i ] );
- ++$i;
- }
+ return $dummy_products_to_update;
}
/**
@@ -273,11 +250,15 @@ public function update_product_content( $product, $ai_generated_product_content
if ( ! isset( $ai_generated_product_content['image']['src'] ) || ! isset( $ai_generated_product_content['image']['alt'] ) || ! isset( $ai_generated_product_content['title'] ) || ! isset( $ai_generated_product_content['description'] ) ) {
return;
}
+
+ require_once ABSPATH . 'wp-admin/includes/media.php';
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ require_once ABSPATH . 'wp-admin/includes/image.php';
+
// Since the media_sideload_image function is expensive and can take longer to complete
// the process of downloading the external image and uploading it to the media library,
- // here we are increasing the time limit and the memory limit to avoid any issues.
+ // here we are increasing the time limit to avoid any issues.
set_time_limit( 60 );
- wp_raise_memory_limit();
$product_image_id = media_sideload_image( $ai_generated_product_content['image']['src'], $product->get_id(), $ai_generated_product_content['image']['alt'], 'id' );
diff --git a/src/StoreApi/Routes/V1/AI/BusinessDescription.php b/src/StoreApi/Routes/V1/AI/BusinessDescription.php
new file mode 100644
index 00000000000..f377eff9a6f
--- /dev/null
+++ b/src/StoreApi/Routes/V1/AI/BusinessDescription.php
@@ -0,0 +1,89 @@
+ \WP_REST_Server::CREATABLE,
+ 'callback' => [ $this, 'get_response' ],
+ 'permission_callback' => [ Middleware::class, 'is_authorized' ],
+ 'args' => [
+ 'business_description' => [
+ 'description' => __( 'The business description for a given store.', 'woo-gutenberg-products-block' ),
+ 'type' => 'string',
+ ],
+ ],
+ ],
+ 'schema' => [ $this->schema, 'get_public_item_schema' ],
+ 'allow_batch' => [ 'v1' => true ],
+ ];
+ }
+
+ /**
+ * Update the last business description.
+ *
+ * @param \WP_REST_Request $request Request object.
+ *
+ * @return bool|string|\WP_Error|\WP_REST_Response
+ */
+ protected function get_route_post_response( \WP_REST_Request $request ) {
+
+ $business_description = $request->get_param( 'business_description' );
+
+ if ( ! $business_description ) {
+ return $this->error_to_response(
+ new \WP_Error(
+ 'invalid_business_description',
+ __( 'Invalid business description.', 'woo-gutenberg-products-block' )
+ )
+ );
+ }
+
+ update_option( 'last_business_description_with_ai_content_generated', $business_description );
+
+ return rest_ensure_response(
+ array(
+ 'ai_content_generated' => true,
+ )
+ );
+ }
+
+}
diff --git a/src/StoreApi/Routes/V1/AI/Images.php b/src/StoreApi/Routes/V1/AI/Images.php
new file mode 100644
index 00000000000..f6070e2c1b6
--- /dev/null
+++ b/src/StoreApi/Routes/V1/AI/Images.php
@@ -0,0 +1,118 @@
+ \WP_REST_Server::CREATABLE,
+ 'callback' => [ $this, 'get_response' ],
+ 'permission_callback' => [ Middleware::class, 'is_authorized' ],
+ 'args' => [
+ 'business_description' => [
+ 'description' => __( 'The business description for a given store.', 'woo-gutenberg-products-block' ),
+ 'type' => 'string',
+ ],
+ ],
+ ],
+ 'schema' => [ $this->schema, 'get_public_item_schema' ],
+ 'allow_batch' => [ 'v1' => true ],
+ ];
+ }
+
+ /**
+ * Generate Images from Pexels
+ *
+ * @param \WP_REST_Request $request Request object.
+ *
+ * @return bool|string|\WP_Error|\WP_REST_Response
+ */
+ protected function get_route_post_response( \WP_REST_Request $request ) {
+
+ $business_description = sanitize_text_field( wp_unslash( $request['business_description'] ) );
+
+ if ( empty( $business_description ) ) {
+ $business_description = get_option( 'woo_ai_describe_store_description' );
+ }
+
+ $last_business_description = get_option( 'last_business_description_with_ai_content_generated' );
+
+ if ( $last_business_description === $business_description ) {
+ return rest_ensure_response(
+ $this->prepare_item_for_response(
+ [
+ 'ai_content_generated' => true,
+ 'images' => array(),
+ ],
+ $request
+ )
+ );
+ }
+
+ $ai_connection = new Connection();
+
+ $site_id = $ai_connection->get_site_id();
+
+ if ( is_wp_error( $site_id ) ) {
+ return $this->error_to_response( $site_id );
+ }
+
+ $token = $ai_connection->get_jwt_token( $site_id );
+
+ if ( is_wp_error( $token ) ) {
+ return $this->error_to_response( $token );
+ }
+
+ $images = ( new Pexels() )->get_images( $ai_connection, $token, $business_description );
+
+ if ( is_wp_error( $images ) ) {
+ return $this->error_to_response( $images );
+ }
+
+ return rest_ensure_response(
+ $this->prepare_item_for_response(
+ [
+ 'ai_content_generated' => true,
+ 'images' => $images,
+ ],
+ $request
+ )
+ );
+ }
+}
diff --git a/src/StoreApi/Routes/V1/AI/Middleware.php b/src/StoreApi/Routes/V1/AI/Middleware.php
new file mode 100644
index 00000000000..797e2e00216
--- /dev/null
+++ b/src/StoreApi/Routes/V1/AI/Middleware.php
@@ -0,0 +1,50 @@
+getErrorCode(),
+ $error->getMessage(),
+ array( 'status' => $error->getCode() )
+ );
+ }
+
+ $allow_ai_connection = get_option( 'woocommerce_blocks_allow_ai_connection' );
+
+ if ( ! $allow_ai_connection ) {
+ try {
+ throw new RouteException( 'ai_connection_not_allowed', __( 'AI content generation is not allowed on this store. Update your store settings if you wish to enable this feature.', 'woo-gutenberg-products-block' ), 403 );
+ } catch ( RouteException $error ) {
+ return new \WP_Error(
+ $error->getErrorCode(),
+ $error->getMessage(),
+ array( 'status' => $error->getCode() )
+ );
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/StoreApi/Routes/V1/AI/Patterns.php b/src/StoreApi/Routes/V1/AI/Patterns.php
new file mode 100644
index 00000000000..120e6479885
--- /dev/null
+++ b/src/StoreApi/Routes/V1/AI/Patterns.php
@@ -0,0 +1,94 @@
+ \WP_REST_Server::CREATABLE,
+ 'callback' => [ $this, 'get_response' ],
+ 'permission_callback' => [ Middleware::class, 'is_authorized' ],
+ 'args' => [
+ 'business_description' => [
+ 'description' => __( 'The business description for a given store.', 'woo-gutenberg-products-block' ),
+ 'type' => 'string',
+ ],
+ 'images' => [
+ 'description' => __( 'The images for a given store.', 'woo-gutenberg-products-block' ),
+ 'type' => 'object',
+ ],
+ ],
+ ],
+ 'schema' => [ $this->schema, 'get_public_item_schema' ],
+ 'allow_batch' => [ 'v1' => true ],
+ ];
+ }
+
+ /**
+ * Update patterns with the content and images powered by AI.
+ *
+ * @param \WP_REST_Request $request Request object.
+ *
+ * @return bool|string|\WP_Error|\WP_REST_Response
+ */
+ protected function get_route_post_response( \WP_REST_Request $request ) {
+ $business_description = sanitize_text_field( wp_unslash( $request['business_description'] ) );
+
+ $ai_connection = new Connection();
+
+ $site_id = $ai_connection->get_site_id();
+
+ if ( is_wp_error( $site_id ) ) {
+ return $this->error_to_response( $site_id );
+ }
+
+ $token = $ai_connection->get_jwt_token( $site_id );
+
+ $images = $request['images'];
+
+ try {
+ ( new PatternUpdater() )->generate_content( $ai_connection, $token, $images, $business_description );
+ return rest_ensure_response( array( 'ai_content_generated' => true ) );
+ } catch ( \WP_Error $e ) {
+ return $this->error_to_response( $e );
+ }
+ }
+}
diff --git a/src/StoreApi/Routes/V1/AI/Product.php b/src/StoreApi/Routes/V1/AI/Product.php
new file mode 100644
index 00000000000..cf99f28af8c
--- /dev/null
+++ b/src/StoreApi/Routes/V1/AI/Product.php
@@ -0,0 +1,111 @@
+ \WP_REST_Server::CREATABLE,
+ 'callback' => [ $this, 'get_response' ],
+ 'permission_callback' => [ Middleware::class, 'is_authorized' ],
+ 'args' => [
+ 'index' => [
+ 'description' => __( 'The business description for a given store.', 'woo-gutenberg-products-block' ),
+ 'type' => 'integer',
+ ],
+ 'products_information' => [
+ 'description' => __( 'Data generated by AI for updating dummy products.', 'woo-gutenberg-products-block' ),
+ 'type' => 'object',
+ ],
+ ],
+ ],
+ 'schema' => [ $this->schema, 'get_public_item_schema' ],
+ 'allow_batch' => [ 'v1' => true ],
+ ];
+ }
+
+ /**
+ * Update product with the content and image powered by AI.
+ *
+ * @param \WP_REST_Request $request Request object.
+ *
+ * @return bool|string|\WP_Error|\WP_REST_Response
+ */
+ protected function get_route_post_response( \WP_REST_Request $request ) {
+ $product_updater = new ProductUpdater();
+ $dummy_products = $product_updater->fetch_dummy_products_to_update();
+
+ if ( empty( $dummy_products ) ) {
+ return rest_ensure_response(
+ array(
+ 'ai_content_generated' => true,
+ )
+ );
+ }
+
+ $index = $request['index'];
+ if ( ! is_numeric( $index ) ) {
+ return rest_ensure_response(
+ array(
+ 'ai_content_generated' => false,
+ )
+ );
+ }
+
+ $products_information = $request['products_information'] ?? array();
+
+ if ( ! isset( $dummy_products[ $index ] ) ) {
+ return rest_ensure_response(
+ array(
+ 'ai_content_generated' => false,
+ )
+ );
+ }
+
+ $product_updater->update_product_content( $dummy_products[ $index ], $products_information );
+
+ return rest_ensure_response(
+ array(
+ 'ai_content_generated' => true,
+ )
+ );
+ }
+
+}
diff --git a/src/StoreApi/Routes/V1/Patterns.php b/src/StoreApi/Routes/V1/AI/Products.php
similarity index 51%
rename from src/StoreApi/Routes/V1/Patterns.php
rename to src/StoreApi/Routes/V1/AI/Products.php
index a378244ab09..72024429a2d 100644
--- a/src/StoreApi/Routes/V1/Patterns.php
+++ b/src/StoreApi/Routes/V1/AI/Products.php
@@ -1,30 +1,30 @@
\WP_REST_Server::CREATABLE,
'callback' => [ $this, 'get_response' ],
- 'permission_callback' => [ $this, 'is_authorized' ],
+ 'permission_callback' => [ Middleware::class, 'is_authorized' ],
'args' => [
'business_description' => [
'description' => __( 'The business description for a given store.', 'woo-gutenberg-products-block' ),
'type' => 'string',
],
+ 'images' => [
+ 'description' => __( 'The images for a given store.', 'woo-gutenberg-products-block' ),
+ 'type' => 'object',
+ ],
],
],
'schema' => [ $this->schema, 'get_public_item_schema' ],
@@ -59,30 +63,7 @@ public function get_args() {
}
/**
- * Permission callback.
- *
- * @throws RouteException If the user is not allowed to make this request.
- *
- * @return true|\WP_Error
- */
- public function is_authorized() {
- try {
- if ( ! current_user_can( 'manage_options' ) ) {
- throw new RouteException( 'woocommerce_rest_invalid_user', __( 'You are not allowed to make this request. Please make sure you are logged in.', 'woo-gutenberg-products-block' ), 403 );
- }
- } catch ( RouteException $error ) {
- return new \WP_Error(
- $error->getErrorCode(),
- $error->getMessage(),
- array( 'status' => $error->getCode() )
- );
- }
-
- return true;
- }
-
- /**
- * Ensure the content and images in patterns are powered by AI.
+ * Generate the content for the products.
*
* @param \WP_REST_Request $request Request object.
*
@@ -115,6 +96,7 @@ protected function get_route_post_response( \WP_REST_Request $request ) {
$this->prepare_item_for_response(
[
'ai_content_generated' => true,
+ 'product_content' => null,
],
$request
)
@@ -126,46 +108,34 @@ protected function get_route_post_response( \WP_REST_Request $request ) {
$site_id = $ai_connection->get_site_id();
if ( is_wp_error( $site_id ) ) {
- return $site_id;
+ return $this->error_to_response( $site_id );
}
$token = $ai_connection->get_jwt_token( $site_id );
if ( is_wp_error( $token ) ) {
- return $token;
+ return $this->error_to_response( $token );
}
- $images = ( new Pexels() )->get_images( $ai_connection, $token, $business_description );
-
- if ( is_wp_error( $images ) ) {
- $response = $this->error_to_response( $images );
- } else {
- $populate_patterns = ( new PatternUpdater() )->generate_content( $ai_connection, $token, $images, $business_description );
-
- if ( is_wp_error( $populate_patterns ) ) {
- $response = $this->error_to_response( $populate_patterns );
- }
-
- $populate_products = ( new ProductUpdater() )->generate_content( $ai_connection, $token, $images, $business_description );
+ $images = $request['images'];
- if ( is_wp_error( $populate_products ) ) {
- $response = $this->error_to_response( $populate_products );
- }
+ $populate_products = ( new ProductUpdater() )->generate_content( $ai_connection, $token, $images, $business_description );
- if ( true === $populate_patterns && true === $populate_products ) {
- update_option( 'last_business_description_with_ai_content_generated', $business_description );
- }
+ if ( is_wp_error( $populate_products ) ) {
+ return $this->error_to_response( $populate_products );
}
- if ( ! isset( $response ) ) {
- $response = $this->prepare_item_for_response(
- [
- 'ai_content_generated' => true,
- ],
- $request
- );
+ if ( ! isset( $populate_products['product_content'] ) ) {
+ return $this->error_to_response( new \WP_Error( 'product_content_not_found', __( 'Product content not found.', 'woo-gutenberg-products-block' ) ) );
}
- return rest_ensure_response( $response );
+ $product_content = $populate_products['product_content'];
+
+ $item = array(
+ 'ai_content_generated' => true,
+ 'product_content' => $product_content,
+ );
+
+ return rest_ensure_response( $item );
}
}
diff --git a/src/StoreApi/RoutesController.php b/src/StoreApi/RoutesController.php
index 79ba9c973c0..aace314ca39 100644
--- a/src/StoreApi/RoutesController.php
+++ b/src/StoreApi/RoutesController.php
@@ -1,7 +1,6 @@
schema_controller = $schema_controller;
$this->routes = [
- 'v1' => [
+ 'v1' => [
Routes\V1\Batch::IDENTIFIER => Routes\V1\Batch::class,
Routes\V1\Cart::IDENTIFIER => Routes\V1\Cart::class,
Routes\V1\CartAddItem::IDENTIFIER => Routes\V1\CartAddItem::class,
@@ -48,7 +47,6 @@ public function __construct( SchemaController $schema_controller ) {
Routes\V1\Checkout::IDENTIFIER => Routes\V1\Checkout::class,
Routes\V1\CheckoutOrder::IDENTIFIER => Routes\V1\CheckoutOrder::class,
Routes\V1\Order::IDENTIFIER => Routes\V1\Order::class,
- Routes\V1\Patterns::IDENTIFIER => Routes\V1\Patterns::class,
Routes\V1\ProductAttributes::IDENTIFIER => Routes\V1\ProductAttributes::class,
Routes\V1\ProductAttributesById::IDENTIFIER => Routes\V1\ProductAttributesById::class,
Routes\V1\ProductAttributeTerms::IDENTIFIER => Routes\V1\ProductAttributeTerms::class,
@@ -61,6 +59,14 @@ public function __construct( SchemaController $schema_controller ) {
Routes\V1\ProductsById::IDENTIFIER => Routes\V1\ProductsById::class,
Routes\V1\ProductsBySlug::IDENTIFIER => Routes\V1\ProductsBySlug::class,
],
+ // @todo Migrate internal AI routes to WooCommerce Core codebase.
+ 'private' => [
+ Routes\V1\AI\Images::IDENTIFIER => Routes\V1\AI\Images::class,
+ Routes\V1\AI\Patterns::IDENTIFIER => Routes\V1\AI\Patterns::class,
+ Routes\V1\AI\Product::IDENTIFIER => Routes\V1\AI\Product::class,
+ Routes\V1\AI\Products::IDENTIFIER => Routes\V1\AI\Products::class,
+ Routes\V1\AI\BusinessDescription::IDENTIFIER => Routes\V1\AI\BusinessDescription::class,
+ ],
];
}
@@ -70,6 +76,7 @@ public function __construct( SchemaController $schema_controller ) {
public function register_all_routes() {
$this->register_routes( 'v1', 'wc/store' );
$this->register_routes( 'v1', 'wc/store/v1' );
+ $this->register_routes( 'private', 'wc/private' );
}
/**
diff --git a/src/StoreApi/SchemaController.php b/src/StoreApi/SchemaController.php
index 55e38cb18c6..bc1a284ca7e 100644
--- a/src/StoreApi/SchemaController.php
+++ b/src/StoreApi/SchemaController.php
@@ -49,12 +49,16 @@ public function __construct( ExtendSchema $extend ) {
Schemas\V1\OrderCouponSchema::IDENTIFIER => Schemas\V1\OrderCouponSchema::class,
Schemas\V1\OrderFeeSchema::IDENTIFIER => Schemas\V1\OrderFeeSchema::class,
Schemas\V1\OrderSchema::IDENTIFIER => Schemas\V1\OrderSchema::class,
- Schemas\V1\PatternsSchema::IDENTIFIER => Schemas\V1\PatternsSchema::class,
Schemas\V1\ProductSchema::IDENTIFIER => Schemas\V1\ProductSchema::class,
Schemas\V1\ProductAttributeSchema::IDENTIFIER => Schemas\V1\ProductAttributeSchema::class,
Schemas\V1\ProductCategorySchema::IDENTIFIER => Schemas\V1\ProductCategorySchema::class,
Schemas\V1\ProductCollectionDataSchema::IDENTIFIER => Schemas\V1\ProductCollectionDataSchema::class,
Schemas\V1\ProductReviewSchema::IDENTIFIER => Schemas\V1\ProductReviewSchema::class,
+ Schemas\V1\AI\ImagesSchema::IDENTIFIER => Schemas\V1\AI\ImagesSchema::class,
+ Schemas\V1\AI\PatternsSchema::IDENTIFIER => Schemas\V1\AI\PatternsSchema::class,
+ Schemas\V1\AI\ProductSchema::IDENTIFIER => Schemas\V1\AI\ProductSchema::class,
+ Schemas\V1\AI\ProductsSchema::IDENTIFIER => Schemas\V1\AI\ProductsSchema::class,
+ Schemas\V1\AI\BusinessDescriptionSchema::IDENTIFIER => Schemas\V1\AI\BusinessDescriptionSchema::class,
],
];
}
diff --git a/src/StoreApi/Schemas/V1/AI/BusinessDescriptionSchema.php b/src/StoreApi/Schemas/V1/AI/BusinessDescriptionSchema.php
new file mode 100644
index 00000000000..b628fe9c33a
--- /dev/null
+++ b/src/StoreApi/Schemas/V1/AI/BusinessDescriptionSchema.php
@@ -0,0 +1,47 @@
+ true,
+ ];
+ }
+}
diff --git a/src/StoreApi/Schemas/V1/AI/ImagesSchema.php b/src/StoreApi/Schemas/V1/AI/ImagesSchema.php
new file mode 100644
index 00000000000..317e4c028f6
--- /dev/null
+++ b/src/StoreApi/Schemas/V1/AI/ImagesSchema.php
@@ -0,0 +1,45 @@
+ true,
+ ];
+ }
+}
diff --git a/src/StoreApi/Schemas/V1/AI/ProductsSchema.php b/src/StoreApi/Schemas/V1/AI/ProductsSchema.php
new file mode 100644
index 00000000000..114df7807af
--- /dev/null
+++ b/src/StoreApi/Schemas/V1/AI/ProductsSchema.php
@@ -0,0 +1,48 @@
+ $item['ai_content_generated'],
+ 'product_content' => $item['product_content'],
+ ];
+ }
+}
diff --git a/woocommerce-gutenberg-products-block.php b/woocommerce-gutenberg-products-block.php
index fa2218f122e..8ad020bfda1 100644
--- a/woocommerce-gutenberg-products-block.php
+++ b/woocommerce-gutenberg-products-block.php
@@ -3,7 +3,7 @@
* Plugin Name: WooCommerce Blocks
* Plugin URI: https://github.com/woocommerce/woocommerce-gutenberg-products-block
* Description: WooCommerce blocks for the Gutenberg editor.
- * Version: 11.5.1
+ * Version: 11.5.2
* Author: Automattic
* Author URI: https://woocommerce.com
* Text Domain: woo-gutenberg-products-block