diff --git a/.github/workflows/update-pot.yml b/.github/workflows/update-pot.yml index 5283215..752382b 100644 --- a/.github/workflows/update-pot.yml +++ b/.github/workflows/update-pot.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP with tools uses: shivammathur/setup-php@v2 diff --git a/includes/API/Orders.php b/includes/API/Orders.php deleted file mode 100644 index fc5945b..0000000 --- a/includes/API/Orders.php +++ /dev/null @@ -1,454 +0,0 @@ -request = $request; - $this->posted = $this->request->get_json_params(); - - if ( 'POST' == $request->get_method() ) { - $this->incoming_shop_order(); - } - - add_filter( 'rest_request_before_callbacks', array( $this, 'rest_request_before_callbacks' ), 10, 3 ); - add_filter( 'woocommerce_rest_shop_order_object_query', array( $this, 'order_query' ), 10, 2 ); - add_filter( - 'woocommerce_rest_pre_insert_shop_order_object', - array( - $this, - 'pre_insert_shop_order_object', - ), - 10, - 3 - ); - add_filter( 'woocommerce_rest_prepare_shop_order_object', array( $this, 'order_response' ), 10, 3 ); - add_filter( 'woocommerce_order_get_items', array( $this, 'order_get_items' ), 10, 3 ); - - add_action( 'woocommerce_rest_set_order_item', array( $this, 'rest_set_order_item' ), 10, 2 ); - add_filter( - 'woocommerce_product_variation_get_attributes', - array( - $this, - 'product_variation_get_attributes', - ), - 10, - 2 - ); - add_action( 'woocommerce_before_order_object_save', array( $this, 'before_order_object_save' ), 10, 2 ); - add_filter( 'posts_clauses', array( $this, 'orderby_additions' ), 10, 2 ); - add_filter( 'option_woocommerce_tax_based_on', array( $this, 'tax_based_on' ), 10, 2 ); - } - - /** - * Filters the response before executing any REST API callbacks. - * - * We can use this filter to bypass data validation checks - * - * @param mixed|WP_Error|WP_HTTP_Response|WP_REST_Response $response Result to send to the client. - * Usually a WP_REST_Response or WP_Error. - * @param array $handler Route handler used for the request. - * @param WP_REST_Request $request Request used to generate the response. - */ - public function rest_request_before_callbacks( $response, $handler, $request ) { - if ( is_wp_error( $response ) ) { - // Check if the error code 'rest_invalid_param' exists - if ( $response->get_error_message( 'rest_invalid_param' ) ) { - // Get the error data for 'rest_invalid_param' - $error_data = $response->get_error_data( 'rest_invalid_param' ); - - // Check if the invalid parameter was 'line_items' - if ( \array_key_exists( 'line_items', $error_data['params'] ) ) { - // Get the 'line_items' details - $line_items_details = $error_data['details']['line_items']; - - // Check if 'line_items[X][quantity]' has 'rest_invalid_type' - // Use a regular expression to match 'line_items[X][quantity]', where X is a number - if ( 'rest_invalid_type' === $line_items_details['code'] && - preg_match( '/^line_items\[\d+\]\[quantity\]$/', $line_items_details['data']['param'] ) ) { - if ( woocommerce_pos_get_settings( 'general', 'decimal_qty' ) ) { - unset( $error_data['params']['line_items'], $error_data['details']['line_items'] ); - } - } - - // Check if 'line_items[X][parent_name]' has 'rest_invalid_type' - // Use a regular expression to match 'line_items[X][parent_name]', where X is a number - if ( 'rest_invalid_type' === $line_items_details['code'] && - preg_match( '/^line_items\[\d+\]\[parent_name\]$/', $line_items_details['data']['param'] ) ) { - unset( $error_data['params']['line_items'], $error_data['details']['line_items'] ); - } - } - - // Check if the invalid parameter was 'billing' - if ( \array_key_exists( 'billing', $error_data['params'] ) ) { - // Get the 'billing' details - $billing_details = $error_data['details']['billing']; - - // Check if 'billing' has 'rest_invalid_email' - if ( 'rest_invalid_email' === $billing_details['code'] ) { - unset( $error_data['params']['billing'], $error_data['details']['billing'] ); - } - } - - // Check if the invalid parameter was 'orderby' - if ( \array_key_exists( 'orderby', $error_data['params'] ) ) { - // Get the 'orderby' details - $orderby_details = $error_data['details']['orderby']; - - // Get the 'orderby' request - $orderby_request = $request->get_param( 'orderby' ); - - // Extended 'orderby' values - $orderby_extended = array( - 'status', - 'customer_id', - 'payment_method', - 'total', - ); - - // Check if 'orderby' has 'rest_not_in_enum', but is in the extended 'orderby' values - if ( 'rest_not_in_enum' === $orderby_details['code'] && \in_array( $orderby_request, $orderby_extended, true ) ) { - unset( $error_data['params']['orderby'], $error_data['details']['orderby'] ); - } - } - - // Check if $error_data['params'] is empty - if ( empty( $error_data['params'] ) ) { - return null; - } - // Remove old error data and add new error data - $error_message = 'Invalid parameter(s): ' . implode( ', ', array_keys( $error_data['params'] ) ) . '.'; - - $response->remove( 'rest_invalid_param' ); - $response->add( 'rest_invalid_param', $error_message, $error_data ); - } - } - - return $response; - } - - - - public function incoming_shop_order(): void { - $raw_data = $this->request->get_json_params(); - - /* - * WC REST validation enforces email address for orders - * this hack allows guest orders to bypass this validation - */ - if ( isset( $raw_data['customer_id'] ) && 0 == $raw_data['customer_id'] ) { - add_filter( - 'is_email', - function ( $result, $email ) { - if ( ! $email ) { - return true; - } - - return $result; - }, - 10, - 2 - ); - } - } - - /** - * Filters the value of the woocommerce_tax_based_on option. - * - * @param mixed $value Value of the option. - * @param string $option Option name. - */ - public function tax_based_on( $value, $option ) { - $tax_based_on = 'base'; // default value is base - - // try to get POS tax settings from order meta - $raw_data = $this->request->get_json_params(); - if ( isset( $raw_data['meta_data'] ) ) { - foreach ( $raw_data['meta_data'] as $meta ) { - if ( '_woocommerce_pos_tax_based_on' == $meta['key'] ) { - $tax_based_on = $meta['value']; - } - } - } - - return $tax_based_on; - } - - /** - * Filter the query arguments for a request. - * - * @param array $args Key value array of query var to query value. - * @param WP_REST_Request $request The request used. - * - * @return array $args Key value array of query var to query value. - */ - public function order_query( $args, WP_REST_Request $request ) { - return $args; - } - - /** - * @param $order - * @param $request - * @param $creating - */ - public function pre_insert_shop_order_object( $order, $request, $creating ) { - return $order; - } - - /** - * @param WP_REST_Response $response The response object. - * @param WC_Order $order Object data. - * @param WP_REST_Request $request Request object. - * - * @return WP_REST_Response - */ - public function order_response( WP_REST_Response $response, WC_Order $order, WP_REST_Request $request ): WP_REST_Response { - $data = $response->get_data(); - - // Add UUID to order - $this->maybe_add_post_uuid( $order ); - - // Add payment link to the order. - $pos_payment_url = add_query_arg( - array( - 'pay_for_order' => true, - 'key' => $order->get_order_key(), - ), - get_home_url( null, '/wcpos-checkout/order-pay/' . $order->get_id() ) - ); - - $response->add_link( 'payment', $pos_payment_url, array( 'foo' => 'bar' ) ); - - // 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->get_id() ); - - return $response; - } - - /** - * @param $items WC_Order_Item[] - * @param $order WC_Order - * @param $item_type string[] ['line_item' | 'fee' | 'shipping' | 'tax' | 'coupon'] - * - * @return WC_Order_Item[] - */ - public function order_get_items( array $items, WC_Order $order, array $item_type ): array { - foreach ( $items as $item ) { - $this->maybe_add_order_item_uuid( $item ); - } - - return $items; - } - - /** - * @param $item - * @param $posted - * - * @throws Exception - */ - public function rest_set_order_item( $item, $posted ): void { - /* - * fixes two problems wth WC REST API - * - variation meta_data with 'any' are not being saved - * - default variation meta_data is always added (not unique) - */ - if ( isset( $posted['variation_id'] ) && 0 !== $posted['variation_id'] ) { - $variation = wc_get_product( (int) $posted['variation_id'] ); - $valid_keys = array(); - - if ( \is_callable( array( $variation, 'get_variation_attributes' ) ) ) { - foreach ( $variation->get_variation_attributes() as $attribute_name => $attribute ) { - $valid_keys[] = str_replace( 'attribute_', '', $attribute_name ); - } - - if ( isset( $posted['meta_data'] ) && \is_array( $posted['meta_data'] ) ) { - foreach ( $posted['meta_data'] as $meta ) { - // fix initial item creation - if ( isset( $meta['attr_id'] ) ) { - if ( 0 == $meta['attr_id'] ) { - // not a taxonomy - if ( \in_array( strtolower( $meta['display_key'] ), $valid_keys, true ) ) { - $item->add_meta_data( strtolower( $meta['display_key'] ), $meta['display_value'], true ); - } - } else { - $taxonomy = wc_attribute_taxonomy_name_by_id( $meta['attr_id'] ); - - $terms = get_the_terms( (int) $posted['product_id'], $taxonomy ); - if ( ! empty( $terms ) ) { - foreach ( $terms as $term ) { - if ( $term->name === $meta['display_value'] ) { - $item->add_meta_data( $taxonomy, $term->slug, true ); - } - } - } - } - } - // fix subsequent overwrites - if ( wc_attribute_taxonomy_id_by_name( $meta['key'] ) || \in_array( $meta['key'], $valid_keys, true ) ) { - $item->add_meta_data( $meta['key'], $meta['value'], true ); - } - } - } - } - } - } - - /** - * @param $value - * @param WC_Product_Variation $variation - * - * @return void - */ - public function product_variation_get_attributes( $value, WC_Product_Variation $variation ) { - /* - * - could fix 'any' options here using raw posted data - * - may be useful for product title generation - */ - - return $value; - } - - /** - * Add custom 'created_via' prop for POS orders, used in WC Admin display. - * - * @param WC_Order $order The object being saved. - * - * @throws WC_Data_Exception - */ - public function before_order_object_save( WC_Order $order ): void { - if ( 0 === $order->get_id() ) { - $order->set_created_via( PLUGIN_NAME ); - } - - /** - * Add cashier user id to order meta - * Note: There should only be one cashier per order, currently this will overwrite previous cashier id. - */ - $user_id = get_current_user_id(); - $cashier_id = $order->get_meta( '_pos_user' ); - - if ( ! $cashier_id ) { - $order->update_meta_data( '_pos_user', $user_id ); - } - } - - /** - * Filters all query clauses at once, for convenience. - * - * Covers the WHERE, GROUP BY, JOIN, ORDER BY, DISTINCT, - * fields (SELECT), and LIMIT clauses. - * - * @param string[] $clauses { - * Associative array of the clauses for the query. - * - * @var string The WHERE clause of the query. - * @var string The GROUP BY clause of the query. - * @var string The JOIN clause of the query. - * @var string The ORDER BY clause of the query. - * @var string The DISTINCT clause of the query. - * @var string The SELECT clause of the query. - * @var string The LIMIT clause of the query. - * } - * - * @param WP_Query $wp_query The WP_Query instance (passed by reference). - */ - public function orderby_additions( array $clauses, WP_Query $wp_query ): array { - global $wpdb; - - $order = $wp_query->query_vars['order'] ?? 'DESC'; - - if ( isset( $wp_query->query_vars['orderby'] ) ) { - - // add option to order by status - if ( 'status' === $wp_query->query_vars['orderby'] ) { - $clauses['join'] .= " LEFT JOIN {$wpdb->prefix}posts AS order_posts ON {$wpdb->prefix}posts.ID = order_posts.ID "; - $clauses['orderby'] = ' order_posts.post_status ' . $order; - } - - // add option to order by customer_id - if ( 'customer_id' === $wp_query->query_vars['orderby'] ) { - $clauses['join'] .= " LEFT JOIN {$wpdb->prefix}postmeta AS customer_meta ON {$wpdb->prefix}posts.ID = customer_meta.post_id "; - $clauses['where'] .= " AND customer_meta.meta_key = '_customer_user' "; - $clauses['orderby'] = ' customer_meta.meta_value ' . $order; - } - - // add option to order by payment_method - if ( 'payment_method' === $wp_query->query_vars['orderby'] ) { - $clauses['join'] .= " LEFT JOIN {$wpdb->prefix}postmeta AS payment_method_meta ON {$wpdb->prefix}posts.ID = payment_method_meta.post_id "; - $clauses['where'] .= " AND payment_method_meta.meta_key = '_payment_method' "; - $clauses['orderby'] = ' payment_method_meta.meta_value ' . $order; - } - - // add option to order by total - if ( 'total' === $wp_query->query_vars['orderby'] ) { - $clauses['join'] .= " LEFT JOIN {$wpdb->prefix}postmeta AS total_meta ON {$wpdb->prefix}posts.ID = total_meta.post_id "; - $clauses['where'] .= " AND total_meta.meta_key = '_order_total' "; - $clauses['orderby'] = ' total_meta.meta_value+0 ' . $order; - } - } - - return $clauses; - } - - /** - * Returns array of all order ids. - * - * @param array $fields - * - * @return array|WP_Error - */ - public function get_all_posts( array $fields = array() ): array { - $args = array( - 'limit' => -1, - 'return' => 'ids', - 'status' => array_keys( wc_get_order_statuses() ), // Get valid order statuses - ); - - $order_query = new WC_Order_Query( $args ); - - 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 new WP_Error( - 'woocommerce_pos_rest_cannot_fetch', - 'Error fetching order IDs.', - array( 'status' => 500 ) - ); - } - } -} diff --git a/includes/i18n.php b/includes/i18n.php index 9bd837a..07d6b92 100644 --- a/includes/i18n.php +++ b/includes/i18n.php @@ -14,4 +14,11 @@ namespace WCPOS\WooCommercePOS; class i18n { + + /** + * Load the plugin text domain for translation. + */ + public function construct() { + load_plugin_textdomain( 'woocommerce-pos', false, PLUGIN_PATH . '/languages/' ); + } } diff --git a/templates/legacy-receipt.php b/templates/legacy-receipt.php deleted file mode 100644 index 3136b01..0000000 --- a/templates/legacy-receipt.php +++ /dev/null @@ -1,308 +0,0 @@ - - - - <?php _e( 'Receipt', 'woocommerce-pos' ); ?> - - - - -
-

-
-
-
- {{#formatAddress billing_address title=""}} -
-
- {{formatAddress shipping_address title=""}} -
-
- - - - - - - - - - {{#if cashier}} - - - - - {{/if}} - - - - - {{#if billing_address.email}} - - - - - {{/if}} - {{#if billing_address.phone}} - - - - - {{/if}} -
{{order_number}}
{{formatDate completed_at format="MMMM Do YYYY, h:mm:ss a"}}
{{cashier.first_name}} {{cashier.last_name}}
{{payment_details.method_title}}
- - {{billing_address.email}}
- - {{billing_address.phone}}
- - - - - - - - - - {{#each line_items}} - - - - - - {{/each}} - - - - - - - {{#if has_discount}} - - - - - {{/if}} - {{#each shipping_lines}} - - - - - {{/each}} - {{#each fee_lines}} - - - - - {{/each}} - {{#if has_tax}} - {{#if itemized}} - {{#each tax_lines}} - {{#if has_tax}} - - - - - {{/if}} - {{/each}} - {{else}} - - - - - {{/if}} - {{/if}} - - {{#if has_order_discount}} - - - - - {{/if}} - - - - - - {{#if payment_details.method_pos_cash}} - - - - - - - - - {{/if}} - -
- - - -
- {{name}} - {{#with meta}} -
- {{#each meta}} -
{{#if label}}{{label}}{{else}}{{key}}{{/if}}:
-
{{value}}
- {{/each}} -
- {{/with}} -
{{number quantity precision="auto"}} - {{#if on_sale}} - {{{money subtotal}}} - {{{money total}}} - {{else}} - {{{money total}}} - {{/if}} -
:{{{money subtotal}}}
:{{{money cart_discount negative=true}}}
{{method_title}}:{{{money total}}}
{{name}}:{{{money total}}}
- {{#if ../../incl_tax}}( - ){{/if}} - {{title}}: - {{{money total}}}
- {{#if incl_tax}}( - ){{/if}} - countries->tax_or_vat() ); ?> - {{{money total_tax}}}
:{{{money order_discount negative=true}}}
- - : - {{{money total}}}
:{{{money payment_details.method_pos_cash.tendered}}}
:{{{money payment_details.method_pos_cash.change}}}
-
{{note}}
- - diff --git a/templates/payment.php b/templates/payment.php index e6366c3..bb63178 100644 --- a/templates/payment.php +++ b/templates/payment.php @@ -59,48 +59,48 @@ background-color: #ffffff !important; } - .woocommerce-pos-troubleshooting { - border-left: 4px solid #007cba; /* For the blue vertical line */ - padding: 5px 10px; /* Padding around the text */ - background: #fff; /* White background */ - box-shadow: 0 1px 1px rgba(0,0,0,.04); /* Subtle shadow effect */ - } + .woocommerce-pos-troubleshooting { + border-left: 4px solid #007cba; /* For the blue vertical line */ + padding: 5px 10px; /* Padding around the text */ + background: #fff; /* White background */ + box-shadow: 0 1px 1px rgba(0,0,0,.04); /* Subtle shadow effect */ + } .woocommerce-pos-troubleshooting p.link { margin: 0; } - .woocommerce-pos-troubleshooting h3 { - margin: 0.5em 0; - padding: 0; - font-size: 1em; - font-weight: 600; - } - - .woocommerce-pos-troubleshooting div { - flex: 1; - margin-right: 20px; - } - - .woocommerce-pos-troubleshooting input[type="checkbox"] { - margin-right: 5px; - } - - .woocommerce-pos-troubleshooting button { - margin-top: 20px; - background: #007cba; - border: none; - color: #fff; - padding: 10px 15px; - border-radius: 3px; - cursor: pointer; - } - - .woocommerce-pos-troubleshooting button:hover { - background: #005a87; - } - - .woocommerce-error { + .woocommerce-pos-troubleshooting h3 { + margin: 0.5em 0; + padding: 0; + font-size: 1em; + font-weight: 600; + } + + .woocommerce-pos-troubleshooting div { + flex: 1; + margin-right: 20px; + } + + .woocommerce-pos-troubleshooting input[type="checkbox"] { + margin-right: 5px; + } + + .woocommerce-pos-troubleshooting button { + margin-top: 20px; + background: #007cba; + border: none; + color: #fff; + padding: 10px 15px; + border-radius: 3px; + cursor: pointer; + } + + .woocommerce-pos-troubleshooting button:hover { + background: #005a87; + } + + .woocommerce-error { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; @@ -264,7 +264,8 @@ > -
+queue; $scriptHandles = $wp_scripts->queue; @@ -279,53 +280,55 @@ woocommerce_pos_get_settings( 'checkout', 'dequeue_script_handles' ) ); -$mergedStyleHandles = array_unique(array_merge($styleHandles, $style_exclude_list)); -$mergedScriptHandles = array_unique(array_merge($scriptHandles, $script_exclude_list)); +$mergedStyleHandles = array_unique( array_merge( $styleHandles, $style_exclude_list ) ); +$mergedScriptHandles = array_unique( array_merge( $scriptHandles, $script_exclude_list ) ); ?>
- + : display_name ); ?>
- + : ID ? esc_html__( 'Guest', 'woocommerce-pos' ) : esc_html( $woocommerce_pos_customer->display_name ); ?>
@@ -383,19 +386,28 @@ get_items( 'coupon' ); -if ( $coupons ) { - echo '

' . __('Applied coupons', 'woocommerce') . '

'; - echo ''; -} -?> + if ( $coupons ) { + echo '

' . __( 'Applied coupons', 'woocommerce' ) . '

'; + echo ''; + } + ?>
- $order, 'available_gateways' => $available_gateways, 'order_button_text' => $order_button_text ) ); ?> + $order, + 'available_gateways' => $available_gateways, + 'order_button_text' => $order_button_text, + ) + ); + ?> diff --git a/templates/receipt.php b/templates/receipt.php index 874b889..881041d 100644 --- a/templates/receipt.php +++ b/templates/receipt.php @@ -178,7 +178,7 @@ if ( $pos_user ) { $user = get_user_by( 'id', $pos_user ); $user_name = $user->display_name; - echo '
  • ' . esc_html__( 'Cashier:', 'woocommerce-pos' ) . ' ' . esc_html( $user_name ) . '
  • '; + echo '
  • ' . esc_html__( 'Cashier', 'woocommerce-pos' ) . ': ' . esc_html( $user_name ) . '
  • '; } ?> get_payment_method_title() ) { ?>