PluginProbe ʕ •ᴥ•ʔ
Tracking Code Manager / 2.8.0
Tracking Code Manager v2.8.0
2.8.0 2.7.0 trunk 1.11.8 1.11.9 1.12.0 1.12.1 1.12.2 1.12.3 1.4 1.5 2.0.0 2.0.1 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.6.0
tracking-code-manager / includes / classes / utils / Options.php
tracking-code-manager / includes / classes / utils Last commit date
Cron.php 4 years ago Ecommerce.php 4 years ago Language.php 4 years ago Logger.php 1 month ago MobileDetect.php 1 month ago Options.php 2 weeks ago Plugin.php 1 month ago Properties.php 4 years ago Tracking.php 1 month ago Utils.php 1 month ago
Options.php
558 lines
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit;
4 }
5
6 class TCMP_Options {
7
8 private $request_data;
9
10 public function __construct() {
11 $this->request_data = array();
12 }
13
14 //Cache
15 private function getCacheName( $array ) {
16 if ( ! is_array( $array ) ) {
17 $array = array( $array );
18 }
19 $result = 'Cache';
20 foreach ( $array as $v ) {
21 if ( is_object( $v ) ) {
22 $v = get_class( $v );
23 } elseif ( is_array( $v ) ) {
24 $v = $v[0];
25 if ( is_object( $v ) ) {
26 $v = get_class( $v );
27 }
28 }
29 $result .= '_' . $v;
30 }
31 return $result;
32 }
33 public function getCache( $name, $callable = null ) {
34 $key = $this->getCacheName( $name );
35 $result = $this->getRequest( $key, false );
36 if ( false === $result && $callable && is_callable( $callable ) ) {
37 $result = $callable();
38 $this->setCache( $name, $result );
39 }
40 return $result;
41 }
42 public function setCache( $name, $value ) {
43 $key = $this->getCacheName( $name );
44 $this->setRequest( $key, $value );
45 }
46
47 //always add a prefix to avoid conflicts with other plugins
48 private function get_key( $key ) {
49 return 'TCM_' . $key;
50 }
51 //option
52 private function removeOption( $key ) {
53 $key = $this->get_key( $key );
54 delete_option( $key );
55 }
56 private function getOption( $key, $default = false ) {
57 $key = $this->get_key( $key );
58 $result = get_option( $key, $default );
59 if ( is_string( $result ) ) {
60 $result = trim( $result );
61 }
62 return $result;
63 }
64 private function setOption( $key, $value ) {
65 $key = $this->get_key( $key );
66 if ( is_bool( $value ) ) {
67 $value = ( $value ? 1 : 0 );
68 }
69 update_option( $key, $value );
70 }
71
72 //$_SESSION
73 private function removeSession( $key ) {
74 global $wp_session;
75
76 $key = $this->get_key( $key );
77 if ( isset( $wp_session[ $key ] ) ) {
78 unset( $wp_session[ $key ] );
79 }
80 }
81 private function getSession( $key, $default = false ) {
82 global $wp_session;
83
84 $key = $this->get_key( $key );
85 $result = $default;
86 if ( isset( $wp_session[ $key ] ) ) {
87 $result = $wp_session[ $key ];
88 }
89 if ( is_string( $result ) ) {
90 $result = trim( $result );
91 }
92 return $result;
93 }
94 private function setSession( $key, $value ) {
95 global $wp_session;
96
97 $key = $this->get_key( $key );
98 $wp_session[ $key ] = $value;
99 }
100
101 // Request-scoped state.
102 //
103 // These values are the plugin's own per-request scratch space: the post being
104 // rendered, the ids of snippets already written, the pending admin notices.
105 // Every one of them is produced by this plugin during the request and read
106 // back by it later in the same request. None is ever legitimately supplied by
107 // the client.
108 //
109 // getRequest() used to fall back to $_POST['TCM_' . $key] whenever a key was
110 // absent from the in-memory store, which turned all of that internal state
111 // into an unauthenticated input surface. That is CVE-2026-15180: a POST of
112 // TCM_ErrorMessages[0] landed in the admin notice box on the next page render.
113 // The fallback was already vestigial — nothing in the plugin has written to
114 // $_POST['TCM_*'] since 2.5.0 (RDU-1630) — so it is removed outright rather
115 // than gated behind a nonce, which would have kept an attacker-influenced
116 // path alive for no caller's benefit.
117 //
118 // Consequence for removeRequest(): unsetting the POST field is no longer a
119 // way to clear a key, so it now clears the in-memory entry. That also fixes
120 // the clean-up it was always meant to perform — writeMessages( clean: true )
121 // previously only ever removed a POST field that the plugin itself never set,
122 // so an internally queued notice survived its own render and could be emitted
123 // a second time by the next writeMessages() call in the same request
124 // (includes/admin/editor.php does exactly that).
125 private function removeRequest( $key ) {
126 unset( $this->request_data[ $key ] );
127 }
128 private function getRequest( $key, $default = false ) {
129 $result = $default;
130 if ( isset( $this->request_data[ $key ] ) ) {
131 if ( is_object( $this->request_data[ $key ] ) ) {
132 $result = clone $this->request_data[ $key ];
133 } else {
134 $result = $this->request_data[ $key ];
135 }
136 $result = $this->recursive_wp_kses( $result );
137 }
138 return $result;
139 }
140
141 // Sanitize a request-scoped value tree.
142 //
143 // The permissive whitelist in tcmp_free_wp_kses_tags_attrs.php exists for
144 // exactly one kind of value: a snippet body. Those are admin-authored
145 // tracking snippets and legitimately contain <script>, <iframe> and onload —
146 // that is the plugin's purpose. Applying the same whitelist to every other
147 // string, as this method used to, extended "may contain executable markup" to
148 // values that are never tracking code: notice text, names, ids.
149 //
150 // So the 'code' field keeps the permissive pass (still opt-out-able via Skip
151 // Code Sanitization, since a snippet may need markup the whitelist omits) and
152 // everything else gets wp_kses_post(), which keeps the formatting notices
153 // legitimately embed — <a href>, <strong>, <span style> — while dropping
154 // script/iframe/style and every on* handler.
155 //
156 // This mirrors the split TCMP_Utils::sanitize_post_or_get() already applies on
157 // the save path (F-06): permissive for 'code', plain text for the rest.
158 public function recursive_wp_kses( $array ) {
159 global $tcmp_allowed_html_tags;
160 foreach ( $array as $key => &$value ) {
161 if ( is_array( $value ) ) {
162 $value = $this->recursive_wp_kses( $value );
163 } elseif ( 'code' === $key ) {
164 if ( ! $this->getSkipCodeSanitization() ) {
165 $value = wp_kses( $value, $tcmp_allowed_html_tags );
166 }
167 } elseif ( is_string( $value ) ) {
168 $value = wp_kses_post( $value );
169 } else {
170 // do nothing ... could be a video or graphics object
171 }
172 }
173 return $array;
174 }
175
176 private function setRequest( $key, $value ) {
177 $this->request_data[ $key ] = $value;
178 }
179
180 public function isPluginFirstInstall() {
181 return $this->getOption( 'PluginFirstInstall', false );
182 }
183 public function setPluginFirstInstall( $value ) {
184 $this->setOption( 'PluginFirstInstall', $value );
185 }
186 public function isShowActivationNotice() {
187 return $this->getOption( 'ShowActivationNotice', false );
188 }
189 public function setShowActivationNotice( $value ) {
190 $this->setOption( 'ShowActivationNotice', $value );
191 }
192
193 public function getShowWhatsNewSeenVersion() {
194 return intval( $this->getOption( 'ShowWhatsNewSeenVersion', 0 ) );
195 }
196 public function setShowWhatsNewSeenVersion( $value ) {
197 $this->setOption( 'ShowWhatsNewSeenVersion', $value );
198 }
199
200 //ShowWhatsNew
201 public function isShowWhatsNew() {
202 $result = intval( $this->getOption( 'ShowWhatsNew', true ) );
203 if ( $result ) {
204 $v = $this->getShowWhatsNewSeenVersion();
205 if ( TCMP_WHATSNEW_VERSION == $v ) {
206 $result = false;
207 $this->getOption( 'ShowWhatsNew', false );
208 }
209 }
210 return $result;
211 }
212 public function setShowWhatsNew( $value ) {
213 $this->setOption( 'ShowWhatsNew', $value );
214 }
215
216 //TrackingEnable
217 public function isTrackingEnable() {
218 return $this->getOption( 'TrackingEnable', 0 );
219 }
220 public function setTrackingEnable( $value ) {
221 $this->setOption( 'TrackingEnable', $value );
222 }
223 //TrackingNotice
224 public function isTrackingNotice() {
225 return $this->getOption( 'TrackingNotice', 1 );
226 }
227 public function setTrackingNotice( $value ) {
228 $this->setOption( 'TrackingNotice', $value );
229 }
230
231 public function getTrackingLastSend() {
232 return $this->getOption( 'TrackingLastSend[' . TCMP_PLUGIN_SLUG . ']', 0 );
233 }
234 public function setTrackingLastSend( $value ) {
235 $this->setOption( 'TrackingLastSend[' . TCMP_PLUGIN_SLUG . ']', $value );
236 }
237 public function getPluginInstallDate() {
238 return $this->getOption( 'PluginInstallDate[' . TCMP_PLUGIN_SLUG . ']', 0 );
239 }
240 public function setPluginInstallDate( $value ) {
241 $this->setOption( 'PluginInstallDate[' . TCMP_PLUGIN_SLUG . ']', $value );
242 }
243 public function getPluginUpdateDate() {
244 return $this->getOption( 'PluginUpdateDate[' . TCMP_PLUGIN_SLUG . ']', 0 );
245 }
246 public function setPluginUpdateDate( $value ) {
247 $this->setOption( 'PluginUpdateDate[' . TCMP_PLUGIN_SLUG . ']', $value );
248 }
249
250 //LicenseKey
251 public function getLicenseKey() {
252 return $this->getOption( 'LiceseKey', '' );
253 }
254 public function setLicenseKey( $value ) {
255 $this->setOption( 'LiceseKey', $value );
256 }
257 //LicenseStatus
258 public function isLicenseSuccess() {
259 return $this->getOption( 'LicenseSuccess', 0 );
260 }
261 public function setLicenseSuccess( $value ) {
262 $this->setOption( 'LicenseSuccess', $value );
263 }
264 //License
265 public function getLicense() {
266 return $this->getOption( 'License', false );
267 }
268 public function setLicense( $value ) {
269 $this->setOption( 'License', $value );
270 }
271 //LicenseSiteCount
272 public function getLicenseSiteCount() {
273 return $this->getOption( 'LicenseSiteCount', false );
274 }
275 public function setLicenseSiteCount( $value ) {
276 $this->setOption( 'LicenseSiteCount', $value );
277 }
278 //LicenseLastCheck
279 public function getLicenseLastCheck() {
280 return intval( $this->getOption( 'LicenseLastCheck', 0 ) );
281 }
282 public function setLicenseLastCheck( $value ) {
283 $this->setOption( 'LicenseLastCheck', intval( $value ) );
284 }
285
286 //LoggerEnable
287 public function isLoggerEnable() {
288 return ( $this->getOption( 'LoggerEnable', false ) || ( defined( 'TCMP_LOGGER' ) && TCMP_LOGGER ) );
289 }
290 public function setLoggerEnable( $value ) {
291 $this->setOption( 'LoggerEnable', $value );
292 }
293
294 //Snippet
295 public function getSnippet( $id ) {
296 return $this->getOption( 'Snippet_' . $id, null );
297 }
298 public function setSnippet( $id, $value ) {
299 $this->setOption( 'Snippet_' . $id, $value );
300 }
301 public function remove_snippet( $id ) {
302 $this->removeOption( 'Snippet_' . $id );
303 }
304 //SnippetList
305 public function getSnippetList() {
306 return $this->getOption( 'SnippetList', array() );
307 }
308 public function setSnippetList( $value ) {
309 $this->setOption( 'SnippetList', $value );
310 }
311 public function removeSnippetList() {
312 $this->removeOption( 'SnippetList' );
313 }
314
315 public function pushConversionSnippets( $options, TCMP_EcommercePurchase $purchase ) {
316 global $tcmp;
317 $this->setRequest( 'EcommercePurchase', $purchase );
318 $snippets = $tcmp->manager->get_conversion_snippets( $options );
319 foreach ( $snippets as $v ) {
320 $id = $v['id'];
321 $tcmp->options->pushConversionSnippetId( $id );
322 }
323 }
324 public function pushConversionSnippetId( $id ) {
325 $array = $this->getRequest( 'ConversionSnippetIds', array() );
326 $array[] = $id;
327 $array = array_unique( $array );
328 $this->setRequest( 'ConversionSnippetIds', $array );
329 }
330 public function get_conversion_snippet_ids() {
331 return $this->getRequest( 'ConversionSnippetIds', false );
332 }
333 public function getEcommercePurchase() {
334 /* @var $result TCMP_EcommercePurchase */
335 $result = $this->getRequest( 'EcommercePurchase', false );
336 return $result;
337 }
338
339 public function hasSnippetWritten( $snippet ) {
340 //check also the md5 of code so if the user create 2 different snippets with
341 //the same tracking code we will not insert into 2 times inside the html
342 $id = $snippet['id'];
343 $md5 = md5( $snippet['code'] );
344
345 $listIds = $this->getRequest( 'SnippetsWrittenIds', array() );
346 $listMd5 = $this->getRequest( 'SnippetsWrittenMd5', array() );
347
348 $result = ( in_array( $id, $listIds ) || in_array( $md5, $listMd5 ) );
349 return $result;
350 }
351 public function pushSnippetWritten( $snippet ) {
352 $md5 = md5( $snippet['code'] );
353 $id = $snippet['id'];
354 $listIds = $this->getRequest( 'SnippetsWrittenIds', array() );
355 $listMd5 = $this->getRequest( 'SnippetsWrittenMd5', array() );
356
357 $listIds[ $id ] = $snippet;
358 $listMd5[ $md5 ] = $id;
359 $this->setRequest( 'SnippetsWrittenIds', $listIds );
360 $this->setRequest( 'SnippetsWrittenMd5', $listMd5 );
361 }
362 public function getSnippetsWritten() {
363 return $this->getRequest( 'SnippetsWrittenIds', array() );
364 }
365 public function clearSnippetsWritten() {
366 $this->setRequest( 'SnippetsWrittenIds', array() );
367 $this->setRequest( 'SnippetsWrittenMd5', array() );
368 }
369
370 //PostShown
371 public function getPostShown() {
372 return $this->getRequest( 'PostShown' );
373 }
374 public function setPostShown( $post ) {
375 $this->setRequest( 'PostShown', $post );
376 }
377
378 private function hasGenericMessages( $type ) {
379 $result = $this->getRequest( $type . 'Messages', null );
380 return ( is_array( $result ) && count( $result ) > 0 );
381 }
382 private function pushGenericMessage( $type, $message, $v1 = null, $v2 = null, $v3 = null, $v4 = null, $v5 = null ) {
383 global $tcmp;
384 $array = $this->getRequest( $type . 'Messages', array() );
385 $array[] = $tcmp->lang->L( $message, $v1, $v2, $v3, $v4, $v5 );
386 $this->setRequest( $type . 'Messages', $array );
387 }
388 private function writeGenericMessages( $type, $clean = true ) {
389 $type = sanitize_text_field( $type );
390 $result = false;
391 $array = $this->getRequest( $type . 'Messages', array() );
392 if ( is_array( $array ) && count( $array ) > 0 ) {
393 $result = true;
394 // Escape at the sink so safety no longer depends on every caller
395 // pre-escaping its message arguments (see F-04). wp_kses_post keeps
396 // the intentional markup (links, styled spans) some notices embed
397 // while neutralising anything dangerous. It now wraps the wpautop()
398 // result rather than each message, so the escaping is applied to
399 // everything that is actually echoed.
400 $html = wpautop( implode( "\n", $array ) );
401 ?>
402 <div class="tcmp-box-<?php echo esc_attr( strtolower( $type ) ); ?>"><?php echo wp_kses_post( $html ); ?></div>
403 <?php
404 }
405 if ( $clean ) {
406 $this->removeRequest( $type . 'Messages' );
407 }
408 return $result;
409 }
410
411 //WarningMessages
412 public function hasWarningMessages() {
413 return $this->hasGenericMessages( 'Warning' );
414 }
415 public function pushWarningMessage( $message, $v1 = null, $v2 = null, $v3 = null, $v4 = null, $v5 = null ) {
416 return $this->pushGenericMessage( 'Warning', $message, $v1, $v2, $v3, $v4, $v5 );
417 }
418 public function writeWarningMessages( $clean = true ) {
419 return $this->writeGenericMessages( 'Warning', $clean );
420 }
421 //SuccessMessages
422 public function hasSuccessMessages() {
423 return $this->hasGenericMessages( 'Success' );
424 }
425 public function pushSuccessMessage( $message, $v1 = null, $v2 = null, $v3 = null, $v4 = null, $v5 = null ) {
426 return $this->pushGenericMessage( 'Success', $message, $v1, $v2, $v3, $v4, $v5 );
427 }
428 public function writeSuccessMessages( $clean = true ) {
429 return $this->writeGenericMessages( 'Success', $clean );
430 }
431 //InfoMessages
432 public function hasInfoMessages() {
433 return $this->hasGenericMessages( 'Info' );
434 }
435 public function pushInfoMessage( $message, $v1 = null, $v2 = null, $v3 = null, $v4 = null, $v5 = null ) {
436 return $this->pushGenericMessage( 'Info', $message, $v1, $v2, $v3, $v4, $v5 );
437 }
438 public function writeInfoMessages( $clean = true ) {
439 return $this->writeGenericMessages( 'Info', $clean );
440 }
441 //ErrorMessages
442 public function hasErrorMessages() {
443 return $this->hasGenericMessages( 'Error' );
444 }
445 public function pushErrorMessage( $message, $v1 = null, $v2 = null, $v3 = null, $v4 = null, $v5 = null ) {
446 return $this->pushGenericMessage( 'Error', $message, $v1, $v2, $v3, $v4, $v5 );
447 }
448 public function writeErrorMessages( $clean = true ) {
449 return $this->writeGenericMessages( 'Error', $clean );
450 }
451
452 public function writeMessages( $clean = true ) {
453 $result = false;
454 if ( $this->writeInfoMessages( $clean ) ) {
455 $result = true;
456 }
457 if ( $this->writeSuccessMessages( $clean ) ) {
458 $result = true;
459 }
460 if ( $this->writeWarningMessages( $clean ) ) {
461 $result = true;
462 }
463 if ( $this->writeErrorMessages( $clean ) ) {
464 $result = true;
465 }
466
467 return $result;
468 }
469 public function pushMessage( $success, $message, $v1 = null, $v2 = null, $v3 = null, $v4 = null, $v5 = null ) {
470 if ( $success ) {
471 $this->pushSuccessMessage( $message . 'Success', $v1, $v2, $v3, $v4, $v5 );
472 } else {
473 $this->pushErrorMessage( $message . 'Error', $v1, $v2, $v3, $v4, $v5 );
474 }
475 }
476
477 public function getFeedbackEmail() {
478 return $this->getOption( 'FeedbackEmail', get_bloginfo( 'admin_email' ) );
479 }
480 public function setFeedbackEmail( $value ) {
481 $this->setOption( 'FeedbackEmail', $value );
482 }
483
484 //MetaboxPostTypes
485 public function getMetaboxPostTypes( $create = true ) {
486 global $tcmp;
487 $result = $this->getOption( 'MetaboxPostTypes', array() );
488 if ( $create ) {
489 $types = $tcmp->utils->query( TCMP_QUERY_POST_TYPES );
490 foreach ( $types as $v ) {
491 $v = $v['id'];
492 if ( ! isset( $result[ $v ] ) ) {
493 $result[ $v ] = ( in_array( $v, array( 'post', 'page' ) ) ? 1 : 0 );
494 }
495 }
496 }
497 return $result;
498 }
499 public function setMetaboxPostTypes( $values ) {
500 $this->setOption( 'MetaboxPostTypes', $values );
501 }
502
503 // Add additional recognized tags and attributes
504 public function getAdditionalRecognizedTags() {
505 return $this->getOption( 'additionalRecognizedTags', '' );
506 }
507 public function getAdditionalRecognizedAttributes() {
508 return $this->getOption( 'additionalRecognizedAttributes', '' );
509 }
510 public function setAdditionalRecognizedTags( $text ) {
511 if (is_string($text)) {
512 $this->setOption( 'additionalRecognizedTags', $text );
513 }
514 }
515 public function setAdditionalRecognizedAttributes( $text ) {
516 if (is_string($text)) {
517 $this->setOption( 'additionalRecognizedAttributes', $text );
518 }
519 }
520
521 // Modify Superglobal Variable
522 public function getModifySuperglobalVariable() {
523 return $this->getOption( 'ModifySuperglobalVariable', false );
524 }
525
526 public function setModifySuperglobalVariable( $value ) {
527 global $tcmp;
528 if ( $tcmp->utils->isTrue( $value ) ) {
529 $this->setOption( 'ModifySuperglobalVariable', true );
530 } else {
531 $this->setOption( 'ModifySuperglobalVariable', false );
532 }
533 }
534
535 //hook priority
536 public function getHookPriority() {
537 return $this->getOption( 'HookPriority', TCMP_HOOK_PRIORITY_DEFAULT );
538 }
539
540 public function setHookPriority( $value ) {
541 $this->setOption( 'HookPriority', $value );
542 }
543
544 // Skip Code Sanitization
545 public function getSkipCodeSanitization() {
546 return $this->getOption( 'SkipCodeSanitization', false );
547 }
548
549 public function setSkipCodeSanitization( $value ) {
550 global $tcmp;
551 if ( $tcmp->utils->isTrue( $value ) ) {
552 $this->setOption( 'SkipCodeSanitization', true );
553 } else {
554 $this->setOption( 'SkipCodeSanitization', false );
555 }
556 }
557 }
558