PluginProbe
Taboola / 3.1.0
Taboola v3.1.0
1.0.4 1.0.5 1.0.6 1.0.8 2.0.1 2.0.2 2.1.0 2.1.1 2.2.2 2.2.3 3.0.0 3.0.1 3.0.2 3.1.0 trunk 1.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.2 1.0.3
← All changes | taboola_widget.php +115 -38 3.0.03.1.0 View file →
@@ -2,13 +2,13 @@
2 2 /**
3 3 * Plugin Name: Taboola
4 4 * Plugin URI: https://developers.taboola.com/web-integrations/docs/wordpress-plugin
5 5 * Description: Taboola
6 - * Version: 3.0.0
6 + * Version: 3.1.0
7 7 * Author: Taboola
8 8 */
9 9
10 -define( 'TABOOLA_PLUGIN_VERSION', '3.0' ); // track every release
10 +define( 'TABOOLA_PLUGIN_VERSION', '3.1.0' ); // track every release
11 11 define( 'TABOOLA_MIN_VER', '3.0' ); // bump only when DB changes
12 12 define( 'TABOOLA_DEBUG_MODE', false );
13 13
14 14 define( 'TABOOLA_OPTION_NAME', 'taboola_plugin_version' );
@@ -21,9 +21,11 @@
21 21 define( 'TABOOLA_CONTENT_FORMAT_HTML', 'html' );
22 22
23 23 include_once 'widget.php';
24 24 require_once 'JavaScriptWrapper.php';
25 -require_once plugin_dir_path( __FILE__ ) . 'simple_html_dom.php'; // ← NEW
25 +if ( ! class_exists( 'simple_html_dom' ) ) {
26 + require_once plugin_dir_path( __FILE__ ) . 'simple_html_dom.php'; // ← NEW
27 +}
26 28
27 29 if ( ! class_exists( 'TaboolaWP' ) ) {
28 30 class TaboolaWP {
29 31
@@ -48,9 +50,9 @@
48 50
49 51 /* ─────────────────────────── constructor ─────────────────────────── */
50 52 public function __construct() {
51 53 global $wpdb;
52 -
54 + //initialize plugin constant
53 55 define( 'TaboolaWP', true );
54 56
55 57 $this->_is_widget_on_page = false;
56 58 $this->_is_head_script_loaded = false;
@@ -81,11 +83,34 @@
81 83 if ( is_admin() ) {
82 84 add_action( 'admin_menu', [ $this, 'admin_generate_menu' ] );
83 85 add_filter( 'plugin_action_links',
84 86 [ $this, 'plugin_action_links' ], 10, 2 );
85 - } elseif ( $this->settings ) {
87 + }
88 +
89 +
90 +
91 +
92 + elseif ( $this->settings ) {
86 93 /* loader & flush */
87 94 add_action( 'wp_head', [ $this, 'taboola_header_loader_inject' ] );
95 + if ( ! empty( $this->settings->publisher_id_push ) ) {
96 + add_action( 'wp_head', [ $this, 'taboola_webpush_loader_js' ] );
97 +
98 + $sw = 'sw.js';
99 + $sw_path = ABSPATH . $sw;
100 +
101 + $content = file_exists( $sw_path ) ? file_get_contents( $sw_path ) : '';
102 +
103 + if ( strpos( $content, $this->tpl_sw ) === false ) {
104 + if ( ! is_writable( ABSPATH ) || ( file_exists( $sw_path ) && ! is_writable( $sw_path ) ) ) {
105 + return $this->notice( $this->msg_sw_error );
106 + }
107 + $content = $this->tpl_sw . PHP_EOL . $content;
108 + if ( file_put_contents( $sw_path, $content ) === false ) {
109 + return $this->notice( $this->msg_sw_error );
110 + }
111 + }
112 + }
88 113 add_action( 'wp_footer', [ $this, 'taboola_footer_loader_js' ] );
89 114
90 115 /* content widgets */
91 116 add_filter( 'the_content', [ $this, 'load_taboola_content' ] );
@@ -245,15 +270,21 @@
245 270 return $page_type;
246 271 }
247 272
248 273 // return the head loader script
249 - function taboola_header_loader_js() {
250 - $head_string = "";
274 + function taboola_header_loader_js() {
275 + if ($this->is_widget_on_page()){
276 +
277 + // New logic to get all mid-article locations
278 + $mid_locations_string = '';
279 + if (!empty($this->settings->mid_widgets)) {
280 + $mid_widgets_array = json_decode($this->settings->mid_widgets, true);
281 + if (is_array($mid_widgets_array)) {
282 + $locations = array_column($mid_widgets_array, 'location_string');
283 + $mid_locations_string = implode(', ', $locations);
284 + }
285 + }
251 286
252 - // Only adding the loader if a widget is going to be placed on the page.
253 - if ($this->is_widget_on_page()){
254 - // PC - since these params will be inserted in 'loaderInjectionScript.js' via search and replace,
255 - // double brackets are used to ensure that each key is a unique string.
256 287 $stringParams = array(
257 288 '{{PUBLISHER_ID}}' => $this->settings->publisher_id,
258 289 '{{PAGE_TYPE}}' => $this->get_page_type(),
259 290 '{{WORDPRESS_VERSION}}' => get_bloginfo('version'),
@@ -258,19 +289,18 @@
258 289 '{{PAGE_TYPE}}' => $this->get_page_type(),
259 290 '{{WORDPRESS_VERSION}}' => get_bloginfo('version'),
260 291 '{{PHP_VERSION}}' => phpversion(),
261 292 '{{PLUGIN_VERSION}}' => TABOOLA_PLUGIN_VERSION,
262 - '{{LOC_MID}}' => $this->settings->mid_location_string,
263 - '{{LOC_HOME}}' => $this->settings->home_location_string
293 + '{{LOC_MID}}' => $mid_locations_string,
294 + '{{LOC_HOME}}' => $this->settings->home_location_string ?? ''
264 295 );
265 296
266 297 $scriptWrapper = new JavaScriptWrapper("loaderInjectionScript.js",$stringParams);
267 - $head_string = $scriptWrapper->getScriptMarkupString();
298 + return $scriptWrapper->getScriptMarkupString();
268 299 }
269 - return $head_string;
300 + return "";
270 301 }
271 302
272 -
273 303 // This function is used for the hook action, injects the header content to the <head> tag.
274 304 function taboola_header_loader_inject(){
275 305 echo $this->taboola_header_loader_js();
276 306 }
@@ -473,8 +503,31 @@
473 503 implode( "\n", $arr[TABOOLA_CONTENT_FORMAT_SCRIPT] ?? [] ) .
474 504 '</script>',
475 505 ]);
476 506 }
507 + /* ------------------------------------------------------------------
508 + * wpautop() hardening for anything injected through the_content.
509 + *
510 + * wpautop() pads block-level tags with blank lines, then splits the
511 + * content on blank lines and wraps each chunk in <p>. Its <script>
512 + * protection only runs after that split, so two things tear an injected
513 + * <script> apart and spill its tail onto the page as reader-visible text:
514 + * 1. a blank line anywhere in the script body, and
515 + * 2. a block-level tag (e.g. <div>) sitting inside a JS string literal.
516 + * Both have to be neutralised; fixing only one still breaks.
517 + * ------------------------------------------------------------------*/
518 +
519 + // Hide markup from wpautop's block-tag scan. \x3C decodes back to "<"
520 + // when the surrounding JS string literal is evaluated.
521 + private function js_escape_markup($markup){
522 + return str_replace('<', '\x3C', (string) $markup);
523 + }
524 +
525 + // Collapse blank lines so wpautop has no paragraph boundary to split on.
526 + private function wpautop_safe_script($script){
527 + return preg_replace('/(\R[ \t]*){2,}/', "\n", (string) $script);
528 + }
529 +
477 530 // Below-article widget
478 531 // Do the actual logic of choosing where to place the taboola content.
479 532 function embed_taboola_content_location($content, $taboola_content){
480 533 $do_default = true;
@@ -482,13 +535,13 @@
482 535 // tag is placed outside of content in order to allow "read more" functionality.
483 536 if ($this->settings->out_of_content_enabled){
484 537
485 538 $scriptWrapper = new JavaScriptWrapper("js_inject.min.js",array(
486 - "{{HTML}}" => $this->format_taboola_content($taboola_content,TABOOLA_CONTENT_FORMAT_HTML),
487 - "{{SCRIPT}}" => $this->format_taboola_content($taboola_content,TABOOLA_CONTENT_FORMAT_SCRIPT))
539 + "{{HTML}}" => $this->js_escape_markup($this->format_taboola_content($taboola_content,TABOOLA_CONTENT_FORMAT_HTML)),
540 + "{{SCRIPT}}" => $this->js_escape_markup($this->format_taboola_content($taboola_content,TABOOLA_CONTENT_FORMAT_SCRIPT)))
488 541 );
489 542 $scriptWrapper->appendScript("injectWidgetByMarker('tbmarker');");
490 - $content = $content."<span id='tbmarker'></span><script type='text/javascript'>".$scriptWrapper."</script>";
543 + $content = $content."<span id='tbmarker'></span><script type='text/javascript'>".$this->wpautop_safe_script($scriptWrapper)."</script>";
491 544 $do_default = false;
492 545 }
493 546
494 547 // Default for below-article widget - add to the end of the content
@@ -514,13 +567,13 @@
514 567 if ($full_indicator == TABOOLA_JS_INDICATOR){
515 568
516 569 $xpath = substr($location,strlen(TABOOLA_JS_INDICATOR));
517 570 $scriptWrapper = new JavaScriptWrapper("js_inject.min.js",array(
518 - "{{HTML}}" => $this->format_taboola_content_mid($taboola_content_mid,TABOOLA_CONTENT_FORMAT_HTML),
519 - "{{SCRIPT}}" => $this->format_taboola_content_mid($taboola_content_mid,TABOOLA_CONTENT_FORMAT_SCRIPT))
571 + "{{HTML}}" => $this->js_escape_markup($this->format_taboola_content_mid($taboola_content_mid,TABOOLA_CONTENT_FORMAT_HTML)),
572 + "{{SCRIPT}}" => $this->js_escape_markup($this->format_taboola_content_mid($taboola_content_mid,TABOOLA_CONTENT_FORMAT_SCRIPT)))
520 573 );
521 574 $scriptWrapper->appendScript("injectWidgetByXpath('".$xpath."');");
522 - $content = $content."<span id='tbdefault'></span><script type='text/javascript'>".$scriptWrapper."</script>";
575 + $content = $content."<span id='tbdefault'></span><script type='text/javascript'>".$this->wpautop_safe_script($scriptWrapper)."</script>";
523 576
524 577 $do_default = false;
525 578 }
526 579
@@ -526,9 +579,11 @@
526 579
527 580 // server side selector provided (see simple_html_dom selectors http://simplehtmldom.sourceforge.net/manual.htm)
528 581 // basically it's CSS selectors like in jQuery
529 582 } else{
530 - require_once('simple_html_dom.php');
583 + if ( ! class_exists( 'simple_html_dom' ) ) {
584 + require_once('simple_html_dom.php');
585 + }
531 586
532 587 $html_doc = str_get_html($content);
533 588 $target_location = $html_doc->find($location, ($occurrence) - 1);
534 589
@@ -576,13 +631,13 @@
576 631 if ($full_indicator == TABOOLA_JS_INDICATOR){
577 632
578 633 $xpath = substr($location,strlen(TABOOLA_JS_INDICATOR));
579 634 $scriptWrapper = new JavaScriptWrapper("js_inject.min.js",array(
580 - "{{HTML}}" => $this->format_taboola_content_home($taboola_content_home,TABOOLA_CONTENT_FORMAT_HTML),
581 - "{{SCRIPT}}" => $this->format_taboola_content_home($taboola_content_home,TABOOLA_CONTENT_FORMAT_SCRIPT))
635 + "{{HTML}}" => $this->js_escape_markup($this->format_taboola_content_home($taboola_content_home,TABOOLA_CONTENT_FORMAT_HTML)),
636 + "{{SCRIPT}}" => $this->js_escape_markup($this->format_taboola_content_home($taboola_content_home,TABOOLA_CONTENT_FORMAT_SCRIPT)))
582 637 );
583 638 $scriptWrapper->appendScript("injectWidgetByXpath('".$xpath."');");
584 - $content = $content."<span id='tbdefault'></span><script type='text/javascript'>".$scriptWrapper."</script>";
639 + $content = $content."<span id='tbdefault'></span><script type='text/javascript'>".$this->wpautop_safe_script($scriptWrapper)."</script>";
585 640
586 641 $do_default = false;
587 642 }
588 643
@@ -587,10 +642,12 @@
587 642 }
588 643
589 644 // server side selector provided (see simple_html_dom selectors http://simplehtmldom.sourceforge.net/manual.htm)
590 645 // basically it's CSS selectors like in jQuery
591 - } else{
646 + } else{
647 + if ( ! class_exists( 'simple_html_dom' ) ) {
592 648 require_once('simple_html_dom.php');
649 + }
593 650
594 651 $html_doc = str_get_html($content);
595 652 $target_location = $html_doc->find($location,($this->settings->home_location_string_occurrence)-1);
596 653
@@ -617,12 +674,19 @@
617 674 global $current_user;
618 675 add_menu_page(__('Taboola','taboola_widget'), __('Taboola','taboola_widget'), 'manage_options', 'taboola_widget', array(&$this, 'admin_taboola_settings'), $this->plugin_url.'img/taboola_icon.png', 110);
619 676 }
620 677
678 + // Empty numeric inputs must reach MySQL as NULL, not '', or strict mode
679 + // rejects the whole row.
680 + private function nullable_int($value){
681 + return (isset($value) && trim((string) $value) !== '') ? (int) $value : null;
682 + }
683 +
621 684 function admin_taboola_settings(){
622 685 global $wpdb;
623 686 $settings = $wpdb->get_row("select * from ".$wpdb->prefix."_taboola_settings limit 1");
624 687 $taboola_errors = array();
688 + $taboola_save_error = '';
625 689 if($_SERVER['REQUEST_METHOD'] == 'POST'){
626 690
627 691 if(trim(strip_tags($_POST['publisher_id'])) == ''){
628 692 $taboola_errors[] = "Publisher ID";
@@ -704,33 +768,38 @@
704 768 }
705 769 }
706 770 $mid_widgets_json = json_encode($mid_widgets_data);
707 771
772 + /* $wpdb formats every value as %s unless told otherwise, so a PHP
773 + false or an empty string reaches MySQL as ''. Under
774 + STRICT_TRANS_TABLES (the MySQL 8 default) '' is rejected for the
775 + TINYINT/INT columns and the whole write is refused. Send real
776 + integers for the flags and NULL for empty numeric fields. */
708 777 $data = array(
709 778 "publisher_id" => trim($_POST['publisher_id']),
710 779
711 - "web_push_enabled" => isset($_POST['web_push_enabled']) ? true : false,
712 - "publisher_id_push" => !empty($_POST['publisher_id_push']) ? trim($_POST['publisher_id_push']) : '',
780 + "web_push_enabled" => isset($_POST['web_push_enabled']) ? 1 : 0,
781 + "publisher_id_push" => $this->nullable_int($_POST['publisher_id_push'] ?? null),
713 782
714 - "first_bc_enabled" => isset($_POST['first_bc_enabled']) ? true : false,
783 + "first_bc_enabled" => isset($_POST['first_bc_enabled']) ? 1 : 0,
715 784 "first_bc_widget_id" => !empty($_POST['first_bc_widget_id']) ? trim($_POST['first_bc_widget_id']) : '',
716 785 "first_bc_placement" => !empty($_POST['first_bc_placement']) ? trim($_POST['first_bc_placement']) : '',
717 786
718 - "out_of_content_enabled" => isset($_POST['out_of_content_enabled']) ? true : false,
787 + "out_of_content_enabled" => isset($_POST['out_of_content_enabled']) ? 1 : 0,
719 788
720 - "mid_enabled" => isset($_POST['mid_enabled']) ? true : false,
789 + "mid_enabled" => isset($_POST['mid_enabled']) ? 1 : 0,
721 790 "mid_widgets" => $mid_widgets_json,
722 791
723 - "home_enabled" => isset($_POST['home_enabled']) ? true : false,
792 + "home_enabled" => isset($_POST['home_enabled']) ? 1 : 0,
724 793 "home_widget_id" => !empty($_POST['home_widget_id']) ? trim($_POST['home_widget_id']) : '',
725 794 "home_placement" => !empty($_POST['home_placement']) ? trim($_POST['home_placement']) : '',
726 795
727 - "home_location_string_occurrence" => !empty($_POST['home_location_string_occurrence']) ? $_POST['home_location_string_occurrence'] : '',
796 + "home_location_string_occurrence" => $this->nullable_int($_POST['home_location_string_occurrence'] ?? null),
728 797 "home_location_string" => !empty($_POST['home_location_string']) ? trim($_POST['home_location_string']) : '',
729 - "category_enabled" => isset($_POST['category_enabled']) ? true : false,
798 + "category_enabled" => isset($_POST['category_enabled']) ? 1 : 0,
730 799 "category_widget_id" => !empty($_POST['category_widget_id']) ? trim($_POST['category_widget_id']) : '',
731 800 "category_placement" => !empty($_POST['category_placement']) ? trim($_POST['category_placement']) : '',
732 - "category_location_string_occurrence" => !empty($_POST['category_location_string_occurrence']) ? $_POST['category_location_string_occurrence'] : '',
801 + "category_location_string_occurrence" => $this->nullable_int($_POST['category_location_string_occurrence'] ?? null),
733 802 "category_location_string" => !empty($_POST['category_location_string']) ? trim($_POST['category_location_string']) : '',
734 803
735 804 );
736 805
@@ -741,12 +810,20 @@
741 810 }
742 811
743 812 if ($is_valid_nonce) {
744 813 if($settings == NULL){
745 - $wpdb->insert($this->tbl_taboola_settings, $data);
814 + $saved = $wpdb->insert($this->tbl_taboola_settings, $data);
746 815 } else {
747 - $wpdb->update($this->tbl_taboola_settings, $data, array('id' => $settings->id));
816 + $saved = $wpdb->update($this->tbl_taboola_settings, $data, array('id' => $settings->id));
748 817 }
818 +
819 + // update() returns 0 when nothing changed; only false is a failure.
820 + if ($saved === false) {
821 + $taboola_save_error = "The database rejected the write, so your changes were not saved: "
822 + . ($wpdb->last_error !== '' ? $wpdb->last_error : 'unknown database error');
823 + }
824 + } else {
825 + $taboola_save_error = "Security check failed - the settings page had been open too long. Reload it and apply your changes again.";
749 826 }
750 827 }
751 828 $settings = $wpdb->get_row("select * from ".$wpdb->prefix."_taboola_settings limit 1");
752 829 }