PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.6
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.6
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 / General_Section.php

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

310 lines 9.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * General Settings Section.
4 *
5 * @package WCPOS\WooCommercePOS
6 */
7
8 namespace WCPOS\WooCommercePOS\Services\Settings;
9
10 use WCPOS\WooCommercePOS\Services\Store_Defaults;
11
12 /**
13 * The General Settings Section: store identity, POS behaviour toggles, and
14 * structured store tax IDs.
15 */
16 class General_Section extends Abstract_Section {
17 /**
18 * Section id.
19 */
20 public function id(): string {
21 return 'general';
22 }
23
24 /**
25 * Section defaults.
26 */
27 public function defaults(): array {
28 return array(
29 'pos_only_products' => false,
30 'decimal_qty' => false,
31 'force_ssl' => true,
32 'default_customer' => 0,
33 'default_customer_is_cashier' => false,
34 'barcode_field' => '_global_unique_id',
35 'generate_username' => true,
36 'restore_stock_on_delete' => true,
37 'storefront_receipt_enabled' => false,
38 'storefront_receipt_template' => '',
39 'tracking_consent' => 'undecided',
40 'store_name' => '',
41 'store_phone' => '',
42 'store_email' => '',
43 'policies_and_conditions' => '',
44 'store_tax_ids' => array(),
45 );
46 }
47
48 /**
49 * The PERSISTED tracking consent, bypassing the read view.
50 *
51 * `read()` ends by applying `woocommerce_pos_general_settings`, so the
52 * ordinary accessor lets any plugin filtering that hook manufacture a
53 * consent the merchant never gave. Telemetry send-gates must ask the
54 * database instead. Still runs `migrate()`, so a merchant whose choice
55 * predates the move out of the legacy `tools` option is honoured rather
56 * than silently switched off.
57 *
58 * Display and UI reads keep using the filtered accessor.
59 *
60 * Falls back to the section default rather than an empty string: the value
61 * is mirrored to the POS client, whose schema expects one of the three
62 * states, and `undecided` is the fail-closed one.
63 *
64 * @return string One of allowed|denied|undecided.
65 */
66 public function raw_tracking_consent(): string {
67 $raw = $this->migrate( $this->read_raw() );
68 $defaults = $this->defaults();
69 $fallback = \is_string( $defaults['tracking_consent'] ?? null ) ? $defaults['tracking_consent'] : 'undecided';
70
71 $consent = $raw['tracking_consent'] ?? $fallback;
72
73 return \is_string( $consent ) ? $consent : $fallback;
74 }
75
76 /**
77 * Migrate tracking_consent from the legacy `tools` option if it was set
78 * there before being moved to `general`. In-memory only — an explicit
79 * general-level choice always wins, and the database is never written
80 * from a read path.
81 *
82 * @param array $raw Raw option value.
83 */
84 protected function migrate( array $raw ): array {
85 if ( ! \array_key_exists( 'tracking_consent', $raw ) ) {
86 $legacy_tools = get_option( self::DB_PREFIX . 'tools', array() );
87 if ( \is_array( $legacy_tools ) && \array_key_exists( 'tracking_consent', $legacy_tools ) ) {
88 $raw['tracking_consent'] = $legacy_tools['tracking_consent'];
89 }
90 }
91
92 return $raw;
93 }
94
95 /**
96 * Normalize store_tax_ids and expose resolved fallbacks so the React UI
97 * can render placeholders for store_name / store_phone / store_email /
98 * policies_and_conditions when the user has not entered a value.
99 *
100 * @param array $settings Merged settings.
101 */
102 protected function compose( array $settings ): array {
103 $settings['store_tax_ids'] = self::sanitize_store_tax_ids( $settings['store_tax_ids'] );
104 $settings['store_defaults'] = Store_Defaults::fallbacks();
105
106 return $settings;
107 }
108
109 /**
110 * Sanitize general settings before persisting.
111 *
112 * @param array $settings General settings.
113 */
114 protected function sanitize( array $settings ): array {
115 if ( \array_key_exists( 'store_tax_ids', $settings ) ) {
116 $settings['store_tax_ids'] = self::sanitize_store_tax_ids( $settings['store_tax_ids'] );
117 }
118
119 foreach ( array( 'store_name', 'store_phone' ) as $key ) {
120 if ( \array_key_exists( $key, $settings ) ) {
121 $settings[ $key ] = \is_string( $settings[ $key ] )
122 ? sanitize_text_field( $settings[ $key ] )
123 : '';
124 }
125 }
126
127 if ( \array_key_exists( 'store_email', $settings ) ) {
128 $email = \is_string( $settings['store_email'] ) ? trim( $settings['store_email'] ) : '';
129 $settings['store_email'] = ( '' !== $email && is_email( $email ) ) ? sanitize_email( $email ) : '';
130 }
131
132 if ( \array_key_exists( 'policies_and_conditions', $settings ) ) {
133 $settings['policies_and_conditions'] = \is_string( $settings['policies_and_conditions'] )
134 ? sanitize_textarea_field( $settings['policies_and_conditions'] )
135 : '';
136 }
137
138 if ( \array_key_exists( 'storefront_receipt_enabled', $settings ) ) {
139 $settings['storefront_receipt_enabled'] = wp_validate_boolean( $settings['storefront_receipt_enabled'] );
140 }
141
142 // Template IDs are numeric (database) or slug strings (virtual/gallery); an empty
143 // string means "use the active receipt template". Store as a sanitized string and
144 // let Storefront_Receipts pass it through the already-validated ?template= path.
145 if ( \array_key_exists( 'storefront_receipt_template', $settings ) ) {
146 $settings['storefront_receipt_template'] = \is_scalar( $settings['storefront_receipt_template'] )
147 ? sanitize_text_field( (string) $settings['storefront_receipt_template'] )
148 : '';
149 }
150
151 // store_defaults is a read-only computed field for the UI; never persist it.
152 unset( $settings['store_defaults'] );
153
154 return $settings;
155 }
156
157 /**
158 * Store_tax_ids is a full replacement on PATCH so users can remove rows
159 * by sending the trimmed list.
160 *
161 * @param array $existing Existing settings view.
162 * @param array $patch Incoming partial payload.
163 */
164 public function merge( array $existing, array $patch ): array {
165 $settings = array_replace_recursive( $existing, $patch );
166 if ( isset( $patch['store_tax_ids'] ) && \is_array( $patch['store_tax_ids'] ) ) {
167 $settings['store_tax_ids'] = $patch['store_tax_ids'];
168 }
169
170 return $settings;
171 }
172
173 /**
174 * REST endpoint args for the general update route (formerly the legacy
175 * API\Settings controller's get_general_endpoint_args(), removed in 1.10).
176 */
177 public function endpoint_args(): array {
178 return array(
179 'pos_only_products' => array(
180 'validate_callback' => function ( $param, $request, $key ) {
181 return \is_bool( $param );
182 },
183 ),
184 'decimal_qty' => array(
185 'validate_callback' => function ( $param, $request, $key ) {
186 return \is_bool( $param );
187 },
188 ),
189 'force_ssl' => array(
190 'validate_callback' => function ( $param, $request, $key ) {
191 return \is_bool( $param );
192 },
193 ),
194 'default_customer' => array(
195 'validate_callback' => function ( $param, $request, $key ) {
196 return \is_integer( $param );
197 },
198 ),
199 'default_customer_is_cashier' => array(
200 'validate_callback' => function ( $param, $request, $key ) {
201 return \is_bool( $param );
202 },
203 ),
204 'barcode_field' => array(
205 'validate_callback' => function ( $param, $request, $key ) {
206 return \is_string( $param );
207 },
208 ),
209 'generate_username' => array(
210 'validate_callback' => function ( $param, $request, $key ) {
211 return \is_bool( $param );
212 },
213 ),
214 'restore_stock_on_delete' => array(
215 'validate_callback' => function ( $param, $request, $key ) {
216 return \is_bool( $param );
217 },
218 ),
219 'tracking_consent' => array(
220 'validate_callback' => function ( $param, $request, $key ) {
221 return \is_string( $param ) && \in_array( $param, array( 'allowed', 'denied', 'undecided' ), true );
222 },
223 ),
224 'store_tax_ids' => array(
225 'validate_callback' => function ( $param, $request, $key ) {
226 return \is_array( $param );
227 },
228 ),
229 'store_name' => array(
230 'validate_callback' => function ( $param, $request, $key ) {
231 return \is_string( $param );
232 },
233 ),
234 'store_phone' => array(
235 'validate_callback' => function ( $param, $request, $key ) {
236 return \is_string( $param );
237 },
238 ),
239 'store_email' => array(
240 'validate_callback' => function ( $param, $request, $key ) {
241 return \is_string( $param );
242 },
243 ),
244 'policies_and_conditions' => array(
245 'validate_callback' => function ( $param, $request, $key ) {
246 return \is_string( $param );
247 },
248 ),
249 );
250 }
251
252 /**
253 * Sanitize the additional free-store tax IDs entered in General settings.
254 *
255 * Drops malformed rows and keeps optional country/label fields only when
256 * non-empty. Values are preserved verbatim apart from normal text-field
257 * sanitization and surrounding whitespace.
258 *
259 * @param mixed $tax_ids Raw tax IDs.
260 *
261 * @return array<int,array<string,string>>
262 */
263 public static function sanitize_store_tax_ids( $tax_ids ): array {
264 if ( ! \is_array( $tax_ids ) ) {
265 return array();
266 }
267
268 $sanitized = array();
269 foreach ( $tax_ids as $tax_id ) {
270 if ( ! \is_array( $tax_id ) ) {
271 continue;
272 }
273
274 $type = isset( $tax_id['type'] ) && \is_string( $tax_id['type'] )
275 ? sanitize_key( $tax_id['type'] )
276 : '';
277 $value = isset( $tax_id['value'] ) && \is_string( $tax_id['value'] )
278 ? trim( sanitize_text_field( $tax_id['value'] ) )
279 : '';
280
281 if ( '' === $type || '' === $value ) {
282 continue;
283 }
284
285 $entry = array(
286 'type' => $type,
287 'value' => $value,
288 );
289
290 $country = isset( $tax_id['country'] ) && \is_string( $tax_id['country'] )
291 ? strtoupper( trim( sanitize_text_field( $tax_id['country'] ) ) )
292 : '';
293 if ( '' !== $country ) {
294 $entry['country'] = $country;
295 }
296
297 $label = isset( $tax_id['label'] ) && \is_string( $tax_id['label'] )
298 ? trim( sanitize_text_field( $tax_id['label'] ) )
299 : '';
300 if ( '' !== $label ) {
301 $entry['label'] = $label;
302 }
303
304 $sanitized[] = $entry;
305 }
306
307 return $sanitized;
308 }
309 }
310