PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.16
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.16, at includes/Services/Settings/General_Section.php

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