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 / core / Manager.php
tracking-code-manager / includes / classes / core Last commit date
Manager.php 1 month ago Singleton.php 3 months ago
Manager.php
825 lines
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit;
4 }
5
6 class TCMP_Manager {
7 public function __construct() {
8 }
9 public function init() {
10 add_action( 'wp_ajax_TCMP_changeOrder', array( &$this, 'change_order' ) );
11 }
12 public function is_limit_reached( $notice = true ) {
13 global $tcmp;
14 $cnt = $this->codes_count();
15 $result = ( $cnt >= TCMP_SNIPPETS_LIMIT );
16 if ( $result && $notice ) {
17 $tcmp->options->pushWarningMessage( 'SnippetsLimitReached', TCMP_SNIPPETS_LIMIT, TCMP_PAGE_PREMIUM );
18 } elseif ( $notice && $cnt > 0 ) {
19 $tcmp->options->pushSuccessMessage( 'SnippetsLimitNotice', $cnt, TCMP_SNIPPETS_LIMIT, TCMP_PAGE_PREMIUM );
20 }
21 return $result;
22 }
23 public function change_order() {
24 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'change_order' ) ) {
25 wp_send_json_error( 'Invalid nonce' );
26 }
27
28 if ( ! current_user_can( 'edit_plugins' ) ) {
29 return;
30 }
31
32 if ( ! isset( $_POST['order'] ) ) {
33 return;
34 }
35
36 $data = array();
37 parse_str( tcmp_sqs( 'order' ), $data );
38
39 if ( isset( $data['row'] ) ) {
40 $snippets = $this->values();
41 foreach ( $snippets as $id => $v ) {
42 $v['order'] = 0;
43 $snippets[ $id ] = $v;
44 }
45
46 $index = 1;
47 foreach ( $data['row'] as $order => $id ) {
48 $v = $snippets[ $id ];
49 $v['order'] = $index;
50 $snippets[ $id ] = $v;
51 ++$index;
52 }
53
54 foreach ( $snippets as $id => $v ) {
55 $this->put( $id, $v );
56 }
57 }
58 echo 'OK';
59 wp_die();
60 }
61
62 public function match_device_type( $snippet ) {
63 global $tcmp;
64 $device_type = $tcmp->utils->get( $snippet, 'deviceType', false );
65 $device_type = $tcmp->utils->to_array( $device_type );
66 if ( false == $device_type || 0 == count( $device_type ) ) {
67 return true;
68 }
69
70 $detect = new TCMP_Mobile_Detect();
71 if ( $detect->isMobile() ) {
72 $type = TCMP_DEVICE_TYPE_MOBILE;
73 } elseif ( $detect->isTablet() ) {
74 $type = TCMP_DEVICE_TYPE_TABLET;
75 } else { //if(!$detect->isMobile() && !$detect->isTablet()) {
76 $type = TCMP_DEVICE_TYPE_DESKTOP;
77 }
78
79 $result = false;
80 if ( in_array( TCMP_DEVICE_TYPE_ALL, $device_type ) || in_array( $type, $device_type ) ) {
81 $result = true;
82 }
83 return $result;
84 }
85 public function is_mode_script( $snippet ) {
86 global $tcmp;
87 $result = $tcmp->utils->iget( $snippet, 'trackMode', 0 );
88 return ( 0 == $result );
89 }
90 public function is_mode_conversion( $snippet ) {
91 global $tcmp;
92 $result = $tcmp->utils->iget( $snippet, 'trackMode', 0 );
93 return ( 0 != $result );
94 }
95 public function is_page_everywhere( $snippet ) {
96 global $tcmp;
97 if ( ! $this->is_mode_script( $snippet ) ) {
98 return false;
99 }
100
101 $result = $tcmp->utils->iget( $snippet, 'trackPage', 0 );
102 return ( TCMP_TRACK_PAGE_ALL == $result );
103 }
104 public function is_page_specific( $snippet ) {
105 global $tcmp;
106 if ( ! $this->is_mode_script( $snippet ) ) {
107 return false;
108 }
109
110 $result = $tcmp->utils->iget( $snippet, 'trackPage', 0 );
111 return ( TCMP_TRACK_PAGE_SPECIFIC == $result );
112 }
113
114 public function exists( $name ) {
115 $snippets = $this->values();
116 $result = null;
117 $name = strtoupper( $name );
118 if ( isset( $snippets[ $name ] ) ) {
119 $result = $snippets[ $name ];
120 }
121 return $result;
122 }
123
124 //get a code snippet
125 public function get( $id, $new = false ) {
126 global $tcmp;
127
128 $snippet = $tcmp->options->getSnippet( $id );
129 if ( ! $snippet && $new ) {
130 $snippet = array();
131 $snippet['active'] = 1;
132 $snippet['trackMode'] = -1;
133 $snippet['trackPage'] = -1;
134 }
135
136 $snippet = $this->sanitize( $id, $snippet );
137 return $snippet;
138 }
139
140 public function sanitize( $id, $snippet ) {
141 global $tcmp;
142 if ( null == $snippet || ! is_array( $snippet ) ) {
143 return null;
144 }
145
146 $page = 0;
147 if ( isset( $snippet['includeEverywhereActive'] ) ) {
148 $page = ( intval( 1 == $snippet['includeEverywhereActive'] ) ? 0 : 1 );
149 }
150 $defaults = array(
151 'id' => $id,
152 'active' => 0,
153 'name' => '',
154 'code' => '',
155 'order' => 1000,
156 'position' => TCMP_POSITION_HEAD,
157 'trackMode' => TCMP_TRACK_MODE_CODE,
158 'trackPage' => $page,
159 'includeEverywhereActive' => 0,
160 'includeCategoriesActive' => 0,
161 'includeCategories' => array(),
162 'includeTagsActive' => 0,
163 'includeTags' => array(),
164 'exceptCategoriesActive' => 0,
165 'exceptCategories' => array(),
166 'exceptTagsActive' => 0,
167 'exceptTags' => array(),
168 'deviceType' => TCMP_DEVICE_TYPE_ALL,
169 );
170
171 $types = $tcmp->utils->query( TCMP_QUERY_POST_TYPES );
172 foreach ( $types as $v ) {
173 $defaults[ 'includePostsOfType_' . $v['id'] . '_Active' ] = 0;
174 $defaults[ 'includePostsOfType_' . $v['id'] ] = array();
175 $defaults[ 'exceptPostsOfType_' . $v['id'] . '_Active' ] = 0;
176 $defaults[ 'exceptPostsOfType_' . $v['id'] ] = array();
177 }
178
179 $types = $tcmp->utils->query( TCMP_QUERY_CONVERSION_PLUGINS );
180 foreach ( $types as $v ) {
181 //CP stands for ConversionTrackingCode
182 //$defaults['CTC_'.$v['id'].'_Active']=0;
183 $defaults[ 'CTC_' . $v['id'] . '_ProductsIds' ] = array();
184 $defaults[ 'CTC_' . $v['id'] . '_CategoriesIds' ] = array();
185 $defaults[ 'CTC_' . $v['id'] . '_TagsIds' ] = array();
186 }
187 $snippet = $tcmp->utils->parseArgs( $snippet, $defaults );
188
189 foreach ( $snippet as $k => $v ) {
190 if ( stripos( $k, 'active' ) != false ) {
191 $snippet[ $k ] = intval( $v );
192 } elseif ( is_array( $v ) ) {
193 switch ( $k ) {
194 /*
195 case 'includePostsTypes':
196 case 'excludePostsTypes':
197 //keys are string and not number
198 $result=$this->uarray($snippet, $k, FALSE);
199 break;
200 */
201 default:
202 //keys are number
203 $result = $this->uarray( $snippet, $k, true );
204 break;
205 }
206 }
207 }
208 $snippet['name'] = sanitize_text_field( $snippet['name'] );
209 $snippet['code'] = trim( $snippet['code'] );
210 $snippet['position'] = intval( $snippet['position'] );
211 if ( '' == $snippet['trackMode'] ) {
212 $snippet['trackMode'] = TCMP_TRACK_MODE_CODE;
213 } else {
214 $snippet['trackMode'] = intval( $snippet['trackMode'] );
215 }
216 if ( '' == $snippet['trackPage'] ) {
217 $snippet['trackPage'] = $page;
218 } else {
219 $snippet['trackPage'] = intval( $snippet['trackPage'] );
220 }
221
222 $snippet['includeEverywhereActive'] = 0;
223 if ( TCMP_TRACK_PAGE_ALL == $snippet['trackPage'] ) {
224 $snippet['includeEverywhereActive'] = 1;
225 }
226
227 $code = strtolower( $snippet['code'] );
228 $cnt = substr_count( $code, '<iframe' ) + substr_count( $code, '<script' );
229 if ( $cnt <= 0 ) {
230 $cnt = 1;
231 }
232 $snippet['codes_count'] = $cnt;
233 return $snippet;
234 }
235 private function uarray( $snippet, $key, $is_integer = true ) {
236 $array = $snippet[ $key ];
237 if ( ! is_array( $array ) ) {
238 $array = explode( ',', $array );
239 }
240
241 if ( $is_integer ) {
242 for ( $i = 0; $i < count( $array ); $i++ ) {
243 if ( isset( $array[ $i ]) ) {
244 $array[ $i ] = intval( $array[ $i ] );
245 }
246 }
247 }
248
249 $array = array_unique( $array );
250 $snippet[ $key ] = $array;
251 return $snippet;
252 }
253
254 //add or update a snippet (html tracking code)
255 public function put( $id, $snippet ) {
256 global $tcmp;
257
258 if ( '' == $id || intval( $id ) <= 0 ) {
259 //if is a new code create a new unique id
260 $id = $this->get_last_id() + 1;
261 $snippet['id'] = $id;
262 }
263 $snippet = $this->sanitize( $id, $snippet );
264 $tcmp->options->setSnippet( $id, $snippet );
265
266 $keys = $this->keys();
267 if ( is_array( $keys ) && ! in_array( $id, $keys ) ) {
268 $keys[] = $id;
269 $this->keys( $keys );
270 }
271 return $snippet;
272 }
273
274 //remove the id snippet
275 public function remove( $id ) {
276 global $tcmp;
277 $tcmp->options->remove_snippet( $id );
278 $keys = $this->keys();
279 $result = false;
280 if ( is_array( $keys ) && in_array( $id, $keys ) ) {
281 $keys = array_diff( $keys, array( $id ) );
282 $this->keys( $keys );
283 $result = true;
284 }
285 return $result;
286 }
287
288 //verify if match with this snippet
289 private function match_snippet( $post_id, $post_type, $categories_ids, $tags_ids, $prefix, $snippet ) {
290 global $tcmp;
291 if ( ! $this->match_device_type( $snippet ) ) {
292 return false;
293 }
294
295 $include = false;
296 $post_id = intval( $post_id );
297 if ( $post_id > 0 ) {
298 $what = $prefix . 'PostsOfType_' . $post_type;
299 //echo '<textarea cols="100" rows="4">DEBUG:'.'Post Id = '.$post_id .' $what = '. $what .' What = '.$snippet[$what.'_Active'] .' Active = '.$snippet[$what.'_Active']. ' InAllArray = ' . $tcmp->utils->in_all_array( $post_id, $snippet[$what] ) . '</textarea>';
300 if ( isset( $snippet[ $what . '_Active' ] ) && isset( $snippet[ $what ] ) && $snippet[ $what . '_Active' ] && $tcmp->utils->in_all_array( $post_id, $snippet[ $what ] ) ) {
301 $tcmp->log->debug(
302 'MATCH=%s SNIPPET=%s[%s] DUE TO POST=%s OF TYPE=%s IN [%s]',
303 $prefix,
304 $snippet['id'],
305 $snippet['name'],
306 $post_id,
307 $post_type,
308 $snippet[ $what ]
309 );
310 $include = true;
311 //echo '<p>DEBUG: snippet matched</p>';
312 }
313 }
314
315 return $include;
316 }
317
318 public function write_codes( $position ) {
319 global $tcmp;
320
321 $text = '';
322 $position_text = '';
323 switch ( $position ) {
324 case TCMP_POSITION_HEAD:
325 $position_text = 'HEAD';
326 break;
327 case TCMP_POSITION_BODY:
328 $position_text = 'BODY';
329 break;
330 case TCMP_POSITION_FOOTER:
331 $position_text = 'FOOTER';
332 break;
333 case TCMP_POSITION_CONVERSION:
334 $position_text = 'CONVERSION';
335 break;
336 }
337
338 $post = $tcmp->options->getPostShown();
339 $args = array( 'field' => 'code' );
340 $codes = $tcmp->manager->get_codes( $position, $post, $args );
341 if ( is_array( $codes ) && count( $codes ) > 0 ) {
342 $version = TCMP_PLUGIN_VERSION;
343 $text = "\n<!--BEGIN: TRACKING CODE MANAGER (v$version) BY INTELLYWP.COM IN $position_text//-->";
344 foreach ( $codes as $v ) {
345 $text .= "\n$v";
346 }
347 $text .= "\n<!--END: https://wordpress.org/plugins/tracking-code-manager IN $position_text//-->";
348
349 $purchase = $tcmp->options->getEcommercePurchase();
350 if ( false != $purchase && intval( $tcmp->options->getLicenseSiteCount() ) > 0 ) {
351 $text = $this->insert_dynamic_conversion_values( $purchase, $text );
352 }
353 // The snippet body is the site owner's own tracking markup (script
354 // tags, pixels), so escaping it here would defeat the entire purpose
355 // of the plugin. esc_js_code() is the output sink: it runs the text
356 // through wp_kses() against the whitelist in
357 // tcmp_free_wp_kses_tags_attrs.php unless the admin has explicitly
358 // opted out via the "Skip Code Sanitization" setting. That whitelist
359 // permits <script> and onload, so this filters the markup — it does
360 // not constrain what an editor-capable admin can execute. See the
361 // contract note on esc_js_code().
362 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Filtered by esc_js_code() via wp_kses(); see the contract note there.
363 echo $this->esc_js_code( $text );
364 }
365 }
366
367 // Filter a snippet for output.
368 //
369 // What this sink does and does not guarantee: the snippet body is the site
370 // owner's own tracking markup, and the whitelist in
371 // tcmp_free_wp_kses_tags_attrs.php deliberately permits <script>, <iframe>
372 // and onload on every tag. A snippet author therefore has arbitrary
373 // JavaScript by design — that is the plugin's purpose, and it is governed by
374 // the capability gate on the editor (see F-09), not by this method. wp_kses()
375 // here is a well-formedness and whitelist pass over admin-authored markup; it
376 // is not a privilege boundary and must not be described as one.
377 //
378 // The entity decode below is scoped to <script> and <style> bodies, and that
379 // scope is load-bearing. wp_kses() entity-encodes '&' wherever it appears as
380 // text, so a saved "a && b" or a GTM "?id=x&l=dataLayer" comes back as
381 // "&amp;". Inside <script>/<style> the browser does NOT decode entities, so
382 // leaving them encoded breaks the snippet; in an attribute value or in HTML
383 // text the browser DOES decode them, so kses's encoding is both correct and
384 // necessary there. Decoding everywhere — as this method used to — undid the
385 // neutralization kses had just applied, letting a quote escape its attribute
386 // and letting pre-encoded "&lt;script&gt;" text become a live tag.
387 public function esc_js_code( $text ) {
388 global $tcmp;
389 global $tcmp_allowed_html_tags;
390
391 if ( ! $tcmp->options->getSkipCodeSanitization() ) {
392 $text = wp_kses( $text, $tcmp_allowed_html_tags );
393 }
394 return $this->decode_script_entities( $text );
395 }
396
397 // Decode HTML entities inside <script> and <style> element bodies only.
398 //
399 // Runs whether or not wp_kses() was applied above: a snippet saved while
400 // sanitization was enabled is stored already-encoded (Options::recursive_wp_kses()
401 // runs kses over the 'code' field on save), so it still needs decoding when
402 // rendered after "Skip Code Sanitization" is switched on.
403 private function decode_script_entities( $text ) {
404 // Cast before strpos(): passing null is deprecated on PHP 8.1+.
405 $text = (string) $text;
406 if ( false === strpos( $text, '&' ) ) {
407 return $text;
408 }
409 $result = preg_replace_callback(
410 '#(<(script|style)\b[^>]*>)(.*?)(</\2\s*>)#is',
411 array( $this, 'decode_script_entities_callback' ),
412 $text
413 );
414 // preg_replace_callback() returns null on a PCRE limit; fall back to the
415 // still-encoded text rather than dropping the snippet. Failing closed
416 // costs a broken snippet, never a reversed sanitization.
417 return is_null( $result ) ? $text : $result;
418 }
419
420 private function decode_script_entities_callback( $matches ) {
421 // strtr() with a map replaces each source sequence exactly once and does
422 // not rescan its own output, so '&amp;lt;' decodes to '&lt;' and cannot
423 // cascade into '<'.
424 $decoded = strtr(
425 $matches[3],
426 array(
427 '&amp;' => '&',
428 '&lt;' => '<',
429 '&gt;' => '>',
430 '&quot;' => '"',
431 '&#039;' => "'",
432 )
433 );
434 return $matches[1] . $decoded . $matches[4];
435 }
436
437 private function insert_dynamic_conversion_values( $purchase, $text ) {
438 global $tcmp;
439 $purchase->user_id = intval( $purchase->user_id );
440 if ( $purchase->user_id > 0 ) {
441 $user = get_user_by( 'id', $purchase->user_id );
442 if ( ! is_null( $user ) && false != $user && get_class( $user ) == 'WP_User' ) {
443 /* @var $user WP_User */
444 $purchase->email = $user->user_email;
445 $purchase->fullname = $user->user_firstname;
446 if ( '' != $user->user_lastname ) {
447 $purchase->fullname .= ' ' . $user->user_lastname;
448 }
449 }
450 }
451
452 $purchase->total = floatval( $purchase->total );
453 $purchase->amount = floatval( $purchase->amount );
454 $purchase->tax = floatval( $purchase->tax );
455
456 $fields = array(
457 'ORDERID' => $purchase->order_id,
458 'CURRENCY' => $purchase->currency,
459 'FULLNAME' => $purchase->fullname,
460 'EMAIL' => $purchase->email,
461 'PRODUCTS' => $purchase->products,
462 'AMOUNT' => $purchase->amount,
463 'TOTAL' => $purchase->total,
464 'TAX' => $purchase->tax,
465 );
466
467 $sep = '@@';
468 $buffer = '';
469 $previous = 0;
470 $start = strpos( $text, $sep );
471 if ( false == $start ) {
472 $buffer = $text;
473 } else {
474 while ( false != $start ) {
475 $buffer .= $tcmp->utils->substr( $text, $previous, $start );
476 $end = strpos( $text, $sep, $start + strlen( $sep ) );
477 if ( false != $end ) {
478 $code = $tcmp->utils->substr( $text, $start + strlen( $sep ), $end );
479 $code = $tcmp->utils->to_array( $code );
480 if ( 1 == count( $code ) ) {
481 $code[] = '';
482 }
483
484 $v = false;
485 if ( isset( $fields[ $code[0] ] ) ) {
486 $v = $fields[ $code[0] ];
487 }
488 if ( is_null( $v ) || false == $v ) {
489 $v = $code[1];
490 }
491 if ( is_numeric( $v ) ) {
492 $v = floatval( $v );
493 $v = round( $v, 2 );
494 switch ( $code[0] ) {
495 case 'TOTAL':
496 case 'AMOUNT':
497 case 'TAX':
498 $v = number_format( $v, 2, '.', '' );
499 break;
500 default:
501 $v = intval( $v );
502 break;
503 }
504 } elseif ( is_array( $v ) ) {
505 $a = '';
506 foreach ( $v as $t ) {
507 $t = str_replace( ',', '', $t );
508 if ( '' != $a ) {
509 $a .= ',';
510 }
511 $a .= $t;
512 }
513 $v = $a;
514 }
515 $v = $this->esc_conversion_value( $v );
516 $buffer .= $v;
517
518 $previous = $end + strlen( $sep );
519 $start = strpos( $text, $sep, $previous );
520 } else {
521 $buffer .= $tcmp->utils->substr( $text, $start );
522 $previous = false;
523 $start = false;
524 }
525 }
526
527 if ( false != $previous && $previous < strlen( $text ) ) {
528 $code = $tcmp->utils->substr( $text, $previous );
529 $buffer .= $code;
530 }
531 }
532 return $buffer;
533 }
534
535 // Context-encode a value substituted into a conversion snippet. Placeholders
536 // such as @@EMAIL@@ / @@FULLNAME@@ / @@PRODUCTS@@ carry customer-derived text
537 // and are almost always dropped into JavaScript string literals inside the
538 // tag, but an admin-authored template may also place them in an HTML
539 // attribute or in HTML text.
540 //
541 // SUPPORTED CONTEXTS — a JavaScript string literal (template literals
542 // included), a *quoted* HTML attribute value, and HTML text. In those three
543 // the encoding below holds:
544 //
545 // - Quotes are emitted as the JS unicode escapes \u0022 / \u0027 rather
546 // than \" / \'. Inside a JS string these decode back to " and ' so the
547 // value renders correctly, but NO literal quote ever reaches the HTML
548 // parser, so the value cannot close (break out of) a quoted attribute.
549 // - '<', '>' and '/' become \u003C / \u003E / \/ so the value cannot open
550 // a new tag or a </script> break-out (harmless literals in HTML text).
551 // - The backtick becomes \u0060 and '$' becomes \u0024. A template
552 // literal needs both: escaping the backtick alone stops the value
553 // *terminating* the literal, but '${' opens a substitution without
554 // needing one, so a value of '${payload}' would still be evaluated.
555 // \u0024 is not a substitution opener — the lexer recognises '${' in
556 // the raw source, before escape sequences are processed.
557 // - Backslash and CR/LF are escaped so they cannot alter the JS around them.
558 //
559 // '$' is escaped where whitespace and '=' are not, and the difference is
560 // ubiquity rather than principle: every ordinary name and product list
561 // contains spaces, so encoding those would corrupt the common case, whereas
562 // '$' is rare in the values these placeholders carry — the numeric ones are
563 // number_format()'d or intval()'d before they get here. In a JS string it
564 // costs nothing either, since \u0024 decodes straight back to '$'; only the
565 // two HTML contexts render it as the literal text \u0024, which is the same
566 // fidelity-for-safety trade already made for quotes and '<'.
567 //
568 // NOT SUPPORTED — an *unquoted* HTML attribute value, e.g.
569 // `<img src=p.gif alt=@@FULLNAME@@>`. Whitespace and '=' are deliberately left
570 // intact (encoding them would corrupt ordinary names and product lists in the
571 // two contexts where they are just text), so in an unquoted attribute a value
572 // like `foo onload=alert(1)` introduces a new attribute rather than staying a
573 // value. Quote placeholder attributes in the snippet template. Note this is a
574 // weaker boundary than it looks even so: write_codes() applies wp_kses() after
575 // substitution, and that whitelist permits onload — so kses does not backstop
576 // this. Still PRO-gated (license count > 0) and therefore unreachable in the
577 // free tier.
578 //
579 // Numeric values are unaffected. strtr() maps each source character exactly
580 // once, avoiding cascading double-escaping. (See F-10.)
581 private function esc_conversion_value( $v ) {
582 $replacements = array(
583 '\\' => '\\\\',
584 '"' => '\\u0022',
585 "'" => '\\u0027',
586 '`' => '\\u0060',
587 '$' => '\\u0024',
588 '<' => '\\u003C',
589 '>' => '\\u003E',
590 '/' => '\\/',
591 "\r" => '\\r',
592 "\n" => '\\n',
593 );
594 return strtr( (string) $v, $replacements );
595 }
596
597 //return snippets that match with options
598 public function get_conversion_snippets( $options = null ) {
599 global $tcmp;
600
601 $defaults = array(
602 'pluginId' => 0,
603 'categoriesIds' => array(),
604 'productsIds' => array(),
605 'tagsIds' => array(),
606 );
607 $options = $tcmp->utils->parseArgs( $options, $defaults );
608
609 $result = array();
610 $plugin_id = intval( $options['pluginId'] );
611 $values = $this->values();
612
613 foreach ( $values as $snippet ) {
614 $snippet['trackMode'] = intval( $snippet['trackMode'] );
615 if ( $snippet && $snippet['trackMode'] > 0 && $snippet['trackMode'] == $plugin_id ) {
616 $match = false;
617
618 $match = ( $match || $this->match_conversion( $snippet, $plugin_id, 'ProductsIds', $options['productsIds'] ) );
619 $match = ( $match && $this->match_device_type( $snippet ) );
620 if ( ! $match ) {
621 //no selected so..all match! :)
622 if ( 0 == count( $snippet[ 'CTC_' . $plugin_id . '_ProductsIds' ] )
623 && 0 == count( $snippet[ 'CTC_' . $plugin_id . '_CategoriesIds' ] )
624 && 0 == count( $snippet[ 'CTC_' . $plugin_id . '_TagsIds' ] ) ) {
625 $match = true;
626 }
627 }
628
629 if ( $match ) {
630 $result[] = $snippet;
631 }
632 }
633 }
634 return $result;
635 }
636 private function match_conversion( $snippet, $plugin_id, $suffix, $current_ids ) {
637 global $tcmp;
638
639 $settings_ids = 'CTC_' . $plugin_id . '_' . $suffix;
640 if ( isset( $snippet[ $settings_ids ] ) ) {
641 $settings_ids = $snippet[ $settings_ids ];
642 } else {
643 $settings_ids = array();
644 }
645
646 $result = $tcmp->utils->in_all_array( $current_ids, $settings_ids );
647 return $result;
648 }
649
650 //from a post retrieve the html code that is needed to insert into the page code
651 public function get_codes( $position, $post, $args = array() ) {
652 global $tcmp;
653
654 $defaults = array( 'field' => 'code' );
655 $args = $tcmp->utils->parseArgs( $args, $defaults );
656
657 $post_id = 0;
658 $post_type = 'page';
659 $tags_ids = array();
660 $categories_ids = array();
661 if ( $post ) {
662 $post_id = $tcmp->utils->get( $post, 'ID', false );
663 if ( false == $post_id ) {
664 $post_id = $tcmp->utils->get( $post, 'post_ID' );
665 }
666 $post_type = $tcmp->utils->get( $post, 'post_type' );
667
668 $options = array(
669 'orderby' => 'name',
670 'order' => 'ASC',
671 'fields' => 'ids',
672 );
673 if ( isset( $post->ID ) ) {
674 $tags_ids = wp_get_post_tags( $post->ID, $options );
675 $categories_ids = wp_get_post_categories( $post->ID );
676 } else {
677 $tags_ids = array();
678 $categories_ids = array();
679 }
680 }
681
682 $tcmp->options->clearSnippetsWritten();
683 if ( TCMP_POSITION_CONVERSION == $position ) {
684 //write snippets previously appended
685 $ids = $tcmp->options->get_conversion_snippet_ids();
686 if ( false != $ids && count( $ids ) > 0 ) {
687 foreach ( $ids as $id ) {
688 $snippet = $tcmp->manager->get( $id );
689 if ( $snippet ) {
690 $tcmp->options->pushSnippetWritten( $snippet );
691 }
692 }
693 }
694 } else {
695 $snippets = $this->values();
696 foreach ( $snippets as $v ) {
697 if ( ! $v || ( $position > -1 && $v['position'] != $position ) || '' == $v['code'] || ! $v['active'] ) {
698 continue;
699 }
700 if ( TCMP_TRACK_MODE_CODE != $v['trackMode'] ) {
701 continue;
702 }
703 if ( $tcmp->options->hasSnippetWritten( $v ) ) {
704 $tcmp->log->debug( 'SKIPPED SNIPPET=%s[%s] DUE TO ALREADY WRITTEN', $v['id'], $v['name'] );
705 continue;
706 }
707
708 $match = false;
709 if ( ! $match && ( TCMP_TRACK_PAGE_ALL == $v['trackPage'] || $v['includeEverywhereActive'] ) ) {
710 $tcmp->log->debug( 'INCLUDED SNIPPET=%s[%s] DUE TO EVERYWHERE', $v['id'], $v['name'] );
711 $match = true;
712 }
713 if ( ! $match && $post_id > 0 && $this->match_snippet( $post_id, $post_type, $categories_ids, $tags_ids, 'include', $v ) ) {
714 $match = true;
715 }
716
717 if ( $match && $post_id > 0 ) {
718 //echo '<textarea cols="100" rows="4">check for exclude' . print_r($v, true) . '</textarea>';
719 if ( $this->match_snippet( $post_id, $post_type, $categories_ids, $tags_ids, 'except', $v ) ) {
720 //echo '<textarea cols="100" rows="4">exclude ' . print_r($v, true) . '</textarea>';
721 $tcmp->log->debug( 'FOUND AT LEAST ONE EXCEPT TO EXCLUDE SNIPPET=%s [%s]', $v['id'], $v['name'] );
722 $match = false;
723 }
724 }
725
726 if ( $match ) {
727 $tcmp->options->pushSnippetWritten( $v );
728 }
729 }
730 }
731
732 //obtain result as snippets or array of one field (tipically "id")
733 $result = $tcmp->options->getSnippetsWritten();
734 if ( 'all' != $args['field'] ) {
735 $array = array();
736 foreach ( $result as $k => $v ) {
737 $k = $args['field'];
738 if ( isset( $v[ $k ] ) ) {
739 $array[] = $v[ $k ];
740 } else {
741 $tcmp->log->error( 'SNIPPET=%s [%s] WITHOUT FIELD=%s', $v['id'], $v['name'], $k );
742 }
743 }
744 $result = $array;
745 }
746 return $result;
747 }
748
749 //ottiene o salva tutte le chiavi dei tracking code utilizzati ordinati per id
750 public function keys( $keys = null ) {
751 global $tcmp;
752
753 if ( is_array( $keys ) ) {
754 $tcmp->options->setSnippetList( $keys );
755 $result = $keys;
756 } else {
757 $result = $tcmp->options->getSnippetList();
758 }
759
760 if ( ! is_array( $result ) ) {
761 $result = array();
762 } else {
763 sort( $result );
764 }
765 return $result;
766 }
767
768 //ottiene il conteggio attuale dei tracking code
769 public function count() {
770 $result = count( $this->keys() );
771 return $result;
772 }
773 public function codes_count() {
774 $result = 0;
775 $ids = $this->keys();
776 foreach ( $ids as $id ) {
777 $snippet = $this->get( $id );
778 if ( $snippet ) {
779 $result += 1;
780 }
781 }
782 return $result;
783 }
784 public function get_last_id() {
785 $result = 0;
786 $list = $this->keys();
787 foreach ( $list as $v ) {
788 $v = intval( $v );
789 if ( $v > $result ) {
790 $result = $v;
791 }
792 }
793 return $result;
794 }
795
796 public function values() {
797 $keys = $this->keys();
798 $array = array();
799 foreach ( $keys as $k ) {
800 $v = $this->get( $k );
801 $array[] = $v;
802 }
803 usort( $array, array( $this, 'values_compare' ) );
804
805 $result = array();
806 foreach ( $array as $v ) {
807 $id = $v['id'];
808 $result[ $id ] = $v;
809 }
810 return $result;
811 }
812 public function values_compare( $o1, $o2 ) {
813 global $tcmp;
814 $v1 = $tcmp->utils->iget( $o1, 'order', false );
815 $v2 = $tcmp->utils->iget( $o2, 'order', false );
816 $result = ( $v1 - $v2 );
817 if ( 0 == $result ) {
818 $v1 = $tcmp->utils->get( $o1, 'name', false );
819 $v2 = $tcmp->utils->get( $o2, 'name', false );
820 $result = strcasecmp( $v1, $v2 );
821 }
822 return $result;
823 }
824 }
825