diff --git a/includes/API/Abstracts/WC_Rest_API_Modifier.php b/includes/API/Abstracts/WC_Rest_API_Modifier.php new file mode 100644 index 0000000..bd3ab48 --- /dev/null +++ b/includes/API/Abstracts/WC_Rest_API_Modifier.php @@ -0,0 +1,67 @@ + (int) $id ); + } + + /** + * BUG FIX: some servers are not returning the correct meta_data if it is left as WC_Meta_Data objects + * NOTE: it only seems to effect some versions of PHP, or some plugins are adding weird meta_data types + * The result is mata_data: [{}, {}, {}] ie: empty objects, I think json_encode can't handle the WC_Meta_Data objects + * + * @TODO - I need to find out why this is happening + * + * @param WC_Data $object + * @return array + */ + protected function parse_meta_data( WC_Data $object ): array { + return array_map( function( $meta_data ) { + return $meta_data->get_data(); + }, $object->get_meta_data()); + } + + /** + * BUG FIX: the response for some records can be huge, eg: + * - product descriptions with lots of HTML, + * - I've seen products with 1800+ meta_data objects + * + * This is just a helper function to try and alert us to these large responses + * + * @param WP_REST_Response $response + * @param WC_Data $object + */ + protected function log_large_rest_response( WP_REST_Response $response, WC_Data $object ) { + $response_size = strlen( serialize( $response->data ) ); + $max_response_size = 100000; + if ( $response_size > $max_response_size ) { + Logger::log( "Object ID {$object->get_id()} has a response size of {$response_size} bytes, exceeding the limit of {$max_response_size} bytes." ); + } + } +} diff --git a/includes/API/Customers.php b/includes/API/Customers.php index c2a1d3b..498eb2b 100644 --- a/includes/API/Customers.php +++ b/includes/API/Customers.php @@ -6,14 +6,14 @@ use Ramsey\Uuid\Uuid; use WC_Customer; use WCPOS\WooCommercePOS\Logger; +use WP_Error; use WP_REST_Request; use WP_REST_Response; use WP_User; use WP_User_Query; -use WP_Meta_Query; -class Customers { - private $request; +class Customers extends Abstracts\WC_Rest_API_Modifier { + use Traits\Uuid_Handler; /** * Customers constructor. @@ -22,6 +22,7 @@ class Customers { */ public function __construct( WP_REST_Request $request ) { $this->request = $request; + $this->uuids = $this->get_all_usermeta_uuids(); add_filter( 'rest_request_before_callbacks', array( $this, 'rest_request_before_callbacks' ), 10, 3 ); add_filter( 'woocommerce_rest_customer_query', array( $this, 'customer_query' ), 10, 2 ); @@ -96,43 +97,30 @@ public function rest_request_before_callbacks( $response, $handler, $request ) { public function customer_response( WP_REST_Response $response, WP_User $user_data, WP_REST_Request $request ): WP_REST_Response { $data = $response->get_data(); - /** - * Make sure the customer has a uuid - */ - $uuid = get_user_meta( $user_data->ID, '_woocommerce_pos_uuid', true ); - if ( ! $uuid ) { - $uuid = Uuid::uuid4()->toString(); - update_user_meta( $user_data->ID, '_woocommerce_pos_uuid', $uuid ); - try { - $customer = new WC_Customer( $user_data->ID ); - $data['meta_data'] = $customer->get_meta_data(); - } catch ( Exception $e ) { - Logger::log( 'Error getting customer meta data: ' . $e->getMessage() ); - } - } - - /** - * In the WC REST Customers Controller -> get_formatted_item_data_core function, the customer's - * meta_data is only added for administrators. I assume this is for privacy/security reasons. - * - * Cashiers are not always administrators so we need to add the meta_data for uuids. - * @TODO - are there any other meta_data we need to add? - */ - if ( empty( $data['meta_data'] ) ) { - try { - $customer = new WC_Customer( $user_data->ID ); - $data['meta_data'] = array_values( array_filter( $customer->get_meta_data(), function ( $meta ) { - return '_woocommerce_pos_uuid' === $meta->key; - })); - } catch ( Exception $e ) { - Logger::log( 'Error getting customer meta data: ' . $e->getMessage() ); - } - } - - /** - * Reset the new response data - */ - $response->set_data( $data ); + // Add the uuid to the response + $this->maybe_add_user_uuid( $user_data ); + + /** + * Add the customer meta data to the response + * + * In the WC REST Customers Controller -> get_formatted_item_data_core function, the customer's + * meta_data is only added for administrators. I assume this is for privacy/security reasons. + * + * NOTE: for now we are only adding the uuid meta_data + * @TODO - are there any other meta_data we need to add? + */ + try { + $customer = new WC_Customer( $user_data->ID ); + $data['meta_data'] = array_values( array_filter( $customer->get_meta_data(), function ( $meta ) { + return '_woocommerce_pos_uuid' === $meta->key; + })); + } catch ( Exception $e ) { + Logger::log( 'Error getting customer meta data: ' . $e->getMessage() ); + } + + // Set any changes to the response data + $response->set_data( $data ); + $this->log_large_rest_response( $response, $product ); return $response; } @@ -262,9 +250,9 @@ public function modify_user_query( $user_query ) { * * @param array $fields * - * @return array|void + * @return array|WP_Error */ - public function get_all_posts( array $fields = array() ) { + public function get_all_posts( array $fields = array() ): array { $args = array( 'fields' => 'ID', // Only return user IDs ); @@ -275,18 +263,17 @@ public function get_all_posts( array $fields = array() ) { } $user_query = new WP_User_Query( $args ); - $user_ids = $user_query->get_results(); - // wpdb returns id as string, we need int - return array_map( array( $this, 'format_id' ), $user_ids ); - } - - /** - * @param int $user_id - * - * @return object - */ - private function format_id( $user_id ): object { - return (object) array( 'id' => (int) $user_id ); + try { + $user_ids = $user_query->get_results(); + return array_map( array( $this, 'format_id' ), $user_ids ); + } catch ( Exception $e ) { + Logger::log( 'Error fetching order IDs: ' . $e->getMessage() ); + return new WP_Error( + 'woocommerce_pos_rest_cannot_fetch', + 'Error fetching customer IDs.', + array( 'status' => 500 ) + ); + } } } diff --git a/includes/API/Orders.php b/includes/API/Orders.php index 55231ca..a159ee2 100644 --- a/includes/API/Orders.php +++ b/includes/API/Orders.php @@ -9,6 +9,8 @@ use WC_Order_Item; use WC_Product_Variation; use WCPOS\WooCommercePOS\Logger; +use WP_Error; +use WP_HTTP_Response; use WP_REST_Request; use WP_REST_Response; use function in_array; @@ -18,8 +20,8 @@ use WC_Order_Query; use WP_Query; -class Orders { - private $request; +class Orders extends Abstracts\WC_Rest_API_Modifier { + use Traits\Uuid_Handler; private $posted; @@ -31,6 +33,7 @@ class Orders { public function __construct( WP_REST_Request $request ) { $this->request = $request; $this->posted = $this->request->get_json_params(); + $this->uuids = $this->get_all_postmeta_uuids(); if ( 'POST' == $request->get_method() ) { $this->incoming_shop_order(); @@ -218,25 +221,10 @@ public function pre_insert_shop_order_object( $order, $request, $creating ) { public function order_response( WP_REST_Response $response, WC_Order $order, WP_REST_Request $request ): WP_REST_Response { $data = $response->get_data(); - /** - * make sure the order has a uuid - */ - $uuid = $order->get_meta( '_woocommerce_pos_uuid' ); - if ( ! $uuid ) { - $uuid = Uuid::uuid4()->toString(); - $order->update_meta_data( '_woocommerce_pos_uuid', $uuid ); - $order->save_meta_data(); - $data['meta_data'] = $order->get_meta_data(); - } - - /** - * reset the new response data - */ - $response->set_data( $data ); + // Add UUID to order + $this->maybe_add_post_uuid( $order ); - /** - * Add link for order payment. - */ + // Add payment link to the order. $pos_payment_url = add_query_arg(array( 'pay_for_order' => true, 'key' => $order->get_order_key(), @@ -244,12 +232,19 @@ public function order_response( WP_REST_Response $response, WC_Order $order, WP_ $response->add_link( 'payment', $pos_payment_url, array( 'foo' => 'bar' ) ); - /** - * Add link for order receipt. - */ + // Add receipt link to the order. $pos_receipt_url = get_home_url( null, '/wcpos-checkout/wcpos-receipt/' . $order->get_id() ); $response->add_link( 'receipt', $pos_receipt_url ); + /** + * Make sure we parse the meta data before returning the response + */ + $order->save_meta_data(); // make sure the meta data is saved + $data['meta_data'] = $this->parse_meta_data( $order ); + + $response->set_data( $data ); + $this->log_large_rest_response( $response, $order ); + return $response; } @@ -261,15 +256,7 @@ public function order_response( WP_REST_Response $response, WC_Order $order, WP_ */ public function order_get_items( array $items, WC_Order $order, array $item_type ): array { foreach ( $items as $item ) { - /** - * make sure the cart items have a uuid - */ - $uuid = $item->get_meta( '_woocommerce_pos_uuid' ); - if ( ! $uuid ) { - $uuid = Uuid::uuid4()->toString(); - $item->update_meta_data( '_woocommerce_pos_uuid', $uuid ); - $item->save_meta_data(); - } + $this->maybe_add_order_item_uuid( $item ); } return $items; @@ -428,7 +415,7 @@ public function orderby_additions( array $clauses, WP_Query $wp_query ): array { * * @param array $fields * - * @return array + * @return array|WP_Error */ public function get_all_posts( array $fields = array() ): array { $args = array( @@ -441,21 +428,14 @@ public function get_all_posts( array $fields = array() ): array { try { $order_ids = $order_query->get_orders(); + return array_map( array( $this, 'format_id' ), $order_ids ); } catch ( Exception $e ) { Logger::log( 'Error fetching order IDs: ' . $e->getMessage() ); - return array(); // Return an empty array in case of an error + return new WP_Error( + 'woocommerce_pos_rest_cannot_fetch', + 'Error fetching order IDs.', + array( 'status' => 500 ) + ); } - - // wpdb returns id as string, we need int - return array_map( array( $this, 'format_id' ), $order_ids ); - } - - /** - * @param string $order_id - * - * @return object - */ - private function format_id( string $order_id ): object { - return (object) array( 'id' => (int) $order_id ); } } diff --git a/includes/API/Product_Categories.php b/includes/API/Product_Categories.php index dfa0c5e..f7fbeb4 100644 --- a/includes/API/Product_Categories.php +++ b/includes/API/Product_Categories.php @@ -2,80 +2,72 @@ namespace WCPOS\WooCommercePOS\API; +use Exception; use Ramsey\Uuid\Uuid; +use WCPOS\WooCommercePOS\Logger; +use WP_Error; use WP_REST_Request; use WP_REST_Response; -class Product_Categories { - private $request; +class Product_Categories extends Abstracts\WC_Rest_API_Modifier { + use Traits\Uuid_Handler; - /** - * Customers constructor. - * - * @param $request WP_REST_Request - */ - public function __construct( WP_REST_Request $request ) { - $this->request = $request; + /** + * Customers constructor. + * + * @param $request WP_REST_Request + */ + public function __construct( WP_REST_Request $request ) { + $this->request = $request; - add_filter( 'woocommerce_rest_prepare_product_cat', array( $this, 'product_categories_response' ), 10, 3 ); - } + add_filter( 'woocommerce_rest_prepare_product_cat', array( $this, 'product_categories_response' ), 10, 3 ); + } - /** - * Filter the product response. - * - * @param WP_REST_Response $response The response object. - * @param object $item The original term object. - * @param WP_REST_Request $request Request object. - * - * @return WP_REST_Response $response The response object. - */ - public function product_categories_response( WP_REST_Response $response, object $item, WP_REST_Request $request ): WP_REST_Response { - $data = $response->get_data(); + /** + * Filter the product response. + * + * @param WP_REST_Response $response The response object. + * @param object $item The original term object. + * @param WP_REST_Request $request Request object. + * + * @return WP_REST_Response $response The response object. + */ + public function product_categories_response( WP_REST_Response $response, object $item, WP_REST_Request $request ): WP_REST_Response { + $data = $response->get_data(); - /** - * Make sure the product has a uuid - */ - $uuid = get_term_meta( $item->term_id, '_woocommerce_pos_uuid', true ); - if ( ! $uuid ) { - $uuid = Uuid::uuid4()->toString(); - add_term_meta( $item->term_id, '_woocommerce_pos_uuid', $uuid, true ); - } - $data['uuid'] = $uuid; + // Make sure the term has a uuid + $data['uuid'] = $this->get_term_uuid( $item ); - /** - * Reset the new response data - */ - $response->set_data( $data ); + // Reset the new response data + $response->set_data( $data ); - return $response; - } + return $response; + } - /** - * Returns array of all product category ids - * - * @param array $fields - * - * @return array - */ - public function get_all_posts( array $fields = array() ): array { - $args = array( - 'taxonomy' => 'product_cat', - 'hide_empty' => false, - 'fields' => 'ids', - ); + /** + * Returns array of all product category ids + * + * @param array $fields + * + * @return array|WP_Error + */ + public function get_all_posts( array $fields = array() ): array { + $args = array( + 'taxonomy' => 'product_cat', + 'hide_empty' => false, + 'fields' => 'ids', + ); - $product_category_ids = get_terms( $args ); - - // Convert the array of cat IDs to an array of objects with cat IDs as integers - return array_map( array( $this, 'format_id' ), $product_category_ids ); - } - - /** - * @param string $product_category_id - * - * @return object - */ - private function format_id( string $product_category_id ): object { - return (object) array( 'id' => (int) $product_category_id ); - } + try { + $product_category_ids = get_terms( $args ); + return array_map( array( $this, 'format_id' ), $product_category_ids ); + } catch ( Exception $e ) { + Logger::log( 'Error fetching product IDs: ' . $e->getMessage() ); + return new WP_Error( + 'woocommerce_pos_rest_cannot_fetch', + 'Error fetching product category IDs.', + array( 'status' => 500 ) + ); + } + } } diff --git a/includes/API/Product_Tags.php b/includes/API/Product_Tags.php index ba23acd..dc96613 100644 --- a/includes/API/Product_Tags.php +++ b/includes/API/Product_Tags.php @@ -2,12 +2,15 @@ namespace WCPOS\WooCommercePOS\API; +use Exception; use Ramsey\Uuid\Uuid; +use WCPOS\WooCommercePOS\Logger; +use WP_Error; use WP_REST_Request; use WP_REST_Response; -class Product_Tags { - private $request; +class Product_Tags extends Abstracts\WC_Rest_API_Modifier { + use Traits\Uuid_Handler; /** * Customers constructor. @@ -32,20 +35,11 @@ public function __construct( WP_REST_Request $request ) { public function product_tags_response( WP_REST_Response $response, object $item, WP_REST_Request $request ): WP_REST_Response { $data = $response->get_data(); - /** - * Make sure the product has a uuid - */ - $uuid = get_term_meta( $item->term_id, '_woocommerce_pos_uuid', true ); - if ( ! $uuid ) { - $uuid = Uuid::uuid4()->toString(); - add_term_meta( $item->term_id, '_woocommerce_pos_uuid', $uuid, true ); - } - $data['uuid'] = $uuid; + // Make sure the term has a uuid + $data['uuid'] = $this->get_term_uuid( $item ); - /** - * Reset the new response data - */ - $response->set_data( $data ); + // Reset the new response data + $response->set_data( $data ); return $response; } @@ -55,7 +49,7 @@ public function product_tags_response( WP_REST_Response $response, object $item, * * @param array $fields * - * @return array + * @return array|WP_Error */ public function get_all_posts( array $fields = array() ): array { $args = array( @@ -64,18 +58,16 @@ public function get_all_posts( array $fields = array() ): array { 'fields' => 'ids', ); - $product_tag_ids = get_terms( $args ); - - // Convert the array of tag IDs to an array of objects with tag IDs as integers - return array_map( array( $this, 'format_id' ), $product_tag_ids ); - } - - /** - * @param string $product_tag_id - * - * @return object - */ - private function format_id( string $product_tag_id ): object { - return (object) array( 'id' => (int) $product_tag_id ); + try { + $product_tag_ids = get_terms( $args ); + return array_map( array( $this, 'format_id' ), $product_tag_ids ); + } catch ( Exception $e ) { + Logger::log( 'Error fetching product IDs: ' . $e->getMessage() ); + return new WP_Error( + 'woocommerce_pos_rest_cannot_fetch', + 'Error fetching product tags IDs.', + array( 'status' => 500 ) + ); + } } } diff --git a/includes/API/Product_Variations.php b/includes/API/Product_Variations.php index 20bf541..c73283b 100644 --- a/includes/API/Product_Variations.php +++ b/includes/API/Product_Variations.php @@ -2,19 +2,18 @@ namespace WCPOS\WooCommercePOS\API; +use Exception; use Ramsey\Uuid\Uuid; use WC_Data; +use WP_Error; use WP_Query; use WP_REST_Request; use WP_REST_Response; -use WC_Product_Query; use WCPOS\WooCommercePOS\Logger; -use function image_downsize; -use function is_array; -use function wp_get_attachment_metadata; -class Product_Variations { - private $request; +class Product_Variations extends Abstracts\WC_Rest_API_Modifier { + use Traits\Product_Helpers; + use Traits\Uuid_Handler; /** * Products constructor. @@ -23,9 +22,10 @@ class Product_Variations { */ public function __construct( WP_REST_Request $request ) { $this->request = $request; + $this->get_all_postmeta_uuids(); + $this->add_product_image_src_filter(); add_filter( 'woocommerce_rest_prepare_product_variation_object', array( $this, 'product_response' ), 10, 3 ); - add_filter( 'wp_get_attachment_image_src', array( $this, 'product_image_src' ), 10, 4 ); } /** @@ -40,46 +40,23 @@ public function __construct( WP_REST_Request $request ) { public function product_response( WP_REST_Response $response, WC_Data $product, WP_REST_Request $request ): WP_REST_Response { $data = $response->get_data(); - /** - * Make sure the product has a uuid - */ - $uuid = $product->get_meta( '_woocommerce_pos_uuid' ); - if ( ! $uuid ) { - $uuid = Uuid::uuid4()->toString(); - $product->update_meta_data( '_woocommerce_pos_uuid', $uuid ); - $product->save_meta_data(); - $data['meta_data'] = $product->get_meta_data(); - } + // Add the UUID to the product response + $this->maybe_add_post_uuid( $product ); - /** - * Add barcode field - */ - $barcode_field = woocommerce_pos_get_settings( 'general', 'barcode_field' ); - $data['barcode'] = $product->get_meta( $barcode_field ); - - /** - * Truncate the product description - */ - $max_length = 100; - $plain_text_description = wp_strip_all_tags( $data['description'], true ); - if ( strlen( $plain_text_description ) > $max_length ) { - $truncated_description = substr( $plain_text_description, 0, $max_length - 3 ) . '...'; - $data['description'] = $truncated_description; - } + // Add the barcode to the product response + $data['barcode'] = $this->get_barcode( $product ); - /** - * Check the response size and log a debug message if it is over the maximum size. - */ - $response_size = strlen( serialize( $response->data ) ); - $max_response_size = 10000; - if ( $response_size > $max_response_size ) { - Logger::log( "Variation ID {$product->get_id()} has a response size of {$response_size} bytes, exceeding the limit of {$max_response_size} bytes." ); - } + // Truncate the product description and short_description + $data['description'] = $this->truncate_text( $data['description'] ); + + /** + * Make sure we parse the meta data before returning the response + */ + $product->save_meta_data(); // make sure the meta data is saved + $data['meta_data'] = $this->parse_meta_data( $product ); - /** - * Reset the new response data - */ - $response->set_data( $data ); + $response->set_data( $data ); + $this->log_large_rest_response( $response, $product ); return $response; } @@ -89,9 +66,9 @@ public function product_response( WP_REST_Response $response, WC_Data $product, * * @param array $fields * - * @return array|void + * @return array|WP_Error */ - public function get_all_posts( array $fields = array() ) { + public function get_all_posts( array $fields = array() ): array { $parent_id = $this->request['product_id']; $args = array( @@ -103,75 +80,17 @@ public function get_all_posts( array $fields = array() ) { ); $variation_query = new WP_Query( $args ); - $variation_ids = $variation_query->posts; - // wpdb returns id as string, we need int - return array_map( array( $this, 'format_id' ), $variation_ids ); - } - - /** - * Returns thumbnail if it exists, if not, returns the WC placeholder image. - * - * @param int $id - * - * @return string - */ - private function get_thumbnail( int $id ): string { - $image = false; - $thumb_id = get_post_thumbnail_id( $id ); - - if ( $thumb_id ) { - $image = wp_get_attachment_image_src( $thumb_id, 'shop_thumbnail' ); + try { + $variation_ids = $variation_query->posts; + return array_map( array( $this, 'format_id' ), $variation_ids ); + } catch ( Exception $e ) { + Logger::log( 'Error fetching product IDs: ' . $e->getMessage() ); + return new WP_Error( + 'woocommerce_pos_rest_cannot_fetch', + 'Error fetching variation IDs.', + array( 'status' => 500 ) + ); } - - if ( is_array( $image ) ) { - return $image[0]; - } - - return wc_placeholder_img_src(); - } - - /** - * Filters the attachment image source result. - * The WC REST API returns 'full' images by default, but we want to return 'shop_thumbnail' images. - * - * @param array|false $image { - * Array of image data, or boolean false if no image is available. - * - * @type string $0 Image source URL. - * @type int $1 Image width in pixels. - * @type int $2 Image height in pixels. - * @type bool $3 Whether the image is a resized image. - * } - * @param int $attachment_id Image attachment ID. - * @param string|int[] $size Requested image size. Can be any registered image size name, or - * an array of width and height values in pixels (in that order). - * @param bool $icon Whether the image should be treated as an icon. - */ - public function product_image_src( $image, int $attachment_id, $size, bool $icon ) { - // Get the metadata for the attachment. - $metadata = wp_get_attachment_metadata( $attachment_id ); - - // Use the 'woocommerce_gallery_thumbnail' size if it exists. - if ( isset( $metadata['sizes']['woocommerce_gallery_thumbnail'] ) ) { - return image_downsize( $attachment_id, 'woocommerce_gallery_thumbnail' ); - } - // If 'woocommerce_gallery_thumbnail' doesn't exist, try the 'thumbnail' size. - else if ( isset( $metadata['sizes']['thumbnail'] ) ) { - return image_downsize( $attachment_id, 'thumbnail' ); - } - // If neither 'woocommerce_gallery_thumbnail' nor 'thumbnail' sizes exist, return the original $image. - else { - return $image; - } - } - - /** - * @param string $variation_id - * - * @return object - */ - private function format_id( $variation_id ) { - return (object) array( 'id' => (int) $variation_id ); } } diff --git a/includes/API/Products.php b/includes/API/Products.php index bfada30..aced384 100644 --- a/includes/API/Products.php +++ b/includes/API/Products.php @@ -2,24 +2,22 @@ namespace WCPOS\WooCommercePOS\API; -use Ramsey\Uuid\Uuid; +use Exception; use WC_Data; use WC_Product; use WCPOS\WooCommercePOS\Logger; +use WP_Error; +use WP_HTTP_Response; use WP_Query; use WP_REST_Request; use WP_REST_Response; -use WC_Product_Query; -use function image_downsize; -use function is_array; -use function wp_get_attachment_metadata; /** * @property string $search_term */ -class Products { - private $request; - private $uuids; +class Products extends Abstracts\WC_Rest_API_Modifier { + use Traits\Product_Helpers; + use Traits\Uuid_Handler; /** * Products constructor. @@ -28,7 +26,8 @@ class Products { */ public function __construct( WP_REST_Request $request ) { $this->request = $request; - $this->uuids = $this->get_all_uuids(); + $this->uuids = $this->get_all_postmeta_uuids(); + $this->add_product_image_src_filter(); add_filter( 'rest_request_before_callbacks', array( $this, 'rest_request_before_callbacks' ), 10, 3 ); add_filter( 'woocommerce_rest_prepare_product_object', array( $this, 'product_response' ), 10, 3 ); @@ -37,83 +36,6 @@ public function __construct( WP_REST_Request $request ) { add_filter( 'posts_clauses', array( $this, 'posts_clauses' ), 10, 2 ); add_filter( 'woocommerce_rest_product_schema', array( $this, 'add_barcode_to_product_schema' ) ); add_action( 'woocommerce_rest_insert_product_object', array( $this, 'insert_product_object' ), 10, 3 ); - add_filter( 'wp_get_attachment_image_src', array( $this, 'product_image_src' ), 10, 4 ); - -// add_filter('rest_pre_echo_response', function($result, $server, $request) { -// $logger = wc_get_logger(); -// $test = wp_json_encode( $result, 0 ); -// if(is_array($result)) { -// foreach($result as $record) { -// if(is_array($record) && isset($record['meta_data'])) { -// $test = wp_json_encode( $record, 0 ); -// $logger->info($test, array('source' => 'wcpos-support-3')); -// } -// } -// } -// return $result; -// }, 10, 3); - -// add_filter('rest_pre_serve_request', function($served, $result, $request, $server) { -// $logger = wc_get_logger(); -//// $logger->info(wp_json_encode($result), array('source' => 'wcpos-support')); -// $data = $result->get_data(); -// if(is_array($data)) { -// foreach($data as $record) { -// if(isset($record['meta_data'])) { -// $logger->info(wp_json_encode($record['meta_data']), array('source' => 'wcpos-support')); -// } -// } -// } -// return $served; -// }, 10, 4); - } - - /** - * Note: this gets all postmeta uuids, including orders, we're just interested in doing a check sanity check - * This addresses a bug where I have seen two products with the same uuid - * - * @return array - */ - private function get_all_uuids() : array { - global $wpdb; - $result = $wpdb->get_col( - " - SELECT meta_value - FROM $wpdb->postmeta - WHERE meta_key = '_woocommerce_pos_uuid' - " - ); - return $result; - } - - /** - * Make sure the product has a uuid - */ - private function maybe_add_uuid( WC_Product $product ) { - $uuids = get_post_meta( $product->get_id(), '_woocommerce_pos_uuid', false ); - $uuid_counts = array_count_values( $this->uuids ); - - if ( empty( $uuids ) ) { - $this->add_uuid_meta_data( $product ); - } - - if ( count( $uuids ) > 1 || count( $uuids ) === 1 && $uuid_counts[ $uuids[0] ] > 1 ) { - delete_post_meta( $product->get_id(), '_woocommerce_pos_uuid' ); - $this->add_uuid_meta_data( $product ); - } - } - - /** - * - */ - private function add_uuid_meta_data( WC_Product $product ) { - $uuid = Uuid::uuid4()->toString(); - while ( in_array( $uuid, $this->uuids ) ) { // ensure the new UUID is unique - $uuid = Uuid::uuid4()->toString(); - } - $this->uuids[] = $uuid; // update the UUID list - $product->update_meta_data( '_woocommerce_pos_uuid', $uuid ); - $product->save_meta_data(); } /** @@ -220,35 +142,15 @@ public function insert_product_object( WC_Data $object, WP_REST_Request $request public function product_response( WP_REST_Response $response, WC_Product $product, WP_REST_Request $request ): WP_REST_Response { $data = $response->get_data(); - /** - * Make sure the product has a uuid - */ - $this->maybe_add_uuid( $product ); + // Add the UUID to the product response + $this->maybe_add_post_uuid( $product ); - /** - * Add barcode field - */ - $barcode_field = woocommerce_pos_get_settings( 'general', 'barcode_field' ); - $data['barcode'] = $product->get_meta( $barcode_field ); + // Add the barcode to the product response + $data['barcode'] = $this->get_barcode( $product ); - /** - * Truncate the product description - */ - $max_length = 100; - $plain_text_description = wp_strip_all_tags( $data['description'], true ); - if ( strlen( $plain_text_description ) > $max_length ) { - $truncated_description = substr( $plain_text_description, 0, $max_length - 3 ) . '...'; - $data['description'] = $truncated_description; - } - - /** - * Check the response size and log a debug message if it is over the maximum size. - */ - $response_size = strlen( serialize( $response->data ) ); - $max_response_size = 100000; - if ( $response_size > $max_response_size ) { - Logger::log( "Product ID {$product->get_id()} has a response size of {$response_size} bytes, exceeding the limit of {$max_response_size} bytes." ); - } + // Truncate the product description and short_description + $data['description'] = $this->truncate_text( $data['description'] ); + $data['short_description'] = $this->truncate_text( $data['short_description'] ); /** * If product is variable, add the max and min prices and add them to the meta data @@ -281,19 +183,18 @@ public function product_response( WP_REST_Response $response, WC_Product $produc } else { // Update the meta data with the successfully encoded price data $product->update_meta_data( '_woocommerce_pos_variable_prices', $encoded_price ); - $product->save_meta_data(); } } - /** - * Reset the new response data - * BUG FIX: some servers are not returning the correct meta_data if it is left as WC_Meta_Data objects + * Make sure we parse the meta data before returning the response */ - $data['meta_data'] = array_map( function( $meta_data ) { - return $meta_data->get_data(); - }, $product->get_meta_data()); + $product->save_meta_data(); // make sure the meta data is saved + $data['meta_data'] = $this->parse_meta_data( $product ); + + // Set any changes to the response data $response->set_data( $data ); + $this->log_large_rest_response( $response, $product ); return $response; } @@ -438,7 +339,7 @@ public function posts_search( string $search, WP_Query $wp_query ): string { * * @param array $fields * - * @return array + * @return array|WP_Error */ public function get_all_posts( array $fields = array() ): array { $pos_only_products = woocommerce_pos_get_settings( 'general', 'pos_only_products' ); @@ -466,75 +367,18 @@ public function get_all_posts( array $fields = array() ): array { } $product_query = new WP_Query( $args ); - $product_ids = $product_query->posts; - - // Convert the array of product IDs to an array of objects with product IDs as integers - return array_map( array( $this, 'format_id' ), $product_ids ); - } - - /** - * Returns thumbnail if it exists, if not, returns the WC placeholder image. - * - * @param int $id - * - * @return string - */ - private function get_thumbnail( int $id ): string { - $image = false; - $thumb_id = get_post_thumbnail_id( $id ); - - if ( $thumb_id ) { - $image = wp_get_attachment_image_src( $thumb_id, 'shop_thumbnail' ); - } - - if ( is_array( $image ) ) { - return $image[0]; - } - return wc_placeholder_img_src(); - } - - /** - * Filters the attachment image source result. - * The WC REST API returns 'full' images by default, but we want to return 'shop_thumbnail' images. - * - * @param array|false $image { - * Array of image data, or boolean false if no image is available. - * - * @type string $0 Image source URL. - * @type int $1 Image width in pixels. - * @type int $2 Image height in pixels. - * @type bool $3 Whether the image is a resized image. - * } - * @param int $attachment_id Image attachment ID. - * @param string|int[] $size Requested image size. Can be any registered image size name, or - * an array of width and height values in pixels (in that order). - * @param bool $icon Whether the image should be treated as an icon. - */ - public function product_image_src( $image, int $attachment_id, $size, bool $icon ) { - // Get the metadata for the attachment. - $metadata = wp_get_attachment_metadata( $attachment_id ); - - // Use the 'woocommerce_gallery_thumbnail' size if it exists. - if ( isset( $metadata['sizes']['woocommerce_gallery_thumbnail'] ) ) { - return image_downsize( $attachment_id, 'woocommerce_gallery_thumbnail' ); - } - // If 'woocommerce_gallery_thumbnail' doesn't exist, try the 'thumbnail' size. - else if ( isset( $metadata['sizes']['thumbnail'] ) ) { - return image_downsize( $attachment_id, 'thumbnail' ); - } - // If neither 'woocommerce_gallery_thumbnail' nor 'thumbnail' sizes exist, return the original $image. - else { - return $image; + try { + $product_ids = $product_query->posts; + return array_map( array( $this, 'format_id' ), $product_ids ); + } catch ( Exception $e ) { + Logger::log( 'Error fetching product IDs: ' . $e->getMessage() ); + return new WP_Error( + 'woocommerce_pos_rest_cannot_fetch', + 'Error fetching product IDs.', + array( 'status' => 500 ) + ); } } - /** - * @param string $product_id - * - * @return object - */ - private function format_id( string $product_id ): object { - return (object) array( 'id' => (int) $product_id ); - } } diff --git a/includes/API/Taxes.php b/includes/API/Taxes.php index e051e18..2753bc8 100644 --- a/includes/API/Taxes.php +++ b/includes/API/Taxes.php @@ -2,10 +2,10 @@ namespace WCPOS\WooCommercePOS\API; +use WP_Error; use WP_REST_Request; -class Taxes { - private $request; +class Taxes extends Abstracts\WC_Rest_API_Modifier { /** * Taxes constructor. @@ -40,27 +40,20 @@ public function check_permissions( $permission ) { * * @param array $fields * - * @return array|void + * @return array|WP_Error */ - public function get_all_posts( array $fields = array() ) { + public function get_all_posts( array $fields = array() ): array { global $wpdb; - $all_posts = $wpdb->get_results( ' + $results = $wpdb->get_results( ' SELECT tax_rate_id as id FROM ' . $wpdb->prefix . 'woocommerce_tax_rates - ' ); + ', ARRAY_A ); - // wpdb returns id as string, we need int - return array_map( array( $this, 'format_id' ), $all_posts ); - } - - /** - * @param object $record - * - * @return object - */ - private function format_id( $record ) { - $record->id = (int) $record->id; + // Convert array of arrays into array of strings (ids) + $all_ids = array_map( function( $item ) { + return strval( $item['id'] ); + }, $results ); - return $record; + return array_map( array( $this, 'format_id' ), $all_ids ); } } diff --git a/includes/API/Traits/Product_Helpers.php b/includes/API/Traits/Product_Helpers.php new file mode 100644 index 0000000..0943530 --- /dev/null +++ b/includes/API/Traits/Product_Helpers.php @@ -0,0 +1,94 @@ +get_meta( $barcode_field ); + } + + /** + * @param string $text + * @return string + */ + private function truncate_text( string $text ): string { + $max_length = 100; + $result = wp_strip_all_tags( $text, true ); + if ( strlen( $result ) > $max_length ) { + $result = substr( $result, 0, $max_length - 3 ) . '...'; + } + return $result; + } + +} diff --git a/includes/API/Traits/Uuid_Handler.php b/includes/API/Traits/Uuid_Handler.php new file mode 100644 index 0000000..de85fee --- /dev/null +++ b/includes/API/Traits/Uuid_Handler.php @@ -0,0 +1,136 @@ +get_col( + " + SELECT meta_value + FROM $wpdb->postmeta + WHERE meta_key = '_woocommerce_pos_uuid' + " + ); + return $result; + } + + /** + * @return array + */ + private function get_all_usermeta_uuids(): array { + global $wpdb; + $result = $wpdb->get_col( + " + SELECT meta_value + FROM $wpdb->usermeta + WHERE meta_key = '_woocommerce_pos_uuid' + " + ); + return $result; + } + + /** + * Make sure the product has a uuid + */ + private function maybe_add_post_uuid( WC_Data $object ) { + $uuids = get_post_meta( $object->get_id(), '_woocommerce_pos_uuid', false ); + $uuid_counts = array_count_values( $this->uuids ); + + // if there is no uuid, add one, ie: new product + if ( empty( $uuids ) ) { + $object->update_meta_data( '_woocommerce_pos_uuid', $this->create_uuid() ); + } + + // this is a sanity check, if there is more than one uuid for a product, delete them all and add a new one + if ( count( $uuids ) > 1 || count( $uuids ) === 1 && $uuid_counts[ $uuids[0] ] > 1 ) { + delete_post_meta( $object->get_id(), '_woocommerce_pos_uuid' ); + $object->update_meta_data( '_woocommerce_pos_uuid', $this->create_uuid() ); + } + } + + /** + * @param WP_User $user + * @return void + */ + private function maybe_add_user_uuid( WP_User $user ) { + $uuids = get_user_meta( $user->ID, '_woocommerce_pos_uuid', false ); + $uuid_counts = array_count_values( $this->uuids ); + + if ( empty( $uuids ) ) { + update_user_meta( $user->ID, '_woocommerce_pos_uuid', $this->create_uuid() ); + } + + // this is a sanity check, if there is more than one uuid for a product, delete them all and add a new one + if ( count( $uuids ) > 1 || count( $uuids ) === 1 && $uuid_counts[ $uuids[0] ] > 1 ) { + delete_user_meta( $user->ID, '_woocommerce_pos_uuid' ); + update_user_meta( $user->ID, '_woocommerce_pos_uuid', $this->create_uuid() ); + } + } + + /** + * @TODO: this is called from the order class, so it doesn't have a sanity check yet + * + * @param WC_Order_Item $item + * @return void + */ + private function maybe_add_order_item_uuid( WC_Order_Item $item ) { + $uuid = $item->get_meta( '_woocommerce_pos_uuid' ); + if ( ! $uuid ) { + $uuid = Uuid::uuid4()->toString(); + $item->update_meta_data( '_woocommerce_pos_uuid', $uuid ); + $item->save_meta_data(); + } + } + + /** + * @TODO: sanity check + * + * @param object $item + * @return string + */ + private function get_term_uuid( object $item ): string { + $uuid = get_term_meta( $item->term_id, '_woocommerce_pos_uuid', true ); + if ( ! $uuid ) { + $uuid = Uuid::uuid4()->toString(); + add_term_meta( $item->term_id, '_woocommerce_pos_uuid', $uuid, true ); + } + return $uuid; + } + + /** + * @return string + */ + private function create_uuid(): string { + $uuid = Uuid::uuid4()->toString(); + while ( in_array( $uuid, $this->uuids ) ) { // ensure the new UUID is unique + Logger::log( 'This should not happen!!' ); + $uuid = Uuid::uuid4()->toString(); + } + $this->uuids[] = $uuid; // update the UUID list + return $uuid; + } + +} diff --git a/includes/Templates/Payment.php b/includes/Templates/Payment.php index 0f65faf..fbe6c3a 100644 --- a/includes/Templates/Payment.php +++ b/includes/Templates/Payment.php @@ -105,7 +105,7 @@ public function remove_scripts(): void { // Check if the source URL contains the active theme's directory if ( strpos( $src, $active_theme_directory ) !== false || in_array( $handle, $exclude_list ) ) { - wp_dequeue_style( $handle ); + wp_dequeue_script( $handle ); } }