| @@ -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.1 | |
| 6 | + * Version: 3.1.0 | |
| 7 | 7 | * Author: Taboola |
| 8 | 8 | */ |
| 9 | 9 | |
| 10 | -define( 'TABOOLA_PLUGIN_VERSION', '3.0.1' ); // 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 | |
| @@ -501,8 +503,31 @@ | ||
| 501 | 503 | implode( "\n", $arr[TABOOLA_CONTENT_FORMAT_SCRIPT] ?? [] ) . |
| 502 | 504 | '</script>', |
| 503 | 505 | ]); |
| 504 | 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 | + | |
| 505 | 530 | // Below-article widget |
| 506 | 531 | // Do the actual logic of choosing where to place the taboola content. |
| 507 | 532 | function embed_taboola_content_location($content, $taboola_content){ |
| 508 | 533 | $do_default = true; |
| @@ -510,13 +535,13 @@ | ||
| 510 | 535 | // tag is placed outside of content in order to allow "read more" functionality. |
| 511 | 536 | if ($this->settings->out_of_content_enabled){ |
| 512 | 537 | |
| 513 | 538 | $scriptWrapper = new JavaScriptWrapper("js_inject.min.js",array( |
| 514 | - "{{HTML}}" => $this->format_taboola_content($taboola_content,TABOOLA_CONTENT_FORMAT_HTML), | |
| 515 | - "{{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))) | |
| 516 | 541 | ); |
| 517 | 542 | $scriptWrapper->appendScript("injectWidgetByMarker('tbmarker');"); |
| 518 | - $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>"; | |
| 519 | 544 | $do_default = false; |
| 520 | 545 | } |
| 521 | 546 | |
| 522 | 547 | // Default for below-article widget - add to the end of the content |
| @@ -542,13 +567,13 @@ | ||
| 542 | 567 | if ($full_indicator == TABOOLA_JS_INDICATOR){ |
| 543 | 568 | |
| 544 | 569 | $xpath = substr($location,strlen(TABOOLA_JS_INDICATOR)); |
| 545 | 570 | $scriptWrapper = new JavaScriptWrapper("js_inject.min.js",array( |
| 546 | - "{{HTML}}" => $this->format_taboola_content_mid($taboola_content_mid,TABOOLA_CONTENT_FORMAT_HTML), | |
| 547 | - "{{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))) | |
| 548 | 573 | ); |
| 549 | 574 | $scriptWrapper->appendScript("injectWidgetByXpath('".$xpath."');"); |
| 550 | - $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>"; | |
| 551 | 576 | |
| 552 | 577 | $do_default = false; |
| 553 | 578 | } |
| 554 | 579 | |
| @@ -554,9 +579,11 @@ | ||
| 554 | 579 | |
| 555 | 580 | // server side selector provided (see simple_html_dom selectors http://simplehtmldom.sourceforge.net/manual.htm) |
| 556 | 581 | // basically it's CSS selectors like in jQuery |
| 557 | 582 | } else{ |
| 558 | - require_once('simple_html_dom.php'); | |
| 583 | + if ( ! class_exists( 'simple_html_dom' ) ) { | |
| 584 | + require_once('simple_html_dom.php'); | |
| 585 | + } | |
| 559 | 586 | |
| 560 | 587 | $html_doc = str_get_html($content); |
| 561 | 588 | $target_location = $html_doc->find($location, ($occurrence) - 1); |
| 562 | 589 | |
| @@ -604,13 +631,13 @@ | ||
| 604 | 631 | if ($full_indicator == TABOOLA_JS_INDICATOR){ |
| 605 | 632 | |
| 606 | 633 | $xpath = substr($location,strlen(TABOOLA_JS_INDICATOR)); |
| 607 | 634 | $scriptWrapper = new JavaScriptWrapper("js_inject.min.js",array( |
| 608 | - "{{HTML}}" => $this->format_taboola_content_home($taboola_content_home,TABOOLA_CONTENT_FORMAT_HTML), | |
| 609 | - "{{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))) | |
| 610 | 637 | ); |
| 611 | 638 | $scriptWrapper->appendScript("injectWidgetByXpath('".$xpath."');"); |
| 612 | - $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>"; | |
| 613 | 640 | |
| 614 | 641 | $do_default = false; |
| 615 | 642 | } |
| 616 | 643 | |
| @@ -615,10 +642,12 @@ | ||
| 615 | 642 | } |
| 616 | 643 | |
| 617 | 644 | // server side selector provided (see simple_html_dom selectors http://simplehtmldom.sourceforge.net/manual.htm) |
| 618 | 645 | // basically it's CSS selectors like in jQuery |
| 619 | - } else{ | |
| 646 | + } else{ | |
| 647 | + if ( ! class_exists( 'simple_html_dom' ) ) { | |
| 620 | 648 | require_once('simple_html_dom.php'); |
| 649 | + } | |
| 621 | 650 | |
| 622 | 651 | $html_doc = str_get_html($content); |
| 623 | 652 | $target_location = $html_doc->find($location,($this->settings->home_location_string_occurrence)-1); |
| 624 | 653 | |
| @@ -645,12 +674,19 @@ | ||
| 645 | 674 | global $current_user; |
| 646 | 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); |
| 647 | 676 | } |
| 648 | 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 | + | |
| 649 | 684 | function admin_taboola_settings(){ |
| 650 | 685 | global $wpdb; |
| 651 | 686 | $settings = $wpdb->get_row("select * from ".$wpdb->prefix."_taboola_settings limit 1"); |
| 652 | 687 | $taboola_errors = array(); |
| 688 | + $taboola_save_error = ''; | |
| 653 | 689 | if($_SERVER['REQUEST_METHOD'] == 'POST'){ |
| 654 | 690 | |
| 655 | 691 | if(trim(strip_tags($_POST['publisher_id'])) == ''){ |
| 656 | 692 | $taboola_errors[] = "Publisher ID"; |
| @@ -732,33 +768,38 @@ | ||
| 732 | 768 | } |
| 733 | 769 | } |
| 734 | 770 | $mid_widgets_json = json_encode($mid_widgets_data); |
| 735 | 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. */ | |
| 736 | 777 | $data = array( |
| 737 | 778 | "publisher_id" => trim($_POST['publisher_id']), |
| 738 | 779 | |
| 739 | - "web_push_enabled" => isset($_POST['web_push_enabled']) ? true : false, | |
| 740 | - "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), | |
| 741 | 782 | |
| 742 | - "first_bc_enabled" => isset($_POST['first_bc_enabled']) ? true : false, | |
| 783 | + "first_bc_enabled" => isset($_POST['first_bc_enabled']) ? 1 : 0, | |
| 743 | 784 | "first_bc_widget_id" => !empty($_POST['first_bc_widget_id']) ? trim($_POST['first_bc_widget_id']) : '', |
| 744 | 785 | "first_bc_placement" => !empty($_POST['first_bc_placement']) ? trim($_POST['first_bc_placement']) : '', |
| 745 | 786 | |
| 746 | - "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, | |
| 747 | 788 | |
| 748 | - "mid_enabled" => isset($_POST['mid_enabled']) ? true : false, | |
| 789 | + "mid_enabled" => isset($_POST['mid_enabled']) ? 1 : 0, | |
| 749 | 790 | "mid_widgets" => $mid_widgets_json, |
| 750 | 791 | |
| 751 | - "home_enabled" => isset($_POST['home_enabled']) ? true : false, | |
| 792 | + "home_enabled" => isset($_POST['home_enabled']) ? 1 : 0, | |
| 752 | 793 | "home_widget_id" => !empty($_POST['home_widget_id']) ? trim($_POST['home_widget_id']) : '', |
| 753 | 794 | "home_placement" => !empty($_POST['home_placement']) ? trim($_POST['home_placement']) : '', |
| 754 | 795 | |
| 755 | - "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), | |
| 756 | 797 | "home_location_string" => !empty($_POST['home_location_string']) ? trim($_POST['home_location_string']) : '', |
| 757 | - "category_enabled" => isset($_POST['category_enabled']) ? true : false, | |
| 798 | + "category_enabled" => isset($_POST['category_enabled']) ? 1 : 0, | |
| 758 | 799 | "category_widget_id" => !empty($_POST['category_widget_id']) ? trim($_POST['category_widget_id']) : '', |
| 759 | 800 | "category_placement" => !empty($_POST['category_placement']) ? trim($_POST['category_placement']) : '', |
| 760 | - "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), | |
| 761 | 802 | "category_location_string" => !empty($_POST['category_location_string']) ? trim($_POST['category_location_string']) : '', |
| 762 | 803 | |
| 763 | 804 | ); |
| 764 | 805 | |
| @@ -769,12 +810,20 @@ | ||
| 769 | 810 | } |
| 770 | 811 | |
| 771 | 812 | if ($is_valid_nonce) { |
| 772 | 813 | if($settings == NULL){ |
| 773 | - $wpdb->insert($this->tbl_taboola_settings, $data); | |
| 814 | + $saved = $wpdb->insert($this->tbl_taboola_settings, $data); | |
| 774 | 815 | } else { |
| 775 | - $wpdb->update($this->tbl_taboola_settings, $data, array('id' => $settings->id)); | |
| 816 | + $saved = $wpdb->update($this->tbl_taboola_settings, $data, array('id' => $settings->id)); | |
| 776 | 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."; | |
| 777 | 826 | } |
| 778 | 827 | } |
| 779 | 828 | $settings = $wpdb->get_row("select * from ".$wpdb->prefix."_taboola_settings limit 1"); |
| 780 | 829 | } |