ContentProcessor.php
1 year ago
PatternsDictionary.php
1 year ago
PatternsHelper.php
1 year ago
UpdatePatterns.php
1 year ago
UpdateProducts.php
1 year ago
UpdateProducts.php
241 lines
| 1 | <?php |
| 2 | |
| 3 | namespace Automattic\WooCommerce\Blocks\AIContent; |
| 4 | |
| 5 | use WP_Error; |
| 6 | |
| 7 | /** |
| 8 | * This class is used to create dummy products for the Customize Your Store flow. |
| 9 | * Even if it is in the AI Content namespace, it is not used for AI content generation. |
| 10 | * |
| 11 | * @internal |
| 12 | */ |
| 13 | class UpdateProducts { |
| 14 | |
| 15 | /** |
| 16 | * The dummy products. |
| 17 | */ |
| 18 | const DUMMY_PRODUCTS = [ |
| 19 | [ |
| 20 | 'title' => 'Vintage Typewriter', |
| 21 | 'image' => 'assets/images/pattern-placeholders/writing-typing-keyboard-technology-white-vintage.jpg', |
| 22 | 'description' => 'A hit spy novel or a love letter? Anything you type using this vintage typewriter from the 20s is bound to make a mark.', |
| 23 | 'price' => 90, |
| 24 | ], |
| 25 | [ |
| 26 | 'title' => 'Leather-Clad Leisure Chair', |
| 27 | 'image' => 'assets/images/pattern-placeholders/table-wood-house-chair-floor-window.jpg', |
| 28 | 'description' => 'Sit back and relax in this comfy designer chair. High-grain leather and steel frame add luxury to your your leisure.', |
| 29 | 'price' => 249, |
| 30 | ], |
| 31 | [ |
| 32 | 'title' => 'Black and White', |
| 33 | 'image' => 'assets/images/pattern-placeholders/white-black-black-and-white-photograph-monochrome-photography.jpg', |
| 34 | 'description' => 'This 24" x 30" high-quality print just exudes summer. Hang it on the wall and forget about the world outside.', |
| 35 | 'price' => 115, |
| 36 | ], |
| 37 | [ |
| 38 | 'title' => '3-Speed Bike', |
| 39 | 'image' => 'assets/images/pattern-placeholders/road-sport-vintage-wheel-retro-old.jpg', |
| 40 | 'description' => 'Zoom through the streets on this premium 3-speed bike. Manufactured and assembled in Germany in the 80s.', |
| 41 | 'price' => 115, |
| 42 | ], |
| 43 | [ |
| 44 | 'title' => 'Hi-Fi Headphones', |
| 45 | 'image' => 'assets/images/pattern-placeholders/man-person-music-black-and-white-white-photography.jpg', |
| 46 | 'description' => 'Experience your favorite songs in a new way with these premium hi-fi headphones.', |
| 47 | 'price' => 125, |
| 48 | ], |
| 49 | [ |
| 50 | 'title' => 'Retro Glass Jug (330 ml)', |
| 51 | 'image' => 'assets/images/pattern-placeholders/drinkware-liquid-tableware-dishware-bottle-fluid.jpg', |
| 52 | 'description' => 'Thick glass and a classic silhouette make this jug a must-have for any retro-inspired kitchen.', |
| 53 | 'price' => 115, |
| 54 | ], |
| 55 | ]; |
| 56 | |
| 57 | |
| 58 | /** |
| 59 | * Return all dummy products that were not modified by the store owner. |
| 60 | * |
| 61 | * @return array|WP_Error An array with the dummy products that need to have their content updated by AI. |
| 62 | */ |
| 63 | public function fetch_dummy_products_to_update() { |
| 64 | $real_products = $this->fetch_product_ids(); |
| 65 | $real_products_count = count( $real_products ); |
| 66 | |
| 67 | if ( is_array( $real_products ) && $real_products_count > 6 ) { |
| 68 | return array( |
| 69 | 'product_content' => array(), |
| 70 | ); |
| 71 | } |
| 72 | |
| 73 | $dummy_products = $this->fetch_product_ids( 'dummy' ); |
| 74 | $dummy_products_count = count( $dummy_products ); |
| 75 | $products_to_create = max( 0, 6 - $real_products_count - $dummy_products_count ); |
| 76 | while ( $products_to_create > 0 ) { |
| 77 | $this->create_new_product( self::DUMMY_PRODUCTS[ $products_to_create - 1 ] ); |
| 78 | --$products_to_create; |
| 79 | } |
| 80 | |
| 81 | // Identify dummy products that need to have their content updated. |
| 82 | $dummy_products_ids = $this->fetch_product_ids( 'dummy' ); |
| 83 | if ( ! is_array( $dummy_products_ids ) ) { |
| 84 | return new \WP_Error( 'failed_to_fetch_dummy_products', __( 'Failed to fetch dummy products.', 'woocommerce' ) ); |
| 85 | } |
| 86 | |
| 87 | $dummy_products = array_map( |
| 88 | function ( $product ) { |
| 89 | return wc_get_product( $product->ID ); |
| 90 | }, |
| 91 | $dummy_products_ids |
| 92 | ); |
| 93 | |
| 94 | $dummy_products_to_update = []; |
| 95 | foreach ( $dummy_products as $dummy_product ) { |
| 96 | if ( ! $dummy_product instanceof \WC_Product ) { |
| 97 | continue; |
| 98 | } |
| 99 | |
| 100 | $should_update_dummy_product = $this->should_update_dummy_product( $dummy_product ); |
| 101 | |
| 102 | if ( $should_update_dummy_product ) { |
| 103 | $dummy_products_to_update[] = $dummy_product; |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | return $dummy_products_to_update; |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * Verify if the dummy product should have its content generated and managed by AI. |
| 112 | * |
| 113 | * @param \WC_Product $dummy_product The dummy product. |
| 114 | * |
| 115 | * @return bool |
| 116 | */ |
| 117 | public function should_update_dummy_product( $dummy_product ): bool { |
| 118 | $date_created = $dummy_product->get_date_created(); |
| 119 | $date_modified = $dummy_product->get_date_modified(); |
| 120 | |
| 121 | if ( ! $date_created instanceof \WC_DateTime || ! $date_modified instanceof \WC_DateTime ) { |
| 122 | return false; |
| 123 | } |
| 124 | |
| 125 | $formatted_date_created = $dummy_product->get_date_created()->date( 'Y-m-d H:i:s' ); |
| 126 | $formatted_date_modified = $dummy_product->get_date_modified()->date( 'Y-m-d H:i:s' ); |
| 127 | |
| 128 | $timestamp_created = strtotime( $formatted_date_created ); |
| 129 | $timestamp_modified = strtotime( $formatted_date_modified ); |
| 130 | $timestamp_current = time(); |
| 131 | |
| 132 | $dummy_product_recently_modified = abs( $timestamp_current - $timestamp_modified ) < 10; |
| 133 | $dummy_product_not_modified = abs( $timestamp_modified - $timestamp_created ) < 60; |
| 134 | |
| 135 | if ( $dummy_product_not_modified || $dummy_product_recently_modified ) { |
| 136 | return true; |
| 137 | } |
| 138 | |
| 139 | return false; |
| 140 | } |
| 141 | |
| 142 | /** |
| 143 | * Creates a new product and assigns the _headstart_post meta to it. |
| 144 | * |
| 145 | * @param array $product_data The product data. |
| 146 | * |
| 147 | * @return bool|int|\WP_Error |
| 148 | */ |
| 149 | public function create_new_product( $product_data ) { |
| 150 | $product = new \WC_Product(); |
| 151 | $image_src = plugins_url( $product_data['image'], dirname( __DIR__, 2 ) ); |
| 152 | $image_alt = $product_data['title']; |
| 153 | $product_image_id = $this->product_image_upload( $product->get_id(), $image_src, $image_alt ); |
| 154 | |
| 155 | $saved_product = $this->product_update( $product, $product_image_id, $product_data['title'], $product_data['description'], $product_data['price'] ); |
| 156 | |
| 157 | if ( is_wp_error( $saved_product ) ) { |
| 158 | return $saved_product; |
| 159 | } |
| 160 | |
| 161 | return update_post_meta( $saved_product, '_headstart_post', true ); |
| 162 | } |
| 163 | |
| 164 | /** |
| 165 | * Return all existing products that have the _headstart_post meta assigned to them. |
| 166 | * |
| 167 | * @param string $type The type of products to fetch. |
| 168 | * |
| 169 | * @return array|null |
| 170 | */ |
| 171 | public function fetch_product_ids( string $type = 'user_created' ) { |
| 172 | global $wpdb; |
| 173 | |
| 174 | if ( 'user_created' === $type ) { |
| 175 | return $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE ID NOT IN ( SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE pm.meta_key = %s AND p.post_type = 'product' AND p.post_status = 'publish' ) AND post_type = 'product' AND post_status = 'publish' LIMIT 6", '_headstart_post' ) ); |
| 176 | } |
| 177 | |
| 178 | return $wpdb->get_results( $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE pm.meta_key = %s AND p.post_type = 'product' AND p.post_status = 'publish'", '_headstart_post' ) ); |
| 179 | } |
| 180 | |
| 181 | /** |
| 182 | * Upload the image for the product. |
| 183 | * |
| 184 | * @param int $product_id The product ID. |
| 185 | * @param string $image_src The image source. |
| 186 | * @param string $image_alt The image alt. |
| 187 | * |
| 188 | * @return int|string|WP_Error |
| 189 | */ |
| 190 | private function product_image_upload( $product_id, $image_src, $image_alt ) { |
| 191 | require_once ABSPATH . 'wp-admin/includes/media.php'; |
| 192 | require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 193 | require_once ABSPATH . 'wp-admin/includes/image.php'; |
| 194 | |
| 195 | // Since the media_sideload_image function is expensive and can take longer to complete |
| 196 | // the process of downloading the external image and uploading it to the media library, |
| 197 | // here we are increasing the time limit to avoid any issues. |
| 198 | set_time_limit( 150 ); |
| 199 | wp_raise_memory_limit( 'image' ); |
| 200 | |
| 201 | return media_sideload_image( $image_src, $product_id, $image_alt, 'id' ); |
| 202 | } |
| 203 | |
| 204 | /** |
| 205 | * Update the product with the new content. |
| 206 | * |
| 207 | * @param \WC_Product $product The product. |
| 208 | * @param int|string|WP_Error $product_image_id The product image ID. |
| 209 | * @param string $product_title The product title. |
| 210 | * @param string $product_description The product description. |
| 211 | * @param int $product_price The product price. |
| 212 | * |
| 213 | * @return int|\WP_Error |
| 214 | */ |
| 215 | private function product_update( $product, $product_image_id, $product_title, $product_description, $product_price ) { |
| 216 | if ( ! $product instanceof \WC_Product ) { |
| 217 | return new WP_Error( 'invalid_product', __( 'Invalid product.', 'woocommerce' ) ); |
| 218 | } |
| 219 | |
| 220 | if ( ! is_wp_error( $product_image_id ) ) { |
| 221 | $product->set_image_id( $product_image_id ); |
| 222 | } else { |
| 223 | wc_get_logger()->warning( |
| 224 | sprintf( |
| 225 | // translators: %s is a generated error message. |
| 226 | __( 'The image upload failed: "%s", creating the product without image', 'woocommerce' ), |
| 227 | $product_image_id->get_error_message() |
| 228 | ), |
| 229 | ); |
| 230 | } |
| 231 | $product->set_name( $product_title ); |
| 232 | $product->set_description( $product_description ); |
| 233 | $product->set_price( $product_price ); |
| 234 | $product->set_regular_price( $product_price ); |
| 235 | $product->set_slug( sanitize_title( $product_title ) ); |
| 236 | $product->save(); |
| 237 | |
| 238 | return $product->get_id(); |
| 239 | } |
| 240 | } |
| 241 |