PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / trunk
WCPOS – Point of Sale (POS) plugin for WooCommerce vtrunk
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 1.9.13 1.9.12 1.9.11 1.9.10 All 159 releases
woocommerce-pos / includes / Services / Settings.php

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

690 lines 18.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 WP_Error;
11 use WCPOS\WooCommercePOS\Interfaces\Settings_Section_Interface;
12 use WCPOS\WooCommercePOS\Services\Settings\Access_Section;
13 use WCPOS\WooCommercePOS\Services\Settings\Checkout_Section;
14 use WCPOS\WooCommercePOS\Services\Settings\Cloud_Print_Section;
15 use WCPOS\WooCommercePOS\Services\Settings\General_Section;
16 use WCPOS\WooCommercePOS\Services\Settings\License_Section;
17 use WCPOS\WooCommercePOS\Services\Settings\Section_Registry;
18 use WCPOS\WooCommercePOS\Services\Settings\Tax_Ids_Section;
19 use WCPOS\WooCommercePOS\Services\Settings\Tools_Section;
20 use WCPOS\WooCommercePOS\Services\Settings\Payment_Gateways_Section;
21 use WCPOS\WooCommercePOS\Services\Settings\Visibility_Section;
22 use const WCPOS\WooCommercePOS\VERSION;
23
24 /**
25 * Settings Service class.
26 */
27 class Settings {
28 /**
29 * Prefix for the $wpdb->options table.
30 *
31 * @var string
32 */
33 protected static $db_prefix = 'woocommerce_pos_settings_';
34
35 /**
36 * The single instance of the class.
37 *
38 * @var null|Settings
39 */
40 private static $instance = null;
41
42 /**
43 * The Section Registry. Built lazily on first access so registrants can
44 * hook `woocommerce_pos_register_settings_sections` during plugins_loaded.
45 *
46 * @var null|Section_Registry
47 */
48 private $registry = null;
49
50 /**
51 * Get the Section Registry, building and populating it on first access.
52 *
53 * @return Section_Registry
54 */
55 public function sections(): Section_Registry {
56 if ( null === $this->registry ) {
57 // Assign before firing the action: a re-entrant settings read from
58 // inside a registration callback gets the partially built registry
59 // instead of recursing forever.
60 $this->registry = new Section_Registry();
61
62 // All core sections are registered here, before the action fires so
63 // extensions can rely on core sections already being present.
64 $this->registry->register( new General_Section() );
65 $this->registry->register( new Checkout_Section() );
66 $this->registry->register( new Tools_Section() );
67 $this->registry->register( new Tax_Ids_Section() );
68 $this->registry->register( new Visibility_Section() );
69 $this->registry->register( new Payment_Gateways_Section() );
70 $this->registry->register( new Access_Section() );
71 $this->registry->register( new License_Section() );
72 $this->registry->register( new Cloud_Print_Section() );
73
74 /**
75 * Fires when the Section Registry is built, letting Pro and
76 * extensions register their own Settings Sections.
77 *
78 * Fires lazily on the FIRST settings read of the request. Hook
79 * this action at plugin file load or early plugins_loaded —
80 * callbacks added after the first read never run.
81 *
82 * @since 1.10.0
83 *
84 * @param Section_Registry $registry The Section Registry.
85 *
86 * @hook woocommerce_pos_register_settings_sections
87 */
88 do_action( 'woocommerce_pos_register_settings_sections', $this->registry );
89 }
90
91 return $this->registry;
92 }
93
94 /**
95 * Drop the built registry so tests can exercise registration. Not for
96 * production use.
97 */
98 public function reset_sections_for_testing(): void {
99 $this->registry = null;
100 }
101
102 /**
103 * Constructor is private to prevent direct instantiation.
104 * Use woocommerce_pos_get_settings() instead.
105 * Or Settings::instance() if you must.
106 */
107 private function __construct() {
108 }
109
110 /**
111 * Gets the singleton instance.
112 *
113 * @return Settings
114 */
115 public static function instance(): self {
116 if ( null === self::$instance ) {
117 self::$instance = new self();
118 }
119
120 return self::$instance;
121 }
122
123 /**
124 * Get settings for a specific section.
125 *
126 * @param string $id The settings section ID.
127 * @param null|mixed $key The specific setting key.
128 *
129 * @return null|array|mixed|WP_Error
130 */
131 public function get_settings( string $id, $key = null ) {
132 $section = $this->sections()->get( $id );
133
134 if ( ! $section instanceof Settings_Section_Interface ) {
135 return $this->unknown_section_error( $id );
136 }
137
138 $settings = $section->read();
139
140 // If key is not provided, return the entire settings.
141 if ( ! \is_string( $key ) ) {
142 return $settings;
143 }
144
145 if ( ! isset( $settings[ $key ] ) ) {
146 return new WP_Error(
147 'woocommerce_pos_settings_error',
148 // translators: 1. %s: Settings group id, 2. %s: Settings key.
149 \sprintf( __( 'Settings with id %1$s and key %2$s not found', 'woocommerce-pos' ), $id, $key ),
150 array( 'status' => 400 )
151 );
152 }
153
154 return $settings[ $key ];
155 }
156
157 /**
158 * Resolve the URL scheme for POS permalinks.
159 *
160 * Central policy for when POS URLs force https: the force_ssl general
161 * setting (default true) so links work when the site home URL is http
162 * but the POS is served over https, eg: behind an SSL-terminating proxy.
163 *
164 * @return null|string 'https' when force_ssl is enabled, null for the home scheme.
165 */
166 public function url_scheme(): ?string {
167 return $this->force_ssl_enabled() ? 'https' : null;
168 }
169
170 /**
171 * Saves settings for a specific section.
172 *
173 * @param string $id The ID of the settings section being saved.
174 * @param array $settings The settings array to be saved.
175 *
176 * @return array|WP_Error Returns the updated settings array on success or WP_Error on failure.
177 */
178 public function save_settings( string $id, array $settings ) {
179 $section = $this->sections()->get( $id );
180
181 if ( ! $section instanceof Settings_Section_Interface ) {
182 return $this->unknown_section_error( $id );
183 }
184
185 return $section->write( $settings );
186 }
187
188 /**
189 * The error returned for a settings id with no registered Settings Section.
190 *
191 * Registering a section through the
192 * `woocommerce_pos_register_settings_sections` action is the only supported
193 * way to add a settings group; there is no generic option fallback.
194 *
195 * @param string $id The settings section ID.
196 *
197 * @return WP_Error
198 */
199 private function unknown_section_error( string $id ): WP_Error {
200 return new WP_Error(
201 'woocommerce_pos_settings_error',
202 // translators: %s: Settings group id, ie: 'general' or 'checkout'.
203 \sprintf( __( 'Settings with id %s not found', 'woocommerce-pos' ), $id ),
204 array( 'status' => 400 )
205 );
206 }
207
208 /*
209 * Public get_{id}_settings() delegates are supported read API for Pro and
210 * extensions. Keep them as non-deprecated facades until that public surface
211 * is intentionally replaced.
212 */
213
214 /**
215 * Get general settings.
216 *
217 * @return array
218 */
219 public function get_general_settings(): array {
220 $section = $this->sections()->get( 'general' );
221
222 return $section ? $section->read() : array();
223 }
224
225 /**
226 * Sanitize the additional free-store tax IDs entered in General settings.
227 *
228 * Delegates to General_Section::sanitize_store_tax_ids(). Kept here as a
229 * static façade because Store_Defaults::tax_ids() calls this method.
230 *
231 * @param mixed $tax_ids Raw tax IDs.
232 * @return array<int,array<string,string>>
233 */
234 public static function sanitize_store_tax_ids( $tax_ids ): array {
235 return General_Section::sanitize_store_tax_ids( $tax_ids );
236 }
237
238 /**
239 * Get tax IDs settings.
240 *
241 * @return array
242 */
243 public function get_tax_ids_settings(): array {
244 $section = $this->sections()->get( 'tax_ids' );
245
246 return $section ? $section->read() : array();
247 }
248
249 /**
250 * Get checkout settings.
251 *
252 * @return array
253 */
254 public function get_checkout_settings(): array {
255 $section = $this->sections()->get( 'checkout' );
256
257 return $section ? $section->read() : array();
258 }
259
260 /**
261 * Get access settings with role capabilities.
262 *
263 * @return array
264 */
265 public function get_access_settings(): array {
266 $section = $this->sections()->get( 'access' );
267
268 return $section ? $section->read() : array();
269 }
270
271 /**
272 * Get tools settings.
273 *
274 * @return array
275 *
276 * @hook woocommerce_pos_tools_settings
277 */
278 public function get_tools_settings(): array {
279 $section = $this->sections()->get( 'tools' );
280
281 return $section ? $section->read() : array();
282 }
283
284 /**
285 * Get license settings.
286 *
287 * @return array
288 */
289 public function get_license_settings(): array {
290 $section = $this->sections()->get( 'license' );
291
292 return $section ? $section->read() : array();
293 }
294
295 /**
296 * Get available barcode fields.
297 *
298 * @return array
299 */
300 public function get_barcodes(): array {
301 global $wpdb;
302
303 // maybe add custom barcode field.
304 $custom_field = $this->get_settings( 'general', 'barcode_field' );
305
306 // Prepare the basic query.
307 $result = $wpdb->get_col(
308 "
309 SELECT DISTINCT(pm.meta_key)
310 FROM $wpdb->postmeta AS pm
311 JOIN $wpdb->posts AS p
312 ON p.ID = pm.post_id
313 WHERE p.post_type IN ('product', 'product_variation')
314 ORDER BY pm.meta_key
315 "
316 );
317
318 $result = array_merge( $result, Barcode_Field::CORE_FIELDS );
319
320 if ( ! empty( $custom_field ) ) {
321 $result[] = $custom_field;
322 }
323
324 sort( $result );
325
326 return array_unique( $result );
327 }
328
329 /**
330 * Get available order statuses.
331 *
332 * @return array
333 */
334 public function get_order_statuses(): array {
335 $order_statuses = wc_get_order_statuses();
336
337 return array_map( 'wc_get_order_status_name', $order_statuses );
338 }
339
340 /**
341 * Get payment gateways settings.
342 *
343 * @return array
344 */
345 public function get_payment_gateways_settings(): array {
346 $section = $this->sections()->get( 'payment_gateways' );
347
348 return $section ? $section->read() : array();
349 }
350
351 /**
352 * POS Visibility settings.
353 *
354 * @return array
355 */
356 public function get_visibility_settings(): array {
357 return $this->visibility_section()->get_visibility_settings();
358 }
359
360 /**
361 * Update visibility settings.
362 *
363 * @param array $args The visibility settings to update.
364 *
365 * @return bool|WP_Error True on success, WP_Error on failure.
366 */
367 public function update_visibility_settings( array $args ) {
368 return $this->visibility_section()->update_visibility_settings( $args );
369 }
370
371 /**
372 * Get product visibility settings.
373 *
374 * @param string $scope The scope of the settings to get. 'default' or store ID.
375 *
376 * @return array $settings The product visibility settings, eg: { pos_only: { ids: [1, 2, 3] }, online_only: { ids: [4, 5, 6] }
377 */
378 public function get_product_visibility_settings( $scope = 'default' ) {
379 return $this->visibility_section()->get_product_visibility_settings( $scope );
380 }
381
382 /**
383 * Get product visibility settings.
384 *
385 * @param string $scope The scope of the settings to get. 'default' or store ID.
386 *
387 * @return array $settings The product visibility settings, eg: { ids: [1, 2, 3] }
388 */
389 public function get_pos_only_product_visibility_settings( $scope = 'default' ) {
390 return $this->visibility_section()->get_pos_only_product_visibility_settings( $scope );
391 }
392
393 /**
394 * Get product visibility settings.
395 *
396 * @param string $scope The scope of the settings to get. 'default' or store ID.
397 *
398 * @return array $settings The product visibility settings, eg: { ids: [1, 2, 3] }
399 */
400 public function get_online_only_product_visibility_settings( $scope = 'default' ) {
401 return $this->visibility_section()->get_online_only_product_visibility_settings( $scope );
402 }
403
404 /**
405 * Get product visibility settings.
406 *
407 * @param string $scope The scope of the settings to get. 'default' or store ID.
408 *
409 * @return array $settings The product visibility settings, eg: { pos_only: { ids: [1, 2, 3] }, online_only: { ids: [4, 5, 6] }
410 */
411 public function get_variations_visibility_settings( $scope = 'default' ) {
412 return $this->visibility_section()->get_variations_visibility_settings( $scope );
413 }
414
415 /**
416 * Get product visibility settings.
417 *
418 * @param string $scope The scope of the settings to get. 'default' or store ID.
419 *
420 * @return array $settings The product visibility settings, eg: { ids: [1, 2, 3] }
421 */
422 public function get_pos_only_variations_visibility_settings( $scope = 'default' ) {
423 return $this->visibility_section()->get_pos_only_variations_visibility_settings( $scope );
424 }
425
426 /**
427 * Get product visibility settings.
428 *
429 * @param string $scope The scope of the settings to get. 'default' or store ID.
430 *
431 * @return array $settings The product visibility settings, eg: { ids: [1, 2, 3] }
432 */
433 public function get_online_only_variations_visibility_settings( $scope = 'default' ) {
434 return $this->visibility_section()->get_online_only_variations_visibility_settings( $scope );
435 }
436
437 /**
438 * Check if a product is POS only.
439 *
440 * @param int|string $product_id The product ID.
441 *
442 * @return bool
443 */
444 public function is_product_pos_only( $product_id ) {
445 return $this->visibility_section()->is_product_pos_only( $product_id );
446 }
447
448 /**
449 * Check if a product is Online only.
450 *
451 * @param int|string $product_id The product ID.
452 *
453 * @return bool
454 */
455 public function is_product_online_only( $product_id ) {
456 return $this->visibility_section()->is_product_online_only( $product_id );
457 }
458
459 /**
460 * Check if a variation is POS only.
461 *
462 * @param int|string $variation_id The variation ID.
463 *
464 * @return bool
465 */
466 public function is_variation_pos_only( $variation_id ) {
467 return $this->visibility_section()->is_variation_pos_only( $variation_id );
468 }
469
470 /**
471 * Check if a variation is Online only.
472 *
473 * @param int|string $variation_id The variation ID.
474 *
475 * @return bool
476 */
477 public function is_variation_online_only( $variation_id ) {
478 return $this->visibility_section()->is_variation_online_only( $variation_id );
479 }
480
481 /**
482 * Visibility behavior bound to the registered section's storage surface.
483 */
484 private function visibility_section(): Visibility_Section {
485 $section = $this->sections()->get( 'visibility' );
486
487 return $section instanceof Visibility_Section ? $section : new Visibility_Section( $section );
488 }
489
490 /**
491 * Delete settings in WP options table.
492 *
493 * @param string $id The settings section ID.
494 *
495 * @return bool|WP_Error
496 */
497 public static function delete_settings( $id ) {
498 if ( ! is_super_admin() && ! current_user_can( 'manage_woocommerce_pos' ) ) {
499 return new WP_Error( 'unauthorized', 'You do not have permission to delete this option.' );
500 }
501
502 return delete_option( self::$db_prefix . $id );
503 }
504
505 /**
506 * Get the database version.
507 *
508 * @return string
509 */
510 public static function get_db_version() {
511 return get_option( 'woocommerce_pos_db_version', '0' );
512 }
513
514 /**
515 * Updates db to new version number
516 * bumps the idb version number.
517 */
518 public static function bump_versions(): void {
519 update_option( 'woocommerce_pos_db_version', VERSION );
520 }
521
522 /**
523 * Read one key from a section's filtered view, falling back to the
524 * section default. Never returns WP_Error — typed accessors are the safe
525 * read surface for PHP callers.
526 *
527 * @param string $id Section id.
528 * @param string $key Setting key.
529 *
530 * @return mixed
531 */
532 private function section_value( string $id, string $key ) {
533 $settings = $this->get_settings( $id );
534 if ( \is_array( $settings ) && \array_key_exists( $key, $settings ) ) {
535 return $settings[ $key ];
536 }
537
538 $section = $this->sections()->get( $id );
539 if ( $section instanceof Settings_Section_Interface ) {
540 $defaults = $section->defaults();
541
542 return $defaults[ $key ] ?? null;
543 }
544
545 return null;
546 }
547
548 /**
549 * Whether the POS-only products feature is enabled.
550 */
551 public function pos_only_products_enabled(): bool {
552 return (bool) $this->section_value( 'general', 'pos_only_products' );
553 }
554
555 /**
556 * Whether decimal stock/cart quantities are enabled.
557 */
558 public function decimal_qty_enabled(): bool {
559 return (bool) $this->section_value( 'general', 'decimal_qty' );
560 }
561
562 /**
563 * Whether the POS frontend forces HTTPS.
564 */
565 public function force_ssl_enabled(): bool {
566 return wp_validate_boolean( $this->section_value( 'general', 'force_ssl' ) );
567 }
568
569 /**
570 * The product meta key used as the barcode field.
571 */
572 public function barcode_field(): string {
573 return (string) $this->section_value( 'general', 'barcode_field' );
574 }
575
576 /**
577 * The default customer id for new POS orders.
578 */
579 public function default_customer_id(): int {
580 return (int) $this->section_value( 'general', 'default_customer' );
581 }
582
583 /**
584 * Whether the logged-in cashier is the default customer.
585 */
586 public function default_customer_is_cashier(): bool {
587 return (bool) $this->section_value( 'general', 'default_customer_is_cashier' );
588 }
589
590 /**
591 * Whether usernames are auto-generated for new customers.
592 */
593 public function generate_username_enabled(): bool {
594 return (bool) $this->section_value( 'general', 'generate_username' );
595 }
596
597 /**
598 * Whether stock is restored when a POS order is deleted.
599 */
600 public function restore_stock_on_delete_enabled(): bool {
601 return (bool) $this->section_value( 'general', 'restore_stock_on_delete' );
602 }
603
604 /**
605 * The analytics tracking consent state: allowed | denied | undecided.
606 */
607 public function tracking_consent(): string {
608 return (string) $this->section_value( 'general', 'tracking_consent' );
609 }
610
611 /**
612 * The PERSISTED tracking consent, ignoring the settings read filter.
613 *
614 * Use this — never `tracking_consent()` — to decide whether data may be
615 * sent off-site. See General_Section::raw_tracking_consent().
616 */
617 public function raw_tracking_consent(): string {
618 $section = $this->sections()->get( 'general' );
619
620 if ( $section instanceof General_Section ) {
621 return $section->raw_tracking_consent();
622 }
623
624 return '';
625 }
626
627 /**
628 * Whether the JWT may be passed as a query parameter (Tools).
629 */
630 public function use_jwt_as_param_enabled(): bool {
631 return (bool) $this->section_value( 'tools', 'use_jwt_as_param' );
632 }
633
634 /**
635 * Admin email toggles for POS orders.
636 */
637 public function admin_emails(): array {
638 return (array) $this->section_value( 'checkout', 'admin_emails' );
639 }
640
641 /**
642 * Customer email toggles for POS orders.
643 */
644 public function customer_emails(): array {
645 return (array) $this->section_value( 'checkout', 'customer_emails' );
646 }
647
648 /**
649 * Cashier email toggles for POS orders.
650 */
651 public function cashier_emails(): array {
652 return (array) $this->section_value( 'checkout', 'cashier_emails' );
653 }
654
655 /**
656 * Script handles dequeued on the POS checkout pages.
657 */
658 public function dequeue_script_handles(): array {
659 return (array) $this->section_value( 'checkout', 'dequeue_script_handles' );
660 }
661
662 /**
663 * Style handles dequeued on the POS checkout pages.
664 */
665 public function dequeue_style_handles(): array {
666 return (array) $this->section_value( 'checkout', 'dequeue_style_handles' );
667 }
668
669 /**
670 * The default receipt mode: fiscal | live.
671 */
672 public function receipt_default_mode(): string {
673 return (string) $this->section_value( 'checkout', 'receipt_default_mode' );
674 }
675
676 /**
677 * Whether paid POS orders should be rejected when stock is unavailable.
678 */
679 public function prevent_overselling_enabled(): bool {
680 return (bool) $this->section_value( 'checkout', 'prevent_overselling' );
681 }
682
683 /**
684 * The user-override tax-ID write map (type => meta key).
685 */
686 public function tax_id_write_map(): array {
687 return (array) $this->section_value( 'tax_ids', 'write_map' );
688 }
689 }
690