PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.9.15
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.9.15
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
woocommerce-pos / includes / Services / Settings.php

Settings.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.9.15, at includes/Services/Settings.php

1,133 lines 32.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Settings.
4 *
5 * @package WCPOS\WooCommercePOS
6 */
7
8 namespace WCPOS\WooCommercePOS\Services;
9
10 use WC_Payment_Gateways;
11 use WP_Error;
12 use const WCPOS\WooCommercePOS\VERSION;
13
14 /**
15 * Settings Service class.
16 */
17 class Settings {
18 /**
19 * Prefix for the $wpdb->options table.
20 *
21 * @var string
22 */
23 protected static $db_prefix = 'woocommerce_pos_settings_';
24
25 /**
26 * Default settings for all sections.
27 *
28 * @var array
29 */
30 protected static $default_settings = array(
31 'general' => array(
32 'pos_only_products' => false,
33 'decimal_qty' => false,
34 'force_ssl' => true,
35 'default_customer' => 0,
36 'default_customer_is_cashier' => false,
37 'barcode_field' => '_sku',
38 'generate_username' => true,
39 'restore_stock_on_delete' => true,
40 'storefront_receipt_enabled' => false,
41 'storefront_receipt_template' => '',
42 'tracking_consent' => 'undecided',
43 'store_name' => '',
44 'store_phone' => '',
45 'store_email' => '',
46 'policies_and_conditions' => '',
47 'store_tax_ids' => array(),
48 ),
49 'tax_ids' => array(
50 // Per-type meta-key write overrides. Empty by default: the composed
51 // write_map (defaults + plugin detection + scan) is used.
52 'write_map' => array(),
53 ),
54 'checkout' => array(
55 'receipt_default_mode' => 'fiscal',
56 'admin_emails' => array(
57 'enabled' => true,
58 'new_order' => true,
59 'cancelled_order' => true,
60 'failed_order' => true,
61 ),
62 'customer_emails' => array(
63 'enabled' => true,
64 'customer_on_hold_order' => true,
65 'customer_processing_order' => true,
66 'customer_completed_order' => true,
67 'customer_refunded_order' => true,
68 'customer_failed_order' => true,
69 ),
70 'cashier_emails' => array(
71 'enabled' => false,
72 'new_order' => true,
73 ),
74 // this is used in the POS, not in WP Admin (at the moment).
75 'dequeue_script_handles' => array(
76 'admin-bar',
77 'wc-add-to-cart',
78 'wc-stripe-upe-classic',
79 ),
80 'dequeue_style_handles' => array(
81 'admin-bar',
82 'woocommerce-general',
83 'woocommerce-inline',
84 'woocommerce-layout',
85 'woocommerce-smallscreen',
86 'woocommerce-blocktheme',
87 'wp-block-library',
88 ),
89 'disable_wp_head' => false,
90 'disable_wp_footer' => false,
91 ),
92 'payment_gateways' => array(
93 'default_gateway' => 'pos_cash',
94 'gateways' => array(
95 'pos_cash' => array(
96 'order' => 0,
97 'enabled' => true,
98 'order_status' => 'wc-completed',
99 ),
100 'pos_card' => array(
101 'order' => 1,
102 'enabled' => true,
103 'order_status' => 'wc-completed',
104 ),
105 ),
106 ),
107 'tools' => array(
108 'use_jwt_as_param' => false,
109 ),
110 'visibility' => array(
111 'products' => array(
112 'default' => array(
113 'pos_only' => array(
114 'ids' => array(),
115 ),
116 'online_only' => array(
117 'ids' => array(),
118 ),
119 ),
120 ),
121 'variations' => array(
122 'default' => array(
123 'pos_only' => array(
124 'ids' => array(),
125 ),
126 'online_only' => array(
127 'ids' => array(),
128 ),
129 ),
130 ),
131 ),
132 );
133 /**
134 * The single instance of the class.
135 *
136 * @var null|Settings
137 */
138 private static $instance = null;
139
140 /**
141 * Get capabilities grouped by type.
142 *
143 * WooCommerce 9.9 replaced promote_users with create_customers for
144 * customer creation via the REST API. We show the correct capability
145 * on the Access settings page based on the installed WC version.
146 *
147 * @return array
148 */
149 private static function get_caps(): array {
150 $customer_create_cap = version_compare( WC()->version, '9.9', '>=' )
151 ? 'create_customers'
152 : 'promote_users';
153
154 return array(
155 'wcpos' => array(
156 'access_woocommerce_pos',
157 'manage_woocommerce_pos',
158 ),
159 'wc' => array(
160 $customer_create_cap,
161 'read_private_products',
162 'edit_product',
163 'edit_others_products',
164 'edit_published_products',
165 'read_private_shop_orders',
166 'publish_shop_orders',
167 'edit_shop_orders',
168 'edit_others_shop_orders',
169 'edit_users',
170 'list_users',
171 'manage_product_terms',
172 'read_private_shop_coupons',
173 ),
174 'wp' => array(
175 'read',
176 ),
177 );
178 }
179
180 /**
181 * Constructor is private to prevent direct instantiation.
182 * Use woocommerce_pos_get_settings() instead.
183 * Or Settings::instance() if you must.
184 */
185 private function __construct() {
186 }
187
188 /**
189 * Gets the singleton instance.
190 *
191 * @return Settings
192 */
193 public static function instance(): self {
194 if ( null === self::$instance ) {
195 self::$instance = new self();
196 }
197
198 return self::$instance;
199 }
200
201 /**
202 * Get settings for a specific section.
203 *
204 * @param string $id The settings section ID.
205 * @param null|mixed $key The specific setting key.
206 *
207 * @return null|array|mixed|WP_Error
208 */
209 public function get_settings( string $id, $key = null ) {
210 $method_name = 'get_' . $id . '_settings';
211
212 if ( method_exists( $this, $method_name ) ) {
213 $settings = $this->$method_name();
214
215 // If key is not provided, return the entire settings.
216 if ( ! \is_string( $key ) ) {
217 return $settings;
218 }
219
220 if ( ! isset( $settings[ $key ] ) ) {
221 return new WP_Error(
222 'woocommerce_pos_settings_error',
223 // translators: 1. %s: Settings group id, 2. %s: Settings key.
224 \sprintf( __( 'Settings with id %1$s and key %2$s not found', 'woocommerce-pos' ), $id, $key ),
225 array( 'status' => 400 )
226 );
227 }
228
229 return $settings[ $key ];
230 }
231
232 return new WP_Error(
233 'woocommerce_pos_settings_error',
234 // translators: %s: Settings group id, ie: 'general' or 'checkout'.
235 \sprintf( __( 'Settings with id %s not found', 'woocommerce-pos' ), $id ),
236 array( 'status' => 400 )
237 );
238 }
239
240 /**
241 * Resolve the URL scheme for POS permalinks.
242 *
243 * Central policy for when POS URLs force https: the force_ssl general
244 * setting (default true) so links work when the site home URL is http
245 * but the POS is served over https, eg: behind an SSL-terminating proxy.
246 *
247 * @return null|string 'https' when force_ssl is enabled, null for the home scheme.
248 */
249 public function url_scheme(): ?string {
250 return wp_validate_boolean( $this->get_settings( 'general', 'force_ssl' ) ) ? 'https' : null;
251 }
252
253 /**
254 * Saves settings for a specific section.
255 *
256 * @param string $id The ID of the settings section being saved.
257 * @param array $settings The settings array to be saved.
258 *
259 * @return array|WP_Error Returns the updated settings array on success or WP_Error on failure.
260 */
261 public function save_settings( string $id, array $settings ) {
262 $sanitize_method = 'sanitize_' . $id . '_settings';
263 if ( method_exists( $this, $sanitize_method ) ) {
264 $settings = $this->$sanitize_method( $settings );
265 }
266
267 $settings = array_merge(
268 $settings,
269 array( 'date_modified_gmt' => current_time( 'mysql', true ) )
270 );
271
272 /**
273 * Filters the settings before they are saved.
274 *
275 * Allows modification of the settings array for a specific section before it is saved to the database.
276 *
277 * @since 1.4.12
278 *
279 * @param array $settings The settings array about to be saved.
280 * @param string $id The ID of the settings section being saved.
281 */
282 $settings = apply_filters( "woocommerce_pos_pre_save_{$id}_settings", $settings, $id );
283
284 $option_name = static::$db_prefix . $id;
285 $previous_value = get_option( $option_name, null );
286 $success = update_option( $option_name, $settings, false );
287
288 if ( ! $success ) {
289 // update_option() returns false both when the value is unchanged (no DB write) and on
290 // actual failure. Use the value read *before* the write attempt to avoid a post-write
291 // race: a concurrent request could change the option between our write and a re-read.
292 $is_noop = null !== $previous_value
293 && maybe_serialize( $previous_value ) === maybe_serialize( $settings );
294
295 if ( ! $is_noop ) {
296 return new WP_Error(
297 'woocommerce_pos_settings_error',
298 // translators: %s: Settings group id, ie: 'general' or 'checkout'.
299 \sprintf( __( 'Can not save settings with id %s', 'woocommerce-pos' ), $id ),
300 array( 'status' => 400 )
301 );
302 }
303 }
304
305 $saved_settings = $this->get_settings( $id );
306
307 if ( $success ) {
308 /*
309 * Fires after settings for a specific section are successfully saved.
310 *
311 * Provides a way to execute additional logic after a specific settings section is updated.
312 *
313 * @since 1.4.12
314 *
315 * @param array $saved_settings The settings array that was just saved.
316 * @param string $id The ID of the settings section that was saved.
317 */
318 do_action( "woocommerce_pos_saved_{$id}_settings", $saved_settings, $id );
319 }
320
321 return $saved_settings;
322 }
323
324 /**
325 * Get general settings.
326 *
327 * @return array
328 */
329 public function get_general_settings(): array {
330 $default_settings = self::$default_settings['general'];
331 $settings = get_option( self::$db_prefix . 'general', array() );
332
333 // Migrate tracking_consent from the legacy `tools` option if it was set there
334 // before being moved to `general`. Only applies when the general option has no
335 // value yet, so an explicit general-level choice always wins.
336 if ( ! \array_key_exists( 'tracking_consent', $settings ) ) {
337 $legacy_tools = get_option( self::$db_prefix . 'tools', array() );
338 if ( \is_array( $legacy_tools ) && \array_key_exists( 'tracking_consent', $legacy_tools ) ) {
339 $settings['tracking_consent'] = $legacy_tools['tracking_consent'];
340 }
341 }
342
343 // if the key does not exist in db settings, use the default settings.
344 foreach ( $default_settings as $key => $value ) {
345 if ( ! \array_key_exists( $key, $settings ) ) {
346 $settings[ $key ] = $value;
347 }
348 }
349 $settings['store_tax_ids'] = self::sanitize_store_tax_ids( $settings['store_tax_ids'] );
350
351 // Expose resolved fallbacks so the React UI can render them as
352 // placeholders for store_name / store_phone / store_email /
353 // policies_and_conditions when the user has not entered a value.
354 $settings['store_defaults'] = Store_Defaults::fallbacks();
355
356 /*
357 * Filters the general settings.
358 *
359 * @since 1.0.0
360 *
361 * @param array $settings
362 *
363 * @return array $settings
364 *
365 * @hook woocommerce_pos_general_settings
366 */
367 return apply_filters( 'woocommerce_pos_general_settings', $settings );
368 }
369
370 /**
371 * Sanitize general settings before persisting.
372 *
373 * @param array $settings General settings.
374 * @return array
375 */
376 protected function sanitize_general_settings( array $settings ): array {
377 if ( \array_key_exists( 'store_tax_ids', $settings ) ) {
378 $settings['store_tax_ids'] = self::sanitize_store_tax_ids( $settings['store_tax_ids'] );
379 }
380
381 foreach ( array( 'store_name', 'store_phone' ) as $key ) {
382 if ( \array_key_exists( $key, $settings ) ) {
383 $settings[ $key ] = \is_string( $settings[ $key ] )
384 ? sanitize_text_field( $settings[ $key ] )
385 : '';
386 }
387 }
388
389 if ( \array_key_exists( 'store_email', $settings ) ) {
390 $email = \is_string( $settings['store_email'] ) ? trim( $settings['store_email'] ) : '';
391 $settings['store_email'] = ( '' !== $email && is_email( $email ) ) ? sanitize_email( $email ) : '';
392 }
393
394 if ( \array_key_exists( 'policies_and_conditions', $settings ) ) {
395 $settings['policies_and_conditions'] = \is_string( $settings['policies_and_conditions'] )
396 ? sanitize_textarea_field( $settings['policies_and_conditions'] )
397 : '';
398 }
399
400 if ( \array_key_exists( 'storefront_receipt_enabled', $settings ) ) {
401 $settings['storefront_receipt_enabled'] = wp_validate_boolean( $settings['storefront_receipt_enabled'] );
402 }
403
404 // Template IDs are numeric (database) or slug strings (virtual/gallery); an empty
405 // string means "use the active receipt template". Store as a sanitized string and
406 // let Storefront_Receipts pass it through the already-validated ?template= path.
407 if ( \array_key_exists( 'storefront_receipt_template', $settings ) ) {
408 $settings['storefront_receipt_template'] = \is_scalar( $settings['storefront_receipt_template'] )
409 ? sanitize_text_field( (string) $settings['storefront_receipt_template'] )
410 : '';
411 }
412
413 // store_defaults is a read-only computed field for the UI; never persist it.
414 unset( $settings['store_defaults'] );
415
416 return $settings;
417 }
418
419 /**
420 * Sanitize the additional free-store tax IDs entered in General settings.
421 *
422 * Drops malformed rows and keeps optional country/label fields only when
423 * non-empty. Values are preserved verbatim apart from normal text-field
424 * sanitization and surrounding whitespace.
425 *
426 * @param mixed $tax_ids Raw tax IDs.
427 * @return array<int,array<string,string>>
428 */
429 public static function sanitize_store_tax_ids( $tax_ids ): array {
430 if ( ! \is_array( $tax_ids ) ) {
431 return array();
432 }
433
434 $sanitized = array();
435 foreach ( $tax_ids as $tax_id ) {
436 if ( ! \is_array( $tax_id ) ) {
437 continue;
438 }
439
440 $type = isset( $tax_id['type'] ) && \is_string( $tax_id['type'] )
441 ? sanitize_key( $tax_id['type'] )
442 : '';
443 $value = isset( $tax_id['value'] ) && \is_string( $tax_id['value'] )
444 ? trim( sanitize_text_field( $tax_id['value'] ) )
445 : '';
446
447 if ( '' === $type || '' === $value ) {
448 continue;
449 }
450
451 $entry = array(
452 'type' => $type,
453 'value' => $value,
454 );
455
456 $country = isset( $tax_id['country'] ) && \is_string( $tax_id['country'] )
457 ? strtoupper( trim( sanitize_text_field( $tax_id['country'] ) ) )
458 : '';
459 if ( '' !== $country ) {
460 $entry['country'] = $country;
461 }
462
463 $label = isset( $tax_id['label'] ) && \is_string( $tax_id['label'] )
464 ? trim( sanitize_text_field( $tax_id['label'] ) )
465 : '';
466 if ( '' !== $label ) {
467 $entry['label'] = $label;
468 }
469
470 $sanitized[] = $entry;
471 }
472
473 return $sanitized;
474 }
475
476 /**
477 * Get tax IDs settings.
478 *
479 * Defaults are merged for any missing keys so the SPA always receives the
480 * full subtree shape.
481 *
482 * @return array
483 */
484 public function get_tax_ids_settings(): array {
485 $default_settings = self::$default_settings['tax_ids'];
486 $settings = get_option( self::$db_prefix . 'tax_ids', array() );
487
488 if ( ! \is_array( $settings ) ) {
489 $settings = array();
490 }
491
492 if ( ! \array_key_exists( 'write_map', $settings ) ) {
493 $legacy_general = get_option( self::$db_prefix . 'general', array() );
494 $legacy_tax_ids = array();
495
496 if (
497 \is_array( $legacy_general )
498 && isset( $legacy_general['tax_ids'] )
499 && \is_array( $legacy_general['tax_ids'] )
500 ) {
501 $legacy_tax_ids = $legacy_general['tax_ids'];
502 }
503
504 if ( isset( $legacy_tax_ids['write_map'] ) && \is_array( $legacy_tax_ids['write_map'] ) ) {
505 $settings['write_map'] = $legacy_tax_ids['write_map'];
506 }
507 }
508
509 foreach ( $default_settings as $key => $value ) {
510 if ( ! \array_key_exists( $key, $settings ) ) {
511 $settings[ $key ] = $value;
512 }
513 }
514
515 /*
516 * Filters the tax IDs settings.
517 *
518 * @param {array} $settings
519 * @returns {array} $settings
520 * @hook woocommerce_pos_tax_ids_settings
521 */
522 return apply_filters( 'woocommerce_pos_tax_ids_settings', $settings );
523 }
524
525 /**
526 * Get checkout settings.
527 *
528 * @return array
529 */
530 public function get_checkout_settings(): array {
531 $default_settings = self::$default_settings['checkout'];
532 $settings = get_option( self::$db_prefix . 'checkout', array() );
533
534 // if the key does not exist in db settings, use the default settings.
535 foreach ( $default_settings as $key => $value ) {
536 if ( ! \array_key_exists( $key, $settings ) ) {
537 $settings[ $key ] = $value;
538 }
539 }
540
541 // Migrate legacy boolean email settings to array format.
542 foreach ( array( 'admin_emails', 'customer_emails' ) as $key ) {
543 if ( isset( $settings[ $key ] ) && \is_bool( $settings[ $key ] ) ) {
544 $defaults = $default_settings[ $key ];
545 $defaults['enabled'] = $settings[ $key ];
546 $settings[ $key ] = $defaults;
547 }
548 }
549
550 /*
551 * Filters the checkout settings.
552 *
553 * @param {array} $settings
554 * @returns {array} $settings
555 * @since 1.0.0
556 * @hook woocommerce_pos_checkout_settings
557 */
558 return apply_filters( 'woocommerce_pos_checkout_settings', $settings );
559 }
560
561 /**
562 * Get access settings with role capabilities.
563 *
564 * @return array
565 */
566 public function get_access_settings(): array {
567 global $wp_roles;
568 $role_caps = array();
569 $caps = self::get_caps();
570
571 $roles = $wp_roles->roles;
572 if ( $roles ) {
573 foreach ( $roles as $slug => $role ) {
574 $role_caps[ $slug ] = array(
575 'name' => $role['name'],
576 'capabilities' => array(
577 'wcpos' => array_intersect_key(
578 array_merge( array_fill_keys( $caps['wcpos'], false ), $role['capabilities'] ),
579 array_flip( $caps['wcpos'] )
580 ),
581 'wc' => array_intersect_key(
582 array_merge( array_fill_keys( $caps['wc'], false ), $role['capabilities'] ),
583 array_flip( $caps['wc'] )
584 ),
585 'wp' => array_intersect_key(
586 array_merge( array_fill_keys( $caps['wp'], false ), $role['capabilities'] ),
587 array_flip( $caps['wp'] )
588 ),
589 ),
590 );
591 }
592 }
593
594 /*
595 * Filters the access settings.
596 *
597 * @param {array} $settings
598 * @returns {array} $settings
599 * @since 1.0.0
600 * @hook woocommerce_pos_access_settings
601 */
602 return apply_filters( 'woocommerce_pos_access_settings', $role_caps );
603 }
604
605 /**
606 * Get tools settings.
607 *
608 * @return array
609 */
610 public function get_tools_settings(): array {
611 $default_settings = self::$default_settings['tools'];
612 $settings = get_option( self::$db_prefix . 'tools', array() );
613
614 // if the key does not exist in db settings, use the default settings.
615 foreach ( $default_settings as $key => $value ) {
616 if ( ! \array_key_exists( $key, $settings ) ) {
617 $settings[ $key ] = $value;
618 }
619 }
620
621 /*
622 * Filters the tools settings.
623 *
624 * @param {array} $settings
625 * @returns {array} $settings
626 * @since 1.3.6
627 * @hook woocommerce_pos_general_settings
628 */
629 return apply_filters( 'woocommerce_pos_tools_settings', $settings );
630 }
631
632 /**
633 * Get license settings.
634 *
635 * @return array
636 */
637 public function get_license_settings() {
638 /*
639 * Filters the license settings.
640 *
641 * @param {array} $settings
642 * @returns {array} $settings
643 * @since 1.0.0
644 * @hook woocommerce_pos_license_settings
645 */
646 return apply_filters( 'woocommerce_pos_license_settings', array() );
647 }
648
649 /**
650 * Get available barcode fields.
651 *
652 * @return array
653 */
654 public function get_barcodes(): array {
655 global $wpdb;
656
657 // maybe add custom barcode field.
658 $custom_field = $this->get_settings( 'general', 'barcode_field' );
659
660 // Prepare the basic query.
661 $result = $wpdb->get_col(
662 "
663 SELECT DISTINCT(pm.meta_key)
664 FROM $wpdb->postmeta AS pm
665 JOIN $wpdb->posts AS p
666 ON p.ID = pm.post_id
667 WHERE p.post_type IN ('product', 'product_variation')
668 ORDER BY pm.meta_key
669 "
670 );
671
672 if ( ! empty( $custom_field ) ) {
673 $result[] = $custom_field;
674 }
675
676 sort( $result );
677
678 return array_unique( $result );
679 }
680
681 /**
682 * Get available order statuses.
683 *
684 * @return array
685 */
686 public function get_order_statuses(): array {
687 $order_statuses = wc_get_order_statuses();
688
689 return array_map( 'wc_get_order_status_name', $order_statuses );
690 }
691
692 /**
693 * Get payment gateways settings.
694 *
695 * @return array
696 */
697 public function get_payment_gateways_settings() {
698 // Note: I need to re-init the gateways here to pass the tests, but it seems to work fine in the app.
699 WC_Payment_Gateways::instance()->init();
700 $installed_gateways = WC_Payment_Gateways::instance()->payment_gateways();
701 $raw_gw_option = get_option( self::$db_prefix . 'payment_gateways', array() );
702 $gateways_settings = array_replace_recursive(
703 self::$default_settings['payment_gateways'],
704 $raw_gw_option
705 );
706
707 // Migrate: if old global checkout order_status exists, apply to all gateways.
708 $checkout_settings = get_option( self::$db_prefix . 'checkout', array() );
709 if ( isset( $checkout_settings['order_status'] ) ) {
710 $global_status = $checkout_settings['order_status'];
711 if ( \is_string( $global_status ) && '' !== $global_status ) {
712 foreach ( $gateways_settings['gateways'] as $gw_id => &$gw_data ) {
713 // Check the raw DB value, not the merged value (which includes defaults).
714 if ( ! isset( $raw_gw_option['gateways'][ $gw_id ]['order_status'] ) ) {
715 $gw_data['order_status'] = $global_status;
716 }
717 }
718 unset( $gw_data );
719 }
720 // Remove the old global setting.
721 unset( $checkout_settings['order_status'] );
722 update_option( self::$db_prefix . 'checkout', $checkout_settings );
723 update_option( self::$db_prefix . 'payment_gateways', $gateways_settings );
724 }
725
726 // NOTE - gateways can be installed and uninstalled, so we need to assume the settings data is stale.
727 $response = array(
728 'default_gateway' => $gateways_settings['default_gateway'],
729 'gateways' => array(),
730 );
731
732 // Gateways that represent deferred/unverified payment default to on-hold.
733 $on_hold_gateways = array( 'bacs', 'cheque' );
734
735 // loop through installed gateways and merge with saved settings.
736 foreach ( $installed_gateways as $id => $gateway ) {
737 // sanity check for gateway class.
738 if ( ! is_a( $gateway, 'WC_Payment_Gateway' ) || 'pre_install_woocommerce_payments_promotion' === $id ) {
739 continue;
740 }
741
742 $default_status = in_array( $id, $on_hold_gateways, true ) ? 'wc-on-hold' : 'wc-completed';
743
744 $response['gateways'][ $id ] = array_replace_recursive(
745 array(
746 'id' => $gateway->id,
747 'title' => $gateway->title,
748 'description' => $gateway->description,
749 'enabled' => false,
750 'order' => 999,
751 'order_status' => $default_status,
752 ),
753 $gateways_settings['gateways'][ $id ] ?? array()
754 );
755 }
756
757 /*
758 * Filters the payment gateways settings.
759 *
760 * @param {array} $settings
761 * @returns {array} $settings
762 * @since 1.0.0
763 * @hook woocommerce_pos_payment_gateways_settings
764 */
765 return apply_filters( 'woocommerce_pos_payment_gateways_settings', $response );
766 }
767
768 /**
769 * POS Visibility settings.
770 */
771 public function get_visibility_settings() {
772 $default_settings = self::$default_settings['visibility'];
773 $settings = get_option( self::$db_prefix . 'visibility', array() );
774
775 // if the key does not exist in db settings, use the default settings.
776 foreach ( $default_settings as $key => $value ) {
777 if ( ! \array_key_exists( $key, $settings ) ) {
778 $settings[ $key ] = $value;
779 }
780 }
781
782 /*
783 * Filters the visibility settings.
784 *
785 * @param {array} $settings
786 * @returns {array} $settings
787 * @since 1.0.0
788 * @hook woocommerce_pos_visibility_settings
789 */
790 return apply_filters( 'woocommerce_pos_visibility_settings', $settings );
791 }
792
793 /**
794 * Update visibility settings.
795 *
796 * @param array $args The visibility settings to update.
797 *
798 * @return bool|WP_Error True on success, WP_Error on failure.
799 */
800 public function update_visibility_settings( array $args ) {
801 // Validate and normalize arguments.
802 if ( empty( $args['post_type'] ) || ! isset( $args['ids'] ) ) {
803 return new WP_Error(
804 'woocommerce_pos_settings_error',
805 /* translators: Error message shown when invalid arguments are provided. */
806 __( 'Invalid arguments provided', 'woocommerce-pos' ),
807 array( 'status' => 400 )
808 );
809 }
810
811 // Define valid visibility options.
812 $valid_options = array( 'pos_only', 'online_only', '' );
813
814 // Check if visibility is set and valid.
815 if ( ! isset( $args['visibility'] ) || ! \in_array( $args['visibility'], $valid_options, true ) ) {
816 return new WP_Error(
817 'woocommerce_pos_settings_error',
818 __( 'Invalid visibility option provided', 'woocommerce-pos' ),
819 array( 'status' => 400 )
820 );
821 }
822
823 $post_type = $args['post_type'];
824 $scope = $args['scope'] ?? 'default';
825 $visibility = $args['visibility'];
826 $ids = \is_array( $args['ids'] ) ? $args['ids'] : array( $args['ids'] );
827 $ids = array_filter( array_map( 'intval', $ids ) ); // Force to array of integers.
828
829 // Get the current visibility settings.
830 $current_settings = $this->get_visibility_settings();
831
832 // Define the opposite visibility type.
833 $opposite_visibility = ( 'pos_only' === $visibility ) ? 'online_only' : 'pos_only';
834
835 // Add or remove IDs based on the visibility type.
836 foreach ( $ids as $id ) {
837 if ( '' === $visibility ) {
838 // Remove from both pos_only and online_only.
839 $current_settings[ $post_type ][ $scope ]['pos_only']['ids'] = $this->remove_id_from_visibility(
840 $current_settings[ $post_type ][ $scope ]['pos_only']['ids'],
841 $id
842 );
843 $current_settings[ $post_type ][ $scope ]['online_only']['ids'] = $this->remove_id_from_visibility(
844 $current_settings[ $post_type ][ $scope ]['online_only']['ids'],
845 $id
846 );
847 } else {
848 // Add to the specified visibility type.
849 $current_settings[ $post_type ][ $scope ][ $visibility ]['ids'] = $this->add_id_to_visibility(
850 $current_settings[ $post_type ][ $scope ][ $visibility ]['ids'],
851 $id
852 );
853 // Remove from the opposite visibility type.
854 $current_settings[ $post_type ][ $scope ][ $opposite_visibility ]['ids'] = $this->remove_id_from_visibility(
855 $current_settings[ $post_type ][ $scope ][ $opposite_visibility ]['ids'],
856 $id
857 );
858 }
859 }
860
861 return $this->save_settings( 'visibility', $current_settings );
862 }
863
864 /**
865 * Get product visibility settings.
866 *
867 * @param string $scope The scope of the settings to get. 'default' or store ID.
868 *
869 * @return array $settings The product visibility settings, eg: { pos_only: { ids: [1, 2, 3] }, online_only: { ids: [4, 5, 6] }
870 */
871 public function get_product_visibility_settings( $scope = 'default' ) {
872 $settings = $this->get_visibility_settings();
873
874 /*
875 * Filters the product visibility settings.
876 *
877 * @param {array} $settings
878 * @returns {array} $settings
879 * @since 1.0.0
880 * @hook woocommerce_pos_product_visibility_settings
881 */
882 return apply_filters( 'woocommerce_pos_product_visibility_settings', $settings['products'][ $scope ], $scope );
883 }
884
885 /**
886 * Get product visibility settings.
887 *
888 * @param string $scope The scope of the settings to get. 'default' or store ID.
889 *
890 * @return array $settings The product visibility settings, eg: { ids: [1, 2, 3] }
891 */
892 public function get_pos_only_product_visibility_settings( $scope = 'default' ) {
893 $settings = $this->get_product_visibility_settings( $scope );
894
895 /*
896 * Filters the product visibility settings.
897 *
898 * @param {array} $settings
899 * @returns {array} $settings
900 * @since 1.0.0
901 * @hook woocommerce_pos_product_visibility_settings
902 */
903 return apply_filters( 'woocommerce_pos_pos_only_product_visibility_settings', $settings['pos_only'], $scope );
904 }
905
906 /**
907 * Get product visibility settings.
908 *
909 * @param string $scope The scope of the settings to get. 'default' or store ID.
910 *
911 * @return array $settings The product visibility settings, eg: { ids: [1, 2, 3] }
912 */
913 public function get_online_only_product_visibility_settings( $scope = 'default' ) {
914 $settings = $this->get_product_visibility_settings( $scope );
915
916 /*
917 * Filters the product visibility settings.
918 *
919 * @param {array} $settings
920 * @returns {array} $settings
921 * @since 1.0.0
922 * @hook woocommerce_pos_product_visibility_settings
923 */
924 return apply_filters( 'woocommerce_pos_online_only_product_visibility_settings', $settings['online_only'], $scope );
925 }
926
927 /**
928 * Get product visibility settings.
929 *
930 * @param string $scope The scope of the settings to get. 'default' or store ID.
931 *
932 * @return array $settings The product visibility settings, eg: { pos_only: { ids: [1, 2, 3] }, online_only: { ids: [4, 5, 6] }
933 */
934 public function get_variations_visibility_settings( $scope = 'default' ) {
935 $settings = $this->get_visibility_settings();
936
937 /*
938 * Filters the product visibility settings.
939 *
940 * @param {array} $settings
941 * @returns {array} $settings
942 * @since 1.0.0
943 * @hook woocommerce_pos_product_visibility_settings
944 */
945 return apply_filters( 'woocommerce_pos_variations_visibility_settings', $settings['variations'][ $scope ], $scope );
946 }
947
948 /**
949 * Get product visibility settings.
950 *
951 * @param string $scope The scope of the settings to get. 'default' or store ID.
952 *
953 * @return array $settings The product visibility settings, eg: { ids: [1, 2, 3] }
954 */
955 public function get_pos_only_variations_visibility_settings( $scope = 'default' ) {
956 $settings = $this->get_variations_visibility_settings( $scope );
957
958 /*
959 * Filters the product visibility settings.
960 *
961 * @param {array} $settings
962 * @returns {array} $settings
963 * @since 1.0.0
964 * @hook woocommerce_pos_product_visibility_settings
965 */
966 return apply_filters( 'woocommerce_pos_pos_only_variations_visibility_settings', $settings['pos_only'], $scope );
967 }
968
969 /**
970 * Get product visibility settings.
971 *
972 * @param string $scope The scope of the settings to get. 'default' or store ID.
973 *
974 * @return array $settings The product visibility settings, eg: { ids: [1, 2, 3] }
975 */
976 public function get_online_only_variations_visibility_settings( $scope = 'default' ) {
977 $settings = $this->get_variations_visibility_settings( $scope );
978
979 /*
980 * Filters the product visibility settings.
981 *
982 * @param {array} $settings
983 * @returns {array} $settings
984 * @since 1.0.0
985 * @hook woocommerce_pos_product_visibility_settings
986 */
987 return apply_filters( 'woocommerce_pos_online_only_variations_visibility_settings', $settings['online_only'], $scope );
988 }
989
990 /**
991 * Check if a product is POS only.
992 *
993 * @param int|string $product_id The product ID.
994 *
995 * @return bool
996 */
997 public function is_product_pos_only( $product_id ) {
998 $product_id = (int) $product_id;
999 $settings = $this->get_pos_only_product_visibility_settings();
1000 $pos_only_ids = array_map( 'intval', (array) $settings['ids'] );
1001
1002 return \in_array( $product_id, $pos_only_ids, true );
1003 }
1004
1005 /**
1006 * Check if a product is Online only.
1007 *
1008 * @param int|string $product_id The product ID.
1009 *
1010 * @return bool
1011 */
1012 public function is_product_online_only( $product_id ) {
1013 $product_id = (int) $product_id;
1014 $settings = $this->get_online_only_product_visibility_settings();
1015 $online_only_ids = array_map( 'intval', (array) $settings['ids'] );
1016
1017 return \in_array( $product_id, $online_only_ids, true );
1018 }
1019
1020 /**
1021 * Check if a variation is POS only.
1022 *
1023 * @param int|string $variation_id The variation ID.
1024 *
1025 * @return bool
1026 */
1027 public function is_variation_pos_only( $variation_id ) {
1028 $variation_id = (int) $variation_id;
1029 $settings = $this->get_pos_only_variations_visibility_settings();
1030 $pos_only_ids = array_map( 'intval', (array) $settings['ids'] );
1031
1032 return \in_array( $variation_id, $pos_only_ids, true );
1033 }
1034
1035 /**
1036 * Check if a variation is Online only.
1037 *
1038 * @param int|string $variation_id The variation ID.
1039 *
1040 * @return bool
1041 */
1042 public function is_variation_online_only( $variation_id ) {
1043 $variation_id = (int) $variation_id;
1044 $settings = $this->get_online_only_variations_visibility_settings();
1045 $online_only_ids = array_map( 'intval', (array) $settings['ids'] );
1046
1047 return \in_array( $variation_id, $online_only_ids, true );
1048 }
1049
1050
1051 /**
1052 * Delete settings in WP options table.
1053 *
1054 * @param string $id The settings section ID.
1055 *
1056 * @return bool|WP_Error
1057 */
1058 public static function delete_settings( $id ) {
1059 if ( ! is_super_admin() && ! current_user_can( 'manage_woocommerce_pos' ) ) {
1060 return new WP_Error( 'unauthorized', 'You do not have permission to delete this option.' );
1061 }
1062
1063 return delete_option( self::$db_prefix . $id );
1064 }
1065
1066 /**
1067 * Delete all settings in WP options table.
1068 *
1069 * @return bool|WP_Error
1070 */
1071 public static function delete_all_settings() {
1072 if ( ! is_super_admin() && ! current_user_can( 'manage_woocommerce_pos' ) ) {
1073 return new WP_Error( 'unauthorized', 'You do not have permission to delete this option.' );
1074 }
1075
1076 foreach ( self::$default_settings as $id => $settings ) {
1077 delete_option( self::$db_prefix . $id );
1078 }
1079
1080 return true;
1081 }
1082
1083 /**
1084 * Get the database version.
1085 *
1086 * @return string
1087 */
1088 public static function get_db_version() {
1089 return get_option( 'woocommerce_pos_db_version', '0' );
1090 }
1091
1092 /**
1093 * Updates db to new version number
1094 * bumps the idb version number.
1095 */
1096 public static function bump_versions(): void {
1097 update_option( 'woocommerce_pos_db_version', VERSION );
1098 }
1099
1100 /**
1101 * Add an ID to a visibility type if it doesn't already exist.
1102 *
1103 * @param array $ids The current array of IDs.
1104 * @param int $id The ID to add.
1105 *
1106 * @return array The updated array of IDs.
1107 */
1108 private function add_id_to_visibility( array $ids, int $id ): array {
1109 if ( ! \in_array( $id, $ids, true ) ) {
1110 $ids[] = $id;
1111 }
1112
1113 return $ids;
1114 }
1115
1116 /**
1117 * Remove an ID from a visibility type if it exists.
1118 *
1119 * @param array $ids The current array of IDs.
1120 * @param int $id The ID to remove.
1121 *
1122 * @return array The updated array of IDs.
1123 */
1124 private function remove_id_from_visibility( array $ids, int $id ): array {
1125 return array_filter(
1126 $ids,
1127 function ( $existing_id ) use ( $id ) {
1128 return $existing_id !== $id;
1129 }
1130 );
1131 }
1132 }
1133