WooCommerce Wishlist Plugin - Version 1.47.0

Version Description

Release Date - 06 June 2022

  • Updated integration with WooCommerce Blocks
  • Fixed custom AJAX endpoint issue for customized WordPress setups
  • Fixed integration issue with XforWooCommerce plugin
Download this release

Release Info

Developer templateinvaders
Plugin Icon 128x128 WooCommerce Wishlist Plugin
Version 1.47.0
Comparing to
See all releases

Code changes from version 1.46.0 to 1.47.0

Files changed (49) hide show
  1. assets/css/admin-form-rtl.min.css +1 -1
  2. assets/css/admin-form.min.css +1 -1
  3. assets/css/admin-rtl.min.css +1 -1
  4. assets/css/admin-setup-rtl.min.css +1 -1
  5. assets/css/admin-setup.min.css +1 -1
  6. assets/css/admin.min.css +1 -1
  7. assets/css/public-rtl.min.css +1 -1
  8. assets/css/public.min.css +1 -1
  9. assets/css/theme-rtl.min.css +1 -1
  10. assets/css/theme.min.css +1 -1
  11. assets/css/webfont-rtl.min.css +1 -1
  12. assets/css/webfont.min.css +1 -1
  13. assets/js/admin.min.js +1 -1
  14. assets/js/public.js +1 -1
  15. assets/js/public.min.js +2 -2
  16. includes/api/ajax.php +24 -2
  17. languages/ti-woocommerce-wishlist.pot +7 -7
  18. public/addtowishlist.class.php +19 -15
  19. readme.txt +8 -1
  20. templates/ti-addtowishlist.php +2 -2
  21. ti-woocommerce-wishlist.php +87 -93
  22. vendor/autoload.php +7 -0
  23. vendor/composer/ClassLoader.php +481 -0
  24. vendor/composer/InstalledVersions.php +337 -0
  25. vendor/composer/LICENSE +21 -0
  26. vendor/composer/autoload_classmap.php +10 -0
  27. vendor/composer/autoload_namespaces.php +9 -0
  28. vendor/composer/autoload_psr4.php +10 -0
  29. vendor/composer/autoload_real.php +57 -0
  30. vendor/composer/autoload_static.php +36 -0
  31. vendor/composer/installed.json +61 -0
  32. vendor/composer/installed.php +32 -0
  33. vendor/composer/platform_check.php +26 -0
  34. vendor/imangazaliev/didom/CHANGELOG.md +211 -0
  35. vendor/imangazaliev/didom/LICENSE +19 -0
  36. vendor/imangazaliev/didom/README-RU.md +833 -0
  37. vendor/imangazaliev/didom/README.md +675 -0
  38. vendor/imangazaliev/didom/composer.json +37 -0
  39. vendor/imangazaliev/didom/composer.lock +1049 -0
  40. vendor/imangazaliev/didom/src/DiDom/ClassAttribute.php +267 -0
  41. vendor/imangazaliev/didom/src/DiDom/Document.php +744 -0
  42. vendor/imangazaliev/didom/src/DiDom/DocumentFragment.php +34 -0
  43. vendor/imangazaliev/didom/src/DiDom/Element.php +401 -0
  44. vendor/imangazaliev/didom/src/DiDom/Encoder.php +58 -0
  45. vendor/imangazaliev/didom/src/DiDom/Errors.php +46 -0
  46. vendor/imangazaliev/didom/src/DiDom/Exceptions/InvalidSelectorException.php +10 -0
  47. vendor/imangazaliev/didom/src/DiDom/Node.php +1185 -0
  48. vendor/imangazaliev/didom/src/DiDom/Query.php +567 -0
  49. vendor/imangazaliev/didom/src/DiDom/StyleAttribute.php +322 -0
assets/css/admin-form-rtl.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tiwlform-number-container{display:inline-block;margin:2px;position:relative;vertical-align:middle}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tiwlform-number-container{display:inline-block;margin:2px;position:relative;vertical-align:middle}
assets/css/admin-form.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tiwlform-number-container{display:inline-block;margin:2px;position:relative;vertical-align:middle}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tiwlform-number-container{display:inline-block;margin:2px;position:relative;vertical-align:middle}
assets/css/admin-rtl.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  *{-webkit-box-sizing:border-box;box-sizing:border-box}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  *{-webkit-box-sizing:border-box;box-sizing:border-box}
assets/css/admin-setup-rtl.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  html{background:#f6f3ed}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  html{background:#f6f3ed}
assets/css/admin-setup.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  html{background:#f6f3ed}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  html{background:#f6f3ed}
assets/css/admin.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  *{-webkit-box-sizing:border-box;box-sizing:border-box}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  *{-webkit-box-sizing:border-box;box-sizing:border-box}
assets/css/public-rtl.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tinv-wishlist form,.tinv-wishlist p:last-child,.tinv-wishlist table{margin-bottom:0}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tinv-wishlist form,.tinv-wishlist p:last-child,.tinv-wishlist table{margin-bottom:0}
assets/css/public.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tinv-wishlist form,.tinv-wishlist p:last-child,.tinv-wishlist table{margin-bottom:0}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tinv-wishlist form,.tinv-wishlist p:last-child,.tinv-wishlist table{margin-bottom:0}
assets/css/theme-rtl.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tinv-wishlist,.tinv-wishlist input,.tinv-wishlist select,.tinv-wishlist textarea,.tinv-wishlist button,.tinv-wishlist input[type=button],.tinv-wishlist input[type=reset],.tinv-wishlist input[type=submit]{font-family:Georgia,serif;font-size:14px;font-weight:400;text-transform:none;line-height:1.75}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tinv-wishlist,.tinv-wishlist input,.tinv-wishlist select,.tinv-wishlist textarea,.tinv-wishlist button,.tinv-wishlist input[type=button],.tinv-wishlist input[type=reset],.tinv-wishlist input[type=submit]{font-family:Georgia,serif;font-size:14px;font-weight:400;text-transform:none;line-height:1.75}
assets/css/theme.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tinv-wishlist,.tinv-wishlist input,.tinv-wishlist select,.tinv-wishlist textarea,.tinv-wishlist button,.tinv-wishlist input[type=button],.tinv-wishlist input[type=reset],.tinv-wishlist input[type=submit]{font-family:Georgia,serif;font-size:14px;font-weight:400;text-transform:none;line-height:1.75}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  .tinv-wishlist,.tinv-wishlist input,.tinv-wishlist select,.tinv-wishlist textarea,.tinv-wishlist button,.tinv-wishlist input[type=button],.tinv-wishlist input[type=reset],.tinv-wishlist input[type=submit]{font-family:Georgia,serif;font-size:14px;font-weight:400;text-transform:none;line-height:1.75}
assets/css/webfont-rtl.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  @font-face{font-family:"tinvwl-webfont";font-display:block;src:url("../fonts/tinvwl-webfont.eot?ver=xu2uyi");src:url("../fonts/tinvwl-webfont.eot?ver=xu2uyi#iefix") format("embedded-opentype"),url("../fonts/tinvwl-webfont.woff2?ver=xu2uyi") format("woff2"),url("../fonts/tinvwl-webfont.woff?ver=xu2uyi") format("woff"),url("../fonts/tinvwl-webfont.ttf?ver=xu2uyi") format("truetype"),url("../fonts/tinvwl-webfont.svg?ver=xu2uyi#tinvwl-webfont") format("svg");font-weight:normal;font-style:normal}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  @font-face{font-family:"tinvwl-webfont";font-display:block;src:url("../fonts/tinvwl-webfont.eot?ver=xu2uyi");src:url("../fonts/tinvwl-webfont.eot?ver=xu2uyi#iefix") format("embedded-opentype"),url("../fonts/tinvwl-webfont.woff2?ver=xu2uyi") format("woff2"),url("../fonts/tinvwl-webfont.woff?ver=xu2uyi") format("woff"),url("../fonts/tinvwl-webfont.ttf?ver=xu2uyi") format("truetype"),url("../fonts/tinvwl-webfont.svg?ver=xu2uyi#tinvwl-webfont") format("svg");font-weight:normal;font-style:normal}
assets/css/webfont.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  @font-face{font-family:"tinvwl-webfont";font-display:block;src:url("../fonts/tinvwl-webfont.eot?ver=xu2uyi");src:url("../fonts/tinvwl-webfont.eot?ver=xu2uyi#iefix") format("embedded-opentype"),url("../fonts/tinvwl-webfont.woff2?ver=xu2uyi") format("woff2"),url("../fonts/tinvwl-webfont.woff?ver=xu2uyi") format("woff"),url("../fonts/tinvwl-webfont.ttf?ver=xu2uyi") format("truetype"),url("../fonts/tinvwl-webfont.svg?ver=xu2uyi#tinvwl-webfont") format("svg");font-weight:normal;font-style:normal}
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  @font-face{font-family:"tinvwl-webfont";font-display:block;src:url("../fonts/tinvwl-webfont.eot?ver=xu2uyi");src:url("../fonts/tinvwl-webfont.eot?ver=xu2uyi#iefix") format("embedded-opentype"),url("../fonts/tinvwl-webfont.woff2?ver=xu2uyi") format("woff2"),url("../fonts/tinvwl-webfont.woff?ver=xu2uyi") format("woff"),url("../fonts/tinvwl-webfont.ttf?ver=xu2uyi") format("truetype"),url("../fonts/tinvwl-webfont.svg?ver=xu2uyi#tinvwl-webfont") format("svg");font-weight:normal;font-style:normal}
assets/js/admin.min.js CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  "use strict";function TInvWL($,h){this.pf="tinvwl",this.g="_",this.ho=h||!1,this.n="TInvWL",this.aj_act=function(t){return[this.pf,t].join(this.g)},this._csel=function(t,n){n=n||".";return"{0}{1}{2}".format(n,this.pf,t)},this._tm=function(t){t=$("script#{0}[type='text/template']".format(t));return t.length?t.html():""},this.formElm=function(){var e,n;$(this._csel("-form-onoff")).tiwl_onoff(),$("input[type=checkbox][tiwl-show], input[type=checkbox][tiwl-hide]").tiwl_onoffblock(),$("[tiwl-value][tiwl-show], [tiwl-value][tiwl-hide]").tiwl_byvalueblock(),void 0!==$.fn.wpColorPicker&&(e=function(t){t=t.substring(1),t=parseInt(t,16);return.2126*(t>>16&255)+.7152*(t>>8&255)+.0722*(t>>0&255)},n=this._csel("-form-color"),$(n).each(function(){var n=$(this),t=$(this).closest(".tinvwl-color-picker"),i=t.find(".tinvwl-eyedropper");n.css("background-color",n.val()),175<e(n.val())&&n.css("color","#000000"),n.iris({mode:"hsv",target:$(this).parent().parent(),change:function(t,n){175<e(n.color.toCSS())?$(this).css("color","#000000"):$(this).css("color",""),$(this).css("background-color",n.color.toCSS())}}),t.on("click",".iris-square-value",function(t){t.preventDefault(),n.iris("toggle")}),i.on("click",function(t){t.preventDefault(),n.iris("show")}),n.on("focusin",function(){n.iris("show")})}),$(document).on("click",function(t){($(t.target).is(n+", .iris-picker, .iris-picker-inner, .iris-slider-offset, .tinvwl-eyedropper, .tinvwl-eyedropper .ftinvwl-eyedropper")?$(n).not($(t.target).closest(".tinvwl-color-picker").find(n)):$(n)).iris("hide")}))},this.wizard_page=function(t){$(t).find("select").change(this._wizard_page_ch),this.wizard_page_ch($(t).find("select"))},this.wizard_page_ch=function(t){var n=(t=$(t)).parent(this._csel("-page-select")),i=n.find("input[type=hidden]").val(),e=n.find(this._csel("-error-icon")),o=n.find(this._csel("-error-desc"));""!==t.val()?(n.removeClass("tinvwl-error"),e.hide(),o.hide()):0==i&&(n.addClass("tinvwl-error"),e.show(),o.show())},this.pageElm=function(){$(this._csel("-header","div.")).prependTo("#wpbody-content"),$(this._csel("-page-select")).each(this._wizard_page),$(".bulkactions [type=submit]").each(this._control_bulkactions),$(".action-search [type=submit]").each(this._control_search)},this.control_bulkactions=function(t){$(t).on("click",this._control_bulkactions_ck)},this.control_bulkactions_ck=function(t,n){var i=(t=$(t)).parents(".bulkactions").eq(0).find("[name=action]"),t=t.parents("form").eq(0);i&&("-1"!==i.val()&&t.find("input[type=checkbox]:checked").length||n.preventDefault())},this.control_search=function(t){$(t).on("click",this._control_search_ck)},this.control_search_ck=function(t,n){t=(t=$(t)).parents(".action-search").eq(0).find("[name=s]");t&&""===t.val()&&n.preventDefault()},this.Run=function(){this.formElm(),this.pageElm()},this.cg=function(){var t,n=this.n;this.ho&&(n=n+(t=new Date).getFullYear()+t.getMonth()+t.getDate()),window[n]=this},this.cg(),String.prototype.format||(String.prototype.format=function(){var i=arguments;return this.replace(/{(\d+)}/g,function(t,n){return void 0!==i[n]?i[n]:t})});var o=this,n=o.n,ho=o.ho,c=ho?"t=new Date(),n=n+t.getFullYear()+t.getMonth()+t.getDate(),":"",i;for(i in o)"function"!=typeof o[i]||"_"===i[0]||o.hasOwnProperty("_"+i)||eval("o._"+i+"=function(a,b,c,d){var n='"+n+"',"+c+"o=window[n]||null;if (o) {return o."+i+"(this,a,b,c,d);};};")}!function(s){s.fn.tiwl_onoff=function(t){var o=s.extend(!0,{},{value:{on:"",off:""},class:"tiwlform-onoff",wrap:"container",button:"button"},t);return s(this).each(function(){var n=s(this),t=s("<div>").attr({class:o.class+"-"+o.button}),i=o.class+"-"+o.wrap,e=s("<div>").attr({id:n.attr("id")+"_"+o.wrap,class:i});return n.is("input")&&(e.attr("class",e.attr("class")+" "+n.attr("class")),n.is(":disabled")&&(e.toggleClass("disabled",n.is(":disabled")),n.prop("disabled",!1)),e.toggleClass("checked",n.is(":checked")),n.hide().removeAttr("class").wrap(e).before(t),e=n.parent(),n.on("change",function(t){if(e.hasClass("disabled"))return t.preventDefault();e.toggleClass("checked",s(this).is(":checked"))}),e.on("click",function(t){if(e.hasClass("disabled"))return t.preventDefault();n.is(":enabled")&&e.hasClass("checked")===n.is(":checked")&&n.click()})),n})},s.fn.tiwl_onoffblock=function(t){var c=s.extend(!0,{},{onEachElm:function(){},isChecked:function(){return s(this).is(":checked")}},t);return s(this).each(function(){function t(){function t(t,i){t=t.match(/[\w\d-\>\.\#\:\=\[\]]+/gim)||[],s.each(t,function(t,n){c.onEachElm.call(s(n).toggle(i))})}var n=s(this),i=n.attr("tiwl-show"),e=n.attr("tiwl-hide"),o=c.isChecked.call(n);return"string"==typeof i&&t(i,o),"string"==typeof e&&t(e,!o),n}var n=s(this);return n.is("input")&&"checkbox"==n.attr("type")?(s(this).on("change",t),t.call(n)):n})},s.fn.tiwl_byvalueblock=function(t){var i=s.extend(!0,{},{onEachElm:function(){},onClick:function(){return s(this).val()==s(this).attr("tiwl-value")}},t);return s(this).each(function(){function t(e){function t(t,i){t=t.match(/[\w\d-\>\.\#\:\=\[\]]+/gim)||[],s.each(t,function(t,n){e.onEachElm.call(s(n).toggle(i))})}var n=s(this),i=n.attr("tiwl-show"),o=n.attr("tiwl-hide"),c=e.onClick.call(n);return"string"==typeof i&&t(i,c),"string"==typeof o&&t(o,!c),n}var n=s(this);return n.is("input")||n.is("select")?(s(this).on("change",function(){t.call(this,i)}),t.call(n,i)):n})};var n=new TInvWL(s);s(document).ready(function(){var t;n.Run(),jQuery('input[name="general-show_notice"]').change(function(){var t=!jQuery(this).is(":checked"),n=jQuery('input[name="general-redirect_require_login"]');t&&!n.is(":checked")&&n.click().trigger("change"),n.closest(".tiwlform-onoff-container").toggleClass("disabled",t)}).change(),s(".tablenav").each(function(){var t=s(this);s.trim(t.find(".alignleft").html()).length||t.find(".alignleft").remove(),s.trim(t.find(".alignright").html()).length&&!t.find(".tablenav-pages").hasClass("one-page")||(t.find(".alignright").remove(),t.find(".tinv-wishlist-clear").remove()),s.trim(t.html()).length||t.remove()}),s(".tablenav .bulkactions select").addClass("tinvwl-select grey").wrap('<span class="tinvwl-select-wrap">').parent().append('<span class="tinvwl-caret"><span></span></span>'),s(".tablenav .bulkactions .button.action, .tablenav #search-submit").removeClass("button").addClass("tinvwl-btn grey"),s(".tinvwl-modal-btn").on("click",function(){s(this).next(".tinvwl-modal").addClass("tinvwl-modal-open")}),s(".tinvwl-overlay, .tinvwl-close-modal, .tinvwl_button_close").on("click",function(t){t.preventDefault(),s(this).parents(".tinvwl-modal:first").removeClass("tinvwl-modal-open")}),void 0!==s.fn.popover&&((t=s(".tinvwl-help")).popover({content:function(){return s(this).closest(".tinvwl-info-wrap").find(".tinvwl-info-desc").html()}}),t.on("click",function(){s(this).popover("toggle")}),t.on("focusout",function(){s(this).popover("hide")}),s(window).on("resize",function(){t.popover("hide")})),s("body").on("click",".tinvwl-confirm-reset",function(t){t.preventDefault(),confirm(tinvwl_comfirm.text_comfirm_reset)&&s(this).removeClass("tinvwl-confirm-reset").trigger("click")})}),s(document).on("click",".tinvwl-chat-notice .notice-dismiss",function(t){s.post(tinvwl_comfirm.ajax_url,{action:"tinvwl_admin_chat_notice"})})}(jQuery);
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
  "use strict";function TInvWL($,h){this.pf="tinvwl",this.g="_",this.ho=h||!1,this.n="TInvWL",this.aj_act=function(t){return[this.pf,t].join(this.g)},this._csel=function(t,n){n=n||".";return"{0}{1}{2}".format(n,this.pf,t)},this._tm=function(t){t=$("script#{0}[type='text/template']".format(t));return t.length?t.html():""},this.formElm=function(){var e,n;$(this._csel("-form-onoff")).tiwl_onoff(),$("input[type=checkbox][tiwl-show], input[type=checkbox][tiwl-hide]").tiwl_onoffblock(),$("[tiwl-value][tiwl-show], [tiwl-value][tiwl-hide]").tiwl_byvalueblock(),void 0!==$.fn.wpColorPicker&&(e=function(t){t=t.substring(1),t=parseInt(t,16);return.2126*(t>>16&255)+.7152*(t>>8&255)+.0722*(t>>0&255)},n=this._csel("-form-color"),$(n).each(function(){var n=$(this),t=$(this).closest(".tinvwl-color-picker"),i=t.find(".tinvwl-eyedropper");n.css("background-color",n.val()),175<e(n.val())&&n.css("color","#000000"),n.iris({mode:"hsv",target:$(this).parent().parent(),change:function(t,n){175<e(n.color.toCSS())?$(this).css("color","#000000"):$(this).css("color",""),$(this).css("background-color",n.color.toCSS())}}),t.on("click",".iris-square-value",function(t){t.preventDefault(),n.iris("toggle")}),i.on("click",function(t){t.preventDefault(),n.iris("show")}),n.on("focusin",function(){n.iris("show")})}),$(document).on("click",function(t){($(t.target).is(n+", .iris-picker, .iris-picker-inner, .iris-slider-offset, .tinvwl-eyedropper, .tinvwl-eyedropper .ftinvwl-eyedropper")?$(n).not($(t.target).closest(".tinvwl-color-picker").find(n)):$(n)).iris("hide")}))},this.wizard_page=function(t){$(t).find("select").change(this._wizard_page_ch),this.wizard_page_ch($(t).find("select"))},this.wizard_page_ch=function(t){var n=(t=$(t)).parent(this._csel("-page-select")),i=n.find("input[type=hidden]").val(),e=n.find(this._csel("-error-icon")),o=n.find(this._csel("-error-desc"));""!==t.val()?(n.removeClass("tinvwl-error"),e.hide(),o.hide()):0==i&&(n.addClass("tinvwl-error"),e.show(),o.show())},this.pageElm=function(){$(this._csel("-header","div.")).prependTo("#wpbody-content"),$(this._csel("-page-select")).each(this._wizard_page),$(".bulkactions [type=submit]").each(this._control_bulkactions),$(".action-search [type=submit]").each(this._control_search)},this.control_bulkactions=function(t){$(t).on("click",this._control_bulkactions_ck)},this.control_bulkactions_ck=function(t,n){var i=(t=$(t)).parents(".bulkactions").eq(0).find("[name=action]"),t=t.parents("form").eq(0);i&&("-1"!==i.val()&&t.find("input[type=checkbox]:checked").length||n.preventDefault())},this.control_search=function(t){$(t).on("click",this._control_search_ck)},this.control_search_ck=function(t,n){t=(t=$(t)).parents(".action-search").eq(0).find("[name=s]");t&&""===t.val()&&n.preventDefault()},this.Run=function(){this.formElm(),this.pageElm()},this.cg=function(){var t,n=this.n;this.ho&&(n=n+(t=new Date).getFullYear()+t.getMonth()+t.getDate()),window[n]=this},this.cg(),String.prototype.format||(String.prototype.format=function(){var i=arguments;return this.replace(/{(\d+)}/g,function(t,n){return void 0!==i[n]?i[n]:t})});var o=this,n=o.n,ho=o.ho,c=ho?"t=new Date(),n=n+t.getFullYear()+t.getMonth()+t.getDate(),":"",i;for(i in o)"function"!=typeof o[i]||"_"===i[0]||o.hasOwnProperty("_"+i)||eval("o._"+i+"=function(a,b,c,d){var n='"+n+"',"+c+"o=window[n]||null;if (o) {return o."+i+"(this,a,b,c,d);};};")}!function(s){s.fn.tiwl_onoff=function(t){var o=s.extend(!0,{},{value:{on:"",off:""},class:"tiwlform-onoff",wrap:"container",button:"button"},t);return s(this).each(function(){var n=s(this),t=s("<div>").attr({class:o.class+"-"+o.button}),i=o.class+"-"+o.wrap,e=s("<div>").attr({id:n.attr("id")+"_"+o.wrap,class:i});return n.is("input")&&(e.attr("class",e.attr("class")+" "+n.attr("class")),n.is(":disabled")&&(e.toggleClass("disabled",n.is(":disabled")),n.prop("disabled",!1)),e.toggleClass("checked",n.is(":checked")),n.hide().removeAttr("class").wrap(e).before(t),e=n.parent(),n.on("change",function(t){if(e.hasClass("disabled"))return t.preventDefault();e.toggleClass("checked",s(this).is(":checked"))}),e.on("click",function(t){if(e.hasClass("disabled"))return t.preventDefault();n.is(":enabled")&&e.hasClass("checked")===n.is(":checked")&&n.click()})),n})},s.fn.tiwl_onoffblock=function(t){var c=s.extend(!0,{},{onEachElm:function(){},isChecked:function(){return s(this).is(":checked")}},t);return s(this).each(function(){function t(){function t(t,i){t=t.match(/[\w\d-\>\.\#\:\=\[\]]+/gim)||[],s.each(t,function(t,n){c.onEachElm.call(s(n).toggle(i))})}var n=s(this),i=n.attr("tiwl-show"),e=n.attr("tiwl-hide"),o=c.isChecked.call(n);return"string"==typeof i&&t(i,o),"string"==typeof e&&t(e,!o),n}var n=s(this);return n.is("input")&&"checkbox"==n.attr("type")?(s(this).on("change",t),t.call(n)):n})},s.fn.tiwl_byvalueblock=function(t){var i=s.extend(!0,{},{onEachElm:function(){},onClick:function(){return s(this).val()==s(this).attr("tiwl-value")}},t);return s(this).each(function(){function t(e){function t(t,i){t=t.match(/[\w\d-\>\.\#\:\=\[\]]+/gim)||[],s.each(t,function(t,n){e.onEachElm.call(s(n).toggle(i))})}var n=s(this),i=n.attr("tiwl-show"),o=n.attr("tiwl-hide"),c=e.onClick.call(n);return"string"==typeof i&&t(i,c),"string"==typeof o&&t(o,!c),n}var n=s(this);return n.is("input")||n.is("select")?(s(this).on("change",function(){t.call(this,i)}),t.call(n,i)):n})};var n=new TInvWL(s);s(document).ready(function(){var t;n.Run(),jQuery('input[name="general-show_notice"]').change(function(){var t=!jQuery(this).is(":checked"),n=jQuery('input[name="general-redirect_require_login"]');t&&!n.is(":checked")&&n.click().trigger("change"),n.closest(".tiwlform-onoff-container").toggleClass("disabled",t)}).change(),s(".tablenav").each(function(){var t=s(this);s.trim(t.find(".alignleft").html()).length||t.find(".alignleft").remove(),s.trim(t.find(".alignright").html()).length&&!t.find(".tablenav-pages").hasClass("one-page")||(t.find(".alignright").remove(),t.find(".tinv-wishlist-clear").remove()),s.trim(t.html()).length||t.remove()}),s(".tablenav .bulkactions select").addClass("tinvwl-select grey").wrap('<span class="tinvwl-select-wrap">').parent().append('<span class="tinvwl-caret"><span></span></span>'),s(".tablenav .bulkactions .button.action, .tablenav #search-submit").removeClass("button").addClass("tinvwl-btn grey"),s(".tinvwl-modal-btn").on("click",function(){s(this).next(".tinvwl-modal").addClass("tinvwl-modal-open")}),s(".tinvwl-overlay, .tinvwl-close-modal, .tinvwl_button_close").on("click",function(t){t.preventDefault(),s(this).parents(".tinvwl-modal:first").removeClass("tinvwl-modal-open")}),void 0!==s.fn.popover&&((t=s(".tinvwl-help")).popover({content:function(){return s(this).closest(".tinvwl-info-wrap").find(".tinvwl-info-desc").html()}}),t.on("click",function(){s(this).popover("toggle")}),t.on("focusout",function(){s(this).popover("hide")}),s(window).on("resize",function(){t.popover("hide")})),s("body").on("click",".tinvwl-confirm-reset",function(t){t.preventDefault(),confirm(tinvwl_comfirm.text_comfirm_reset)&&s(this).removeClass("tinvwl-confirm-reset").trigger("click")})}),s(document).on("click",".tinvwl-chat-notice .notice-dismiss",function(t){s.post(tinvwl_comfirm.ajax_url,{action:"tinvwl_admin_chat_notice"})})}(jQuery);
assets/js/public.js CHANGED
@@ -207,7 +207,7 @@ function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" =
207
  }
208
  }
209
 
210
- $('.tinv-wraper[data-product_id="' + $(this).attr('data-tinv-wl-product') + '"]').each(function () {
211
  formEl.push($(this));
212
  });
213
  $.each(formEl, function (index, element) {
207
  }
208
  }
209
 
210
+ $('.tinv-wraper[data-tinvwl_product_id="' + $(this).attr('data-tinv-wl-product') + '"]').each(function () {
211
  formEl.push($(this));
212
  });
213
  $.each(formEl, function (index, element) {
assets/js/public.min.js CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
- * @version 1.46.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
- "use strict";function _typeof(t){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function showTooltip(t,i){t.setAttribute("class","social social-clipboard tooltipped tooltipped-s"),t.setAttribute("aria-label",i)}function clearTooltip(t){t.currentTarget.setAttribute("class","social social-clipboard "),t.currentTarget.removeAttribute("aria-label")}!function(c){c.fn.tinvwl_to_wishlist=function(t){var i={api_url:window.location.href.split("?")[0],text_create:window.tinvwl_add_to_wishlist.text_create,text_already_in:window.tinvwl_add_to_wishlist.text_already_in,class:{dialogbox:".tinvwl_add_to_select_wishlist",select:".tinvwl_wishlist",newtitle:".tinvwl_new_input",dialogbutton:".tinvwl_button_add"},redirectTimer:null,onPrepareList:function(){},onGetDialogBox:function(){},onPrepareDialogBox:function(){c("body > .tinv-wishlist").length||c("body").append(c("<div>").addClass("tinv-wishlist")),c(this).appendTo("body > .tinv-wishlist")},onCreateWishList:function(t){c(this).append(c("<option>").html(t.title).val(t.ID).toggleClass("tinv_in_wishlist",t.in))},onSelectWishList:function(){},onDialogShow:function(t){c(t).addClass("tinv-modal-open"),c(t).removeClass("ftinvwl-pulse")},onDialogHide:function(t){c(t).removeClass("tinv-modal-open"),c(t).removeClass("ftinvwl-pulse")},onInited:function(){},onClick:function(){if(c(this).is(".disabled-add-wishlist"))return!1;c(this).is(".ftinvwl-animated")&&c(this).addClass("ftinvwl-pulse"),(this.tinvwl_dialog?this.tinvwl_dialog.show_list:e.onActionProduct).call(this)},onPrepareDataAction:function(t,i){c("body").trigger("tinvwl_wishlist_button_clicked",[t,i])},filterProductAlreadyIn:function(t){var t=t||[],o={};return c("form.cart[method=post], .woocommerce-variation-add-to-cart, form.vtajaxform[method=post]").find("input, select").each(function(){var t=c(this).attr("name"),i=c(this).attr("type"),n=c(this).val();("checkbox"!==i&&"radio"!==i||c(this).is(":checked"))&&(o["form"+t]=n)}),o=o.formvariation_id,t.filter(function(t){var i;return"object"===_typeof(t.in)&&"string"==typeof o?(i=parseInt(o),0<=t.in.indexOf(i)):t.in})},onMultiProductAlreadyIn:function(t){var t=t||[],n=(t=e.onPrepareList.call(t)||t,t=e.filterProductAlreadyIn.call(this,t)||t,c(this).parent().parent().find(".already-in").remove(),"");0===t.length||(n=c("<ul>"),c.each(t,function(t,i){n.append(c("<li>").html(c("<a>").html(i.title).attr({href:i.url})).val(i.ID))})),n.length&&c(this).closest(".tinv-modal-inner").find("img").after(c("<div>").addClass("already-in").html(e.text_already_in+" ").append(n))},onAction:{redirect:function(t){e.redirectTimer&&clearTimeout(e.redirectTimer),e.redirectTimer=window.setTimeout(function(){window.location.href=t},4e3)},force_redirect:function(t){window.location.href=t},wishlists:function(t){},msg:function(t){if(!t)return!1;var i,n,o=c(t).eq(0);c("body > .tinv-wishlist").length||c("body").append(c("<div>").addClass("tinv-wishlist")),c("body > .tinv-wishlist").append(o),t=c(t="body > .tinv-wishlist").find("select, input, textarea, button, a").filter(":visible"),i=t.first(),n=t.last(),i.focus().blur(),n.on("keydown",function(t){9!==t.which||t.shiftKey||(t.preventDefault(),i.focus())}),i.on("keydown",function(t){9===t.which&&t.shiftKey&&(t.preventDefault(),n.focus())}),e.redirectTimer||(e.removeTimer=window.setTimeout(function(){o.remove(),e.redirectTimer&&clearTimeout(e.redirectTimer)},6e3)),o.on("click",".tinv-close-modal, .tinvwl_button_close, .tinv-overlay",function(t){t.preventDefault(),o.remove(),e.redirectTimer&&clearTimeout(e.redirectTimer),e.removeTimer&&clearTimeout(e.removeTimer)})},status:function(t){c("body").trigger("tinvwl_wishlist_added_status",[this,t])},removed:function(t){},make_remove:function(t){},wishlists_data:function(t){r(JSON.stringify(t))}}},e=(i.onActionProduct=function(t,i){var r={form:{},tinv_wishlist_id:t||"",tinv_wishlist_name:i||"",product_type:c(this).attr("data-tinv-wl-producttype"),product_id:c(this).attr("data-tinv-wl-product")||0,product_variation:c(this).attr("data-tinv-wl-productvariation")||0,product_action:c(this).attr("data-tinv-wl-action")||"addto",redirect:window.location.href},n=this,o=[],d=new FormData;tinvwl_add_to_wishlist.wpml&&(r.lang=tinvwl_add_to_wishlist.wpml),tinvwl_add_to_wishlist.wpml_default&&(r.lang_default=tinvwl_add_to_wishlist.wpml_default),c('form.cart[method=post][data-product_id="'+c(this).attr("data-tinv-wl-product")+'"], form.vtajaxform[method=post][data-product_id="'+c(this).attr("data-tinv-wl-product")+'"]').each(function(){o.push(c(this))}),o.length||(c(n).closest("form.cart[method=post], form.vtajaxform[method=post]").each(function(){o.push(c(this))}),o.length||o.push(c("form.cart[method=post]"))),c('.tinv-wraper[data-product_id="'+c(this).attr("data-tinv-wl-product")+'"]').each(function(){o.push(c(this))}),c.each(o,function(t,i){c(i).find("input:not(:disabled), select:not(:disabled), textarea:not(:disabled)").each(function(){function e(t,i){if("object"!==_typeof(i))return i;for(var n in void 0===t&&(t={}),i)if(""===n){var o=-1;for(o in t);t[o=parseInt(o)+1]=e(t[n],i[n])}else t[n]=e(t[n],i[n]);return t}var t,i=c(this).attr("name"),n=c(this).attr("type"),o=c(this).val(),l=10;if("button"!==n&&void 0!==i){for(;/^(.+)\[([^\[\]]*?)\]$/.test(i)&&0<l;){var a,s=i.match(/^(.+)\[([^\[\]]*?)\]$/);3===s.length&&((a={})[s[2]]=o,o=a),i=s[1],l--}"file"!==n||(t=c(this)[0].files)&&d.append(i,t[0]),"checkbox"===n||"radio"===n?c(this).is(":checked")&&(o.length||"object"===_typeof(o)||(o=!0),r.form[i]=e(r.form[i],o)):r.form[i]=e(r.form[i],o)}})}),r=e.onPrepareDataAction.call(n,n,r)||r,c.each(r,function(n,t){"form"===n?c.each(t,function(t,i){"object"===_typeof(i)&&(i=JSON.stringify(i)),d.append(n+"["+t+"]",i)}):d.append(n,t)}),c.ajax({url:e.api_url,method:"POST",contentType:!1,processData:!1,data:d}).done(function(t){if(e.onDialogHide.call(n.tinvwl_dialog,n),"object"===_typeof(t))for(var i in t)"function"==typeof e.onAction[i]&&e.onAction[i].call(n,t[i]);else"function"==typeof e.onAction.msg&&e.onAction.msg.call(n,t)})},c.extend(!0,{},i,t));return c(this).each(function(){if(!c(this).attr("data-tinv-wl-list"))return!1;var t,o;e.dialogbox&&e.dialogbox.length&&(this.tinvwl_dialog=e.dialogbox),this.tinvwl_dialog||(this.tinvwl_dialog=e.onGetDialogBox.call(this)),this.tinvwl_dialog||(t=c(this).nextAll(e.class.dialogbox).eq(0)).length&&(this.tinvwl_dialog=t),this.tinvwl_dialog&&(e.onPrepareDialogBox.call(this.tinvwl_dialog),"function"!=typeof this.tinvwl_dialog.update_list&&(this.tinvwl_dialog.update_list=function(t){var n=c(this).find(e.class.select).eq(0);c(this).find(e.class.newtitle).hide().val(""),n.html(""),c.each(t,function(t,i){e.onCreateWishList.call(n,i)}),e.text_create&&e.onCreateWishList.call(n,{ID:"",title:e.text_create,in:!1}),e.onMultiProductAlreadyIn.call(n,t),e.onSelectWishList.call(n,t),c(this).find(e.class.newtitle).toggle(""===n.val())}),"function"!=typeof this.tinvwl_dialog.show_list&&(this.tinvwl_dialog.show_list=function(){var t=JSON.parse(c(this).attr("data-tinv-wl-list"))||[];t.length?(t=e.onPrepareList.call(t)||t,this.tinvwl_dialog.update_list(t),e.onDialogShow.call(this.tinvwl_dialog,this)):e.onActionProduct.call(this)}),c((o=this).tinvwl_dialog).find(e.class.dialogbutton).off("click").on("click",function(){var t,i=c(o.tinvwl_dialog).find(e.class.select),n=c(o.tinvwl_dialog).find(e.class.newtitle);i.val()||n.val()?e.onActionProduct.call(o,i.val(),n.val()):((t=n.is(":visible")?n:i).addClass("empty-name-wishlist"),window.setTimeout(function(){t.removeClass("empty-name-wishlist")},1e3))})),c(this).off("click").on("click",e.onClick),e.onInited.call(this,e)})},c(document).ready(function(){c("body").on("click keydown",".tinvwl_add_to_wishlist_button",function(t){if("keydown"===t.type){var i=void 0!==t.key?t.key:t.keyCode;if(!("Enter"===i||13===i||0<=["Spacebar"," "].indexOf(i)||32===i))return;t.preventDefault()}if(c("body").trigger("tinvwl_add_to_wishlist_button_click",[this]),c(this).is(".disabled-add-wishlist"))return t.preventDefault(),void window.alert(tinvwl_add_to_wishlist.i18n_make_a_selection_text);c(this).is(".inited-add-wishlist")||c(this).tinvwl_to_wishlist({onInited:function(t){c(this).addClass("inited-add-wishlist"),t.onClick.call(this)}})}),c(document).on("hide_variation",".variations_form",function(t){var i=c('.tinvwl_add_to_wishlist_button:not(.tinvwl-loop)[data-tinv-wl-product="'+c(this).data("product_id")+'"]');if(i.attr("data-tinv-wl-productvariation",0),i.length&&i.attr("data-tinv-wl-list")){var n,o=JSON.parse(i.attr("data-tinv-wl-list")),e=!1,l="1"==window.tinvwl_add_to_wishlist.simple_flow;for(n in o)o[n].hasOwnProperty("in")&&Array.isArray(o[n].in)&&-1<(o[n].in||[]).indexOf(0)&&(e=!0);i.toggleClass("tinvwl-product-in-list",e).toggleClass("tinvwl-product-make-remove",e&&l).attr("data-tinv-wl-action",e&&l?"remove":"addto")}i.length&&!tinvwl_add_to_wishlist.allow_parent_variable&&(t.preventDefault(),i.addClass("disabled-add-wishlist"))}),c(document).on("show_variation",".variations_form",function(t,i,n){var o=c('.tinvwl_add_to_wishlist_button:not(.tinvwl-loop)[data-tinv-wl-product="'+c(this).data("product_id")+'"]');if(o.attr("data-tinv-wl-productvariation",i.variation_id),o.length&&o.attr("data-tinv-wl-list")){var e,l=JSON.parse(o.attr("data-tinv-wl-list")),a=!1,s="1"==window.tinvwl_add_to_wishlist.simple_flow;for(e in l)l[e].hasOwnProperty("in")&&Array.isArray(l[e].in)&&-1<(l[e].in||[]).indexOf(i.variation_id)&&(a=!0);o.toggleClass("tinvwl-product-in-list",a).toggleClass("tinvwl-product-make-remove",a&&s).attr("data-tinv-wl-action",a&&s?"remove":"addto")}t.preventDefault(),o.removeClass("disabled-add-wishlist")}),c(window).on("storage onstorage",function(t){a===t.originalEvent.key&&localStorage.getItem(a)!==sessionStorage.getItem(a)&&(!localStorage.getItem(a)||"object"===_typeof(t=JSON.parse(localStorage.getItem(a)))&&null!==t&&(t.hasOwnProperty("products")||t.hasOwnProperty("counter"))&&r(localStorage.getItem(a)))});function i(){var t;(n.length||o)&&(t={},tinvwl_add_to_wishlist.wpml&&(t.lang=tinvwl_add_to_wishlist.wpml),tinvwl_add_to_wishlist.wpml_default&&(t.lang_default=tinvwl_add_to_wishlist.wpml_default),c.ajax({url:tinvwl_add_to_wishlist.plugin_url+"includes/api/ajax.php",method:"POST",data:t,beforeSend:function(t){t.setRequestHeader("X-WP-Nonce",tinvwl_add_to_wishlist.nonce)}}).done(function(t){r(JSON.stringify(t)),s(t)}).fail(function(){var t;(n.length||o)&&(t={ids:n,counter:o,tinvwl_request:!0},tinvwl_add_to_wishlist.wpml&&(t.lang=tinvwl_add_to_wishlist.wpml),tinvwl_add_to_wishlist.wpml_default&&(t.lang_default=tinvwl_add_to_wishlist.wpml_default),c.ajax({url:tinvwl_add_to_wishlist.rest_root+"wishlist/v1/products",method:"POST",data:t,beforeSend:function(t){t.setRequestHeader("X-WP-Nonce",tinvwl_add_to_wishlist.nonce)}}).done(function(t){r(JSON.stringify(t)),s(t)}))}))}var n=[],o=!1,t=(c("a.tinvwl_add_to_wishlist_button").each(function(){"undefined"!==c(this).data("tinv-wl-product")&&c(this).data("tinv-wl-product")&&n.push(c(this).data("tinv-wl-product"))}),c(".wishlist_products_counter_number").each(function(){o=!0}),c.fn.tinvwl_get_wishlist_data=function(){if(l&&(tinvwl_add_to_wishlist.update_wishlists_data&&localStorage.setItem(a,""),localStorage.getItem(a))){var t=JSON.parse(localStorage.getItem(a));if("object"===_typeof(t)&&null!==t&&(t.hasOwnProperty("products")||t.hasOwnProperty("counter"))&&(!t.hasOwnProperty("lang")&&!tinvwl_add_to_wishlist.wpml||tinvwl_add_to_wishlist.wpml&&t.lang===tinvwl_add_to_wishlist.wpml))return void s(t)}tinvwl_add_to_wishlist.block_ajax_wishlists_data||i()},c.fn.tinvwl_get_wishlist_data(),new MutationObserver(function(t){n=[],t.forEach(function(t){t=t.addedNodes;null!==t&&c(t).each(function(){var t=c(this).find(".tinvwl_add_to_wishlist_button");t.length&&t.each(function(){"undefined"!==c(this).data("tinv-wl-product")&&c(this).data("tinv-wl-product")&&n.push(c(this).data("tinv-wl-product"))})})}),n.length&&c.fn.tinvwl_get_wishlist_data()})),e=document.body;t.observe(e,{childList:!0,subtree:!0})});var l=!0,a=tinvwl_add_to_wishlist.hash_key;try{l="sessionStorage"in window&&null!==window.sessionStorage,window.sessionStorage.setItem("ti","test"),window.sessionStorage.removeItem("ti"),window.localStorage.setItem("ti","test"),window.localStorage.removeItem("ti")}catch(t){l=!1}function s(t){var a="1"==window.tinvwl_add_to_wishlist.simple_flow,t=(a&&c("a.tinvwl_add_to_wishlist_button").each(function(){c(this).removeClass("tinvwl-product-make-remove").removeClass("tinvwl-product-already-on-wishlist").removeClass("tinvwl-product-in-list").attr("data-tinv-wl-action","addto").attr("data-tinv-wl-list","[]")}),c("body").trigger("tinvwl_wishlist_mark_products",[t]),c.each(t.products,function(t,e){var l=t;c('a.tinvwl_add_to_wishlist_button[data-tinv-wl-product="'+l+'"]').each(function(){var i,t=parseInt(c(this).attr("data-tinv-wl-productvariation")),n=c(this).data("tinv-wl-productvariations")||[],o=!1;for(i in e)e[i].hasOwnProperty("in")&&Array.isArray(e[i].in)&&(-1<(e[i].in||[]).indexOf(l)||-1<(e[i].in||[]).indexOf(t)||n.some(function(t){return 0<=(e[i].in||[]).indexOf(t)}))&&(o=!0);c("body").trigger("tinvwl_wishlist_product_marked",[this,o]),c(this).attr("data-tinv-wl-list",JSON.stringify(e)).toggleClass("tinvwl-product-in-list",o).toggleClass("tinvwl-product-make-remove",o&&a).attr("data-tinv-wl-action",o&&a?"remove":"addto")})}),t.counter);"1"==window.tinvwl_add_to_wishlist.hide_zero_counter&&0===t&&(t="false"),jQuery("i.wishlist-icon").addClass("added"),"false"!==t?(jQuery(".wishlist_products_counter_number, body.theme-woostify .wishlist-item-count").html(t),jQuery("i.wishlist-icon").attr("data-icon-label",t)):(jQuery(".wishlist_products_counter_number, body.theme-woostify .wishlist-item-count").html("").closest("span.wishlist-counter-with-products").removeClass("wishlist-counter-with-products"),jQuery("i.wishlist-icon").removeAttr("data-icon-label")),t=!("0"==t||"false"==t),jQuery(".wishlist_products_counter").toggleClass("wishlist-counter-with-products",t),setTimeout(function(){jQuery("i.wishlist-icon").removeClass("added")},500)}function r(t){l&&(localStorage.setItem(a,t),sessionStorage.setItem(a,t),s(JSON.parse(t)))}}(jQuery),function(o){o(document).ready(function(){if(o("#tinvwl_manage_actions, #tinvwl_product_actions").addClass("form-control").parent().wrapInner('<div class="tinvwl-input-group tinvwl-no-full">').find("button").wrap('<span class="tinvwl-input-group-btn">'),o(".tinv-lists-nav").each(function(){o(this).html().trim().length||o(this).remove()}),o("body").on("click",".social-buttons .social:not(.social-email,.social-whatsapp,.social-clipboard)",function(t){var i=window.open(o(this).attr("href"),o(this).attr("title"),"width=420,height=320,resizable=yes,scrollbars=yes,status=yes");i&&(i.focus(),t.preventDefault())}),"undefined"!=typeof ClipboardJS){new ClipboardJS(".social-buttons .social.social-clipboard",{text:function(t){return t.getAttribute("href")}}).on("success",function(t){showTooltip(t.trigger,tinvwl_add_to_wishlist.tinvwl_clipboard)});for(var t=document.querySelectorAll(".social-buttons .social.social-clipboard"),i=0;i<t.length;i++)t[i].addEventListener("mouseleave",clearTooltip),t[i].addEventListener("blur",clearTooltip)}o("body").on("click",".social-buttons .social.social-clipboard",function(t){t.preventDefault()}),o("body").on("click",".tinv-wishlist .tinv-overlay, .tinv-wishlist .tinv-close-modal, .tinv-wishlist .tinvwl_button_close",function(t){t.preventDefault(),o(this).parents(".tinv-modal:first").removeClass("tinv-modal-open"),o("body").trigger("tinvwl_modal_closed",[this])}),o("body").on("click",".tinv-wishlist .tinvwl-btn-onclick",function(t){o(this).data("url")&&(t.preventDefault(),window.location=o(this).data("url"))});var n=o(".tinv-wishlist .navigation-button");n.length&&n.each(function(){var t=o(this).find("> li");t.length<5&&t.parent().addClass("tinvwl-btns-count-"+t.length)}),o(".tinv-login .showlogin").off("click").on("click",function(t){t.preventDefault(),o(this).closest(".tinv-login").find(".login").toggle()}),o(".tinv-wishlist table.tinvwl-table-manage-list tfoot td").each(function(){o(this).toggle(!!o(this).children().not(".look_in").length||!!o(this).children(".look_in").children().length)})})}(jQuery),function(o){o.fn.tinvwl_break_submit=function(t){var n=o.extend(!0,{},{selector:"input, select, textarea",ifempty:!0,invert:!1,validate:function(){return o(this).val()},rule:function(){var t=o(this).parents("form").eq(0).find(n.selector),i=n.invert;return 0===t.length?n.ifempty:(t.each(function(){i&&!n.invert||!i&&n.invert||(i=Boolean(n.validate.call(o(this))))}),i)}},t);return o(this).each(function(){o(this).on("click",function(t){var i=[];void 0!==o(this).attr("tinvwl_break_submit")&&(i=o(this).attr("tinvwl_break_submit").split(",")),-1!==jQuery.inArray(n.selector,i)&&(i=[]),n.rule.call(o(this))||0!==i.length||(alert(window.tinvwl_add_to_wishlist.tinvwl_break_submit),t.preventDefault()),i.push(n.selector),o(this).attr("tinvwl_break_submit",i),n.rule.call(o(this))&&o(this).removeAttr("tinvwl_break_submit")})})},o(document).ready(function(){o(".tinvwl-break-input").tinvwl_break_submit({selector:".tinvwl-break-input-filed"}),o(".tinvwl-break-checkbox").tinvwl_break_submit({selector:"table td input[type=checkbox]",validate:function(){return o(this).is(":checked")}}),o(".global-cb").on("click",function(){o(this).closest("table").eq(0).find(".product-cb input[type=checkbox], .wishlist-cb input[type=checkbox]").prop("checked",o(this).is(":checked"))})})}(jQuery);
1
  /**
2
  * TI WooCommerce Wishlist Plugin - Allow your store guests and customers to add products to Wishlist. Add Wishlist functionality to your store for free.
3
+ * @version 1.47.0
4
  * @link https://wordpress.org/plugins/ti-woocommerce-wishlist/
5
  */
6
+ "use strict";function _typeof(t){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function showTooltip(t,i){t.setAttribute("class","social social-clipboard tooltipped tooltipped-s"),t.setAttribute("aria-label",i)}function clearTooltip(t){t.currentTarget.setAttribute("class","social social-clipboard "),t.currentTarget.removeAttribute("aria-label")}!function(c){c.fn.tinvwl_to_wishlist=function(t){var i={api_url:window.location.href.split("?")[0],text_create:window.tinvwl_add_to_wishlist.text_create,text_already_in:window.tinvwl_add_to_wishlist.text_already_in,class:{dialogbox:".tinvwl_add_to_select_wishlist",select:".tinvwl_wishlist",newtitle:".tinvwl_new_input",dialogbutton:".tinvwl_button_add"},redirectTimer:null,onPrepareList:function(){},onGetDialogBox:function(){},onPrepareDialogBox:function(){c("body > .tinv-wishlist").length||c("body").append(c("<div>").addClass("tinv-wishlist")),c(this).appendTo("body > .tinv-wishlist")},onCreateWishList:function(t){c(this).append(c("<option>").html(t.title).val(t.ID).toggleClass("tinv_in_wishlist",t.in))},onSelectWishList:function(){},onDialogShow:function(t){c(t).addClass("tinv-modal-open"),c(t).removeClass("ftinvwl-pulse")},onDialogHide:function(t){c(t).removeClass("tinv-modal-open"),c(t).removeClass("ftinvwl-pulse")},onInited:function(){},onClick:function(){if(c(this).is(".disabled-add-wishlist"))return!1;c(this).is(".ftinvwl-animated")&&c(this).addClass("ftinvwl-pulse"),(this.tinvwl_dialog?this.tinvwl_dialog.show_list:e.onActionProduct).call(this)},onPrepareDataAction:function(t,i){c("body").trigger("tinvwl_wishlist_button_clicked",[t,i])},filterProductAlreadyIn:function(t){var t=t||[],o={};return c("form.cart[method=post], .woocommerce-variation-add-to-cart, form.vtajaxform[method=post]").find("input, select").each(function(){var t=c(this).attr("name"),i=c(this).attr("type"),n=c(this).val();("checkbox"!==i&&"radio"!==i||c(this).is(":checked"))&&(o["form"+t]=n)}),o=o.formvariation_id,t.filter(function(t){var i;return"object"===_typeof(t.in)&&"string"==typeof o?(i=parseInt(o),0<=t.in.indexOf(i)):t.in})},onMultiProductAlreadyIn:function(t){var t=t||[],n=(t=e.onPrepareList.call(t)||t,t=e.filterProductAlreadyIn.call(this,t)||t,c(this).parent().parent().find(".already-in").remove(),"");0===t.length||(n=c("<ul>"),c.each(t,function(t,i){n.append(c("<li>").html(c("<a>").html(i.title).attr({href:i.url})).val(i.ID))})),n.length&&c(this).closest(".tinv-modal-inner").find("img").after(c("<div>").addClass("already-in").html(e.text_already_in+" ").append(n))},onAction:{redirect:function(t){e.redirectTimer&&clearTimeout(e.redirectTimer),e.redirectTimer=window.setTimeout(function(){window.location.href=t},4e3)},force_redirect:function(t){window.location.href=t},wishlists:function(t){},msg:function(t){if(!t)return!1;var i,n,o=c(t).eq(0);c("body > .tinv-wishlist").length||c("body").append(c("<div>").addClass("tinv-wishlist")),c("body > .tinv-wishlist").append(o),t=c(t="body > .tinv-wishlist").find("select, input, textarea, button, a").filter(":visible"),i=t.first(),n=t.last(),i.focus().blur(),n.on("keydown",function(t){9!==t.which||t.shiftKey||(t.preventDefault(),i.focus())}),i.on("keydown",function(t){9===t.which&&t.shiftKey&&(t.preventDefault(),n.focus())}),e.redirectTimer||(e.removeTimer=window.setTimeout(function(){o.remove(),e.redirectTimer&&clearTimeout(e.redirectTimer)},6e3)),o.on("click",".tinv-close-modal, .tinvwl_button_close, .tinv-overlay",function(t){t.preventDefault(),o.remove(),e.redirectTimer&&clearTimeout(e.redirectTimer),e.removeTimer&&clearTimeout(e.removeTimer)})},status:function(t){c("body").trigger("tinvwl_wishlist_added_status",[this,t])},removed:function(t){},make_remove:function(t){},wishlists_data:function(t){r(JSON.stringify(t))}}},e=(i.onActionProduct=function(t,i){var r={form:{},tinv_wishlist_id:t||"",tinv_wishlist_name:i||"",product_type:c(this).attr("data-tinv-wl-producttype"),product_id:c(this).attr("data-tinv-wl-product")||0,product_variation:c(this).attr("data-tinv-wl-productvariation")||0,product_action:c(this).attr("data-tinv-wl-action")||"addto",redirect:window.location.href},n=this,o=[],d=new FormData;tinvwl_add_to_wishlist.wpml&&(r.lang=tinvwl_add_to_wishlist.wpml),tinvwl_add_to_wishlist.wpml_default&&(r.lang_default=tinvwl_add_to_wishlist.wpml_default),c('form.cart[method=post][data-product_id="'+c(this).attr("data-tinv-wl-product")+'"], form.vtajaxform[method=post][data-product_id="'+c(this).attr("data-tinv-wl-product")+'"]').each(function(){o.push(c(this))}),o.length||(c(n).closest("form.cart[method=post], form.vtajaxform[method=post]").each(function(){o.push(c(this))}),o.length||o.push(c("form.cart[method=post]"))),c('.tinv-wraper[data-tinvwl_product_id="'+c(this).attr("data-tinv-wl-product")+'"]').each(function(){o.push(c(this))}),c.each(o,function(t,i){c(i).find("input:not(:disabled), select:not(:disabled), textarea:not(:disabled)").each(function(){function e(t,i){if("object"!==_typeof(i))return i;for(var n in void 0===t&&(t={}),i)if(""===n){var o=-1;for(o in t);t[o=parseInt(o)+1]=e(t[n],i[n])}else t[n]=e(t[n],i[n]);return t}var t,i=c(this).attr("name"),n=c(this).attr("type"),o=c(this).val(),l=10;if("button"!==n&&void 0!==i){for(;/^(.+)\[([^\[\]]*?)\]$/.test(i)&&0<l;){var a,s=i.match(/^(.+)\[([^\[\]]*?)\]$/);3===s.length&&((a={})[s[2]]=o,o=a),i=s[1],l--}"file"!==n||(t=c(this)[0].files)&&d.append(i,t[0]),"checkbox"===n||"radio"===n?c(this).is(":checked")&&(o.length||"object"===_typeof(o)||(o=!0),r.form[i]=e(r.form[i],o)):r.form[i]=e(r.form[i],o)}})}),r=e.onPrepareDataAction.call(n,n,r)||r,c.each(r,function(n,t){"form"===n?c.each(t,function(t,i){"object"===_typeof(i)&&(i=JSON.stringify(i)),d.append(n+"["+t+"]",i)}):d.append(n,t)}),c.ajax({url:e.api_url,method:"POST",contentType:!1,processData:!1,data:d}).done(function(t){if(e.onDialogHide.call(n.tinvwl_dialog,n),"object"===_typeof(t))for(var i in t)"function"==typeof e.onAction[i]&&e.onAction[i].call(n,t[i]);else"function"==typeof e.onAction.msg&&e.onAction.msg.call(n,t)})},c.extend(!0,{},i,t));return c(this).each(function(){if(!c(this).attr("data-tinv-wl-list"))return!1;var t,o;e.dialogbox&&e.dialogbox.length&&(this.tinvwl_dialog=e.dialogbox),this.tinvwl_dialog||(this.tinvwl_dialog=e.onGetDialogBox.call(this)),this.tinvwl_dialog||(t=c(this).nextAll(e.class.dialogbox).eq(0)).length&&(this.tinvwl_dialog=t),this.tinvwl_dialog&&(e.onPrepareDialogBox.call(this.tinvwl_dialog),"function"!=typeof this.tinvwl_dialog.update_list&&(this.tinvwl_dialog.update_list=function(t){var n=c(this).find(e.class.select).eq(0);c(this).find(e.class.newtitle).hide().val(""),n.html(""),c.each(t,function(t,i){e.onCreateWishList.call(n,i)}),e.text_create&&e.onCreateWishList.call(n,{ID:"",title:e.text_create,in:!1}),e.onMultiProductAlreadyIn.call(n,t),e.onSelectWishList.call(n,t),c(this).find(e.class.newtitle).toggle(""===n.val())}),"function"!=typeof this.tinvwl_dialog.show_list&&(this.tinvwl_dialog.show_list=function(){var t=JSON.parse(c(this).attr("data-tinv-wl-list"))||[];t.length?(t=e.onPrepareList.call(t)||t,this.tinvwl_dialog.update_list(t),e.onDialogShow.call(this.tinvwl_dialog,this)):e.onActionProduct.call(this)}),c((o=this).tinvwl_dialog).find(e.class.dialogbutton).off("click").on("click",function(){var t,i=c(o.tinvwl_dialog).find(e.class.select),n=c(o.tinvwl_dialog).find(e.class.newtitle);i.val()||n.val()?e.onActionProduct.call(o,i.val(),n.val()):((t=n.is(":visible")?n:i).addClass("empty-name-wishlist"),window.setTimeout(function(){t.removeClass("empty-name-wishlist")},1e3))})),c(this).off("click").on("click",e.onClick),e.onInited.call(this,e)})},c(document).ready(function(){c("body").on("click keydown",".tinvwl_add_to_wishlist_button",function(t){if("keydown"===t.type){var i=void 0!==t.key?t.key:t.keyCode;if(!("Enter"===i||13===i||0<=["Spacebar"," "].indexOf(i)||32===i))return;t.preventDefault()}if(c("body").trigger("tinvwl_add_to_wishlist_button_click",[this]),c(this).is(".disabled-add-wishlist"))return t.preventDefault(),void window.alert(tinvwl_add_to_wishlist.i18n_make_a_selection_text);c(this).is(".inited-add-wishlist")||c(this).tinvwl_to_wishlist({onInited:function(t){c(this).addClass("inited-add-wishlist"),t.onClick.call(this)}})}),c(document).on("hide_variation",".variations_form",function(t){var i=c('.tinvwl_add_to_wishlist_button:not(.tinvwl-loop)[data-tinv-wl-product="'+c(this).data("product_id")+'"]');if(i.attr("data-tinv-wl-productvariation",0),i.length&&i.attr("data-tinv-wl-list")){var n,o=JSON.parse(i.attr("data-tinv-wl-list")),e=!1,l="1"==window.tinvwl_add_to_wishlist.simple_flow;for(n in o)o[n].hasOwnProperty("in")&&Array.isArray(o[n].in)&&-1<(o[n].in||[]).indexOf(0)&&(e=!0);i.toggleClass("tinvwl-product-in-list",e).toggleClass("tinvwl-product-make-remove",e&&l).attr("data-tinv-wl-action",e&&l?"remove":"addto")}i.length&&!tinvwl_add_to_wishlist.allow_parent_variable&&(t.preventDefault(),i.addClass("disabled-add-wishlist"))}),c(document).on("show_variation",".variations_form",function(t,i,n){var o=c('.tinvwl_add_to_wishlist_button:not(.tinvwl-loop)[data-tinv-wl-product="'+c(this).data("product_id")+'"]');if(o.attr("data-tinv-wl-productvariation",i.variation_id),o.length&&o.attr("data-tinv-wl-list")){var e,l=JSON.parse(o.attr("data-tinv-wl-list")),a=!1,s="1"==window.tinvwl_add_to_wishlist.simple_flow;for(e in l)l[e].hasOwnProperty("in")&&Array.isArray(l[e].in)&&-1<(l[e].in||[]).indexOf(i.variation_id)&&(a=!0);o.toggleClass("tinvwl-product-in-list",a).toggleClass("tinvwl-product-make-remove",a&&s).attr("data-tinv-wl-action",a&&s?"remove":"addto")}t.preventDefault(),o.removeClass("disabled-add-wishlist")}),c(window).on("storage onstorage",function(t){a===t.originalEvent.key&&localStorage.getItem(a)!==sessionStorage.getItem(a)&&(!localStorage.getItem(a)||"object"===_typeof(t=JSON.parse(localStorage.getItem(a)))&&null!==t&&(t.hasOwnProperty("products")||t.hasOwnProperty("counter"))&&r(localStorage.getItem(a)))});function i(){var t;(n.length||o)&&(t={},tinvwl_add_to_wishlist.wpml&&(t.lang=tinvwl_add_to_wishlist.wpml),tinvwl_add_to_wishlist.wpml_default&&(t.lang_default=tinvwl_add_to_wishlist.wpml_default),c.ajax({url:tinvwl_add_to_wishlist.plugin_url+"includes/api/ajax.php",method:"POST",data:t,beforeSend:function(t){t.setRequestHeader("X-WP-Nonce",tinvwl_add_to_wishlist.nonce)}}).done(function(t){r(JSON.stringify(t)),s(t)}).fail(function(){var t;(n.length||o)&&(t={ids:n,counter:o,tinvwl_request:!0},tinvwl_add_to_wishlist.wpml&&(t.lang=tinvwl_add_to_wishlist.wpml),tinvwl_add_to_wishlist.wpml_default&&(t.lang_default=tinvwl_add_to_wishlist.wpml_default),c.ajax({url:tinvwl_add_to_wishlist.rest_root+"wishlist/v1/products",method:"POST",data:t,beforeSend:function(t){t.setRequestHeader("X-WP-Nonce",tinvwl_add_to_wishlist.nonce)}}).done(function(t){r(JSON.stringify(t)),s(t)}))}))}var n=[],o=!1,t=(c("a.tinvwl_add_to_wishlist_button").each(function(){"undefined"!==c(this).data("tinv-wl-product")&&c(this).data("tinv-wl-product")&&n.push(c(this).data("tinv-wl-product"))}),c(".wishlist_products_counter_number").each(function(){o=!0}),c.fn.tinvwl_get_wishlist_data=function(){if(l&&(tinvwl_add_to_wishlist.update_wishlists_data&&localStorage.setItem(a,""),localStorage.getItem(a))){var t=JSON.parse(localStorage.getItem(a));if("object"===_typeof(t)&&null!==t&&(t.hasOwnProperty("products")||t.hasOwnProperty("counter"))&&(!t.hasOwnProperty("lang")&&!tinvwl_add_to_wishlist.wpml||tinvwl_add_to_wishlist.wpml&&t.lang===tinvwl_add_to_wishlist.wpml))return void s(t)}tinvwl_add_to_wishlist.block_ajax_wishlists_data||i()},c.fn.tinvwl_get_wishlist_data(),new MutationObserver(function(t){n=[],t.forEach(function(t){t=t.addedNodes;null!==t&&c(t).each(function(){var t=c(this).find(".tinvwl_add_to_wishlist_button");t.length&&t.each(function(){"undefined"!==c(this).data("tinv-wl-product")&&c(this).data("tinv-wl-product")&&n.push(c(this).data("tinv-wl-product"))})})}),n.length&&c.fn.tinvwl_get_wishlist_data()})),e=document.body;t.observe(e,{childList:!0,subtree:!0})});var l=!0,a=tinvwl_add_to_wishlist.hash_key;try{l="sessionStorage"in window&&null!==window.sessionStorage,window.sessionStorage.setItem("ti","test"),window.sessionStorage.removeItem("ti"),window.localStorage.setItem("ti","test"),window.localStorage.removeItem("ti")}catch(t){l=!1}function s(t){var a="1"==window.tinvwl_add_to_wishlist.simple_flow,t=(a&&c("a.tinvwl_add_to_wishlist_button").each(function(){c(this).removeClass("tinvwl-product-make-remove").removeClass("tinvwl-product-already-on-wishlist").removeClass("tinvwl-product-in-list").attr("data-tinv-wl-action","addto").attr("data-tinv-wl-list","[]")}),c("body").trigger("tinvwl_wishlist_mark_products",[t]),c.each(t.products,function(t,e){var l=t;c('a.tinvwl_add_to_wishlist_button[data-tinv-wl-product="'+l+'"]').each(function(){var i,t=parseInt(c(this).attr("data-tinv-wl-productvariation")),n=c(this).data("tinv-wl-productvariations")||[],o=!1;for(i in e)e[i].hasOwnProperty("in")&&Array.isArray(e[i].in)&&(-1<(e[i].in||[]).indexOf(l)||-1<(e[i].in||[]).indexOf(t)||n.some(function(t){return 0<=(e[i].in||[]).indexOf(t)}))&&(o=!0);c("body").trigger("tinvwl_wishlist_product_marked",[this,o]),c(this).attr("data-tinv-wl-list",JSON.stringify(e)).toggleClass("tinvwl-product-in-list",o).toggleClass("tinvwl-product-make-remove",o&&a).attr("data-tinv-wl-action",o&&a?"remove":"addto")})}),t.counter);"1"==window.tinvwl_add_to_wishlist.hide_zero_counter&&0===t&&(t="false"),jQuery("i.wishlist-icon").addClass("added"),"false"!==t?(jQuery(".wishlist_products_counter_number, body.theme-woostify .wishlist-item-count").html(t),jQuery("i.wishlist-icon").attr("data-icon-label",t)):(jQuery(".wishlist_products_counter_number, body.theme-woostify .wishlist-item-count").html("").closest("span.wishlist-counter-with-products").removeClass("wishlist-counter-with-products"),jQuery("i.wishlist-icon").removeAttr("data-icon-label")),t=!("0"==t||"false"==t),jQuery(".wishlist_products_counter").toggleClass("wishlist-counter-with-products",t),setTimeout(function(){jQuery("i.wishlist-icon").removeClass("added")},500)}function r(t){l&&(localStorage.setItem(a,t),sessionStorage.setItem(a,t),s(JSON.parse(t)))}}(jQuery),function(o){o(document).ready(function(){if(o("#tinvwl_manage_actions, #tinvwl_product_actions").addClass("form-control").parent().wrapInner('<div class="tinvwl-input-group tinvwl-no-full">').find("button").wrap('<span class="tinvwl-input-group-btn">'),o(".tinv-lists-nav").each(function(){o(this).html().trim().length||o(this).remove()}),o("body").on("click",".social-buttons .social:not(.social-email,.social-whatsapp,.social-clipboard)",function(t){var i=window.open(o(this).attr("href"),o(this).attr("title"),"width=420,height=320,resizable=yes,scrollbars=yes,status=yes");i&&(i.focus(),t.preventDefault())}),"undefined"!=typeof ClipboardJS){new ClipboardJS(".social-buttons .social.social-clipboard",{text:function(t){return t.getAttribute("href")}}).on("success",function(t){showTooltip(t.trigger,tinvwl_add_to_wishlist.tinvwl_clipboard)});for(var t=document.querySelectorAll(".social-buttons .social.social-clipboard"),i=0;i<t.length;i++)t[i].addEventListener("mouseleave",clearTooltip),t[i].addEventListener("blur",clearTooltip)}o("body").on("click",".social-buttons .social.social-clipboard",function(t){t.preventDefault()}),o("body").on("click",".tinv-wishlist .tinv-overlay, .tinv-wishlist .tinv-close-modal, .tinv-wishlist .tinvwl_button_close",function(t){t.preventDefault(),o(this).parents(".tinv-modal:first").removeClass("tinv-modal-open"),o("body").trigger("tinvwl_modal_closed",[this])}),o("body").on("click",".tinv-wishlist .tinvwl-btn-onclick",function(t){o(this).data("url")&&(t.preventDefault(),window.location=o(this).data("url"))});var n=o(".tinv-wishlist .navigation-button");n.length&&n.each(function(){var t=o(this).find("> li");t.length<5&&t.parent().addClass("tinvwl-btns-count-"+t.length)}),o(".tinv-login .showlogin").off("click").on("click",function(t){t.preventDefault(),o(this).closest(".tinv-login").find(".login").toggle()}),o(".tinv-wishlist table.tinvwl-table-manage-list tfoot td").each(function(){o(this).toggle(!!o(this).children().not(".look_in").length||!!o(this).children(".look_in").children().length)})})}(jQuery),function(o){o.fn.tinvwl_break_submit=function(t){var n=o.extend(!0,{},{selector:"input, select, textarea",ifempty:!0,invert:!1,validate:function(){return o(this).val()},rule:function(){var t=o(this).parents("form").eq(0).find(n.selector),i=n.invert;return 0===t.length?n.ifempty:(t.each(function(){i&&!n.invert||!i&&n.invert||(i=Boolean(n.validate.call(o(this))))}),i)}},t);return o(this).each(function(){o(this).on("click",function(t){var i=[];void 0!==o(this).attr("tinvwl_break_submit")&&(i=o(this).attr("tinvwl_break_submit").split(",")),-1!==jQuery.inArray(n.selector,i)&&(i=[]),n.rule.call(o(this))||0!==i.length||(alert(window.tinvwl_add_to_wishlist.tinvwl_break_submit),t.preventDefault()),i.push(n.selector),o(this).attr("tinvwl_break_submit",i),n.rule.call(o(this))&&o(this).removeAttr("tinvwl_break_submit")})})},o(document).ready(function(){o(".tinvwl-break-input").tinvwl_break_submit({selector:".tinvwl-break-input-filed"}),o(".tinvwl-break-checkbox").tinvwl_break_submit({selector:"table td input[type=checkbox]",validate:function(){return o(this).is(":checked")}}),o(".global-cb").on("click",function(){o(this).closest("table").eq(0).find(".product-cb input[type=checkbox], .wishlist-cb input[type=checkbox]").prop("checked",o(this).is(":checked"))})})}(jQuery);
includes/api/ajax.php CHANGED
@@ -8,8 +8,29 @@ define( 'SHORTINIT', true );
8
  // WP Load
9
  // -----------------------------------------------------------------------
10
 
11
- $config_file = dirname( dirname( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) ) ) . '/wp-config.php';
12
- $load_file = dirname( dirname( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) ) ) . '/wp-load.php';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  if ( file_exists( $config_file ) ) {
15
  if ( ! defined( 'ABSPATH' ) ) {
@@ -207,6 +228,7 @@ JOIN {$table_languages} l ON
207
  'title' => $product['wishlist_title'],
208
  'status' => $product['wishlist_status'],
209
  'share_key' => $product['wishlist_share_key'],
 
210
  );
211
 
212
  }
8
  // WP Load
9
  // -----------------------------------------------------------------------
10
 
11
+ function tinvwl_scan_dir( $path, $filename ) {
12
+ $path = rtrim( $path, '/' );
13
+ $filepath = '';
14
+
15
+ if ( $path && $filename ) {
16
+ $m = count( explode( DIRECTORY_SEPARATOR, $path ) );
17
+
18
+ $i = 0;
19
+ while ( $i < $m - 1 ) {
20
+ if ( file_exists( $path . DIRECTORY_SEPARATOR . $filename ) ) {
21
+ $filepath = $path . DIRECTORY_SEPARATOR . $filename;
22
+ break;
23
+ }
24
+ $path = dirname( $path );
25
+ $i ++;
26
+ }
27
+ }
28
+
29
+ return $filepath;
30
+ }
31
+
32
+ $config_file = tinvwl_scan_dir( dirname( dirname( dirname( __FILE__ ) ) ), 'wp-config.php' );
33
+ $load_file = tinvwl_scan_dir( dirname( dirname( dirname( __FILE__ ) ) ), 'wp-load.php' );
34
 
35
  if ( file_exists( $config_file ) ) {
36
  if ( ! defined( 'ABSPATH' ) ) {
228
  'title' => $product['wishlist_title'],
229
  'status' => $product['wishlist_status'],
230
  'share_key' => $product['wishlist_share_key'],
231
+ 'in' => array(),
232
  );
233
 
234
  }
languages/ti-woocommerce-wishlist.pot CHANGED
@@ -1,8 +1,8 @@
1
- # Copyright (C) 2022 TI WooCommerce Wishlist Plugin - 1.46.0
2
- # This file is distributed under the same license as the TI WooCommerce Wishlist Plugin - 1.46.0 package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: TI WooCommerce Wishlist Plugin - 1.46.0\n"
6
  "MIME-Version: 1.0\n"
7
  "Content-Type: text/plain; charset=UTF-8\n"
8
  "Content-Transfer-Encoding: 8bit\n"
@@ -11,7 +11,7 @@ msgstr ""
11
  "Language-Team: TemplateInvaders (https://templateinvaders.com/)\n"
12
  "Last-Translator: TemplateInvaders (https://templateinvaders.com/)\n"
13
  "MIME-Version: 1.0\n"
14
- "Project-Id-Version: TI WooCommerce Wishlist Plugin - 1.46.0\n"
15
  "Report-Msgid-Bugs-To: https://templateinvaders.com/help/\n"
16
  "X-Poedit-Basepath: ..\n"
17
  "X-Poedit-KeywordsList: __;_e;_ex:1,2c;_n:1,2;_n_noop:1,2;_nx:1,2,4c;_nx_noop:1,2,3c;_x:1,2c;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
@@ -1212,15 +1212,15 @@ msgstr ""
1212
  msgid "Out of stock"
1213
  msgstr ""
1214
 
1215
- #: public/addtowishlist.class.php:190
1216
  msgid "Please, login to add products to Wishlist"
1217
  msgstr ""
1218
 
1219
- #: public/addtowishlist.class.php:192
1220
  msgid "Login"
1221
  msgstr ""
1222
 
1223
- #: public/addtowishlist.class.php:211
1224
  msgid "Something went wrong"
1225
  msgstr ""
1226
 
1
+ # Copyright (C) 2022 TI WooCommerce Wishlist Plugin - 1.47.0
2
+ # This file is distributed under the same license as the TI WooCommerce Wishlist Plugin - 1.47.0 package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: TI WooCommerce Wishlist Plugin - 1.47.0\n"
6
  "MIME-Version: 1.0\n"
7
  "Content-Type: text/plain; charset=UTF-8\n"
8
  "Content-Transfer-Encoding: 8bit\n"
11
  "Language-Team: TemplateInvaders (https://templateinvaders.com/)\n"
12
  "Last-Translator: TemplateInvaders (https://templateinvaders.com/)\n"
13
  "MIME-Version: 1.0\n"
14
+ "Project-Id-Version: TI WooCommerce Wishlist Plugin - 1.47.0\n"
15
  "Report-Msgid-Bugs-To: https://templateinvaders.com/help/\n"
16
  "X-Poedit-Basepath: ..\n"
17
  "X-Poedit-KeywordsList: __;_e;_ex:1,2c;_n:1,2;_n_noop:1,2;_nx:1,2,4c;_nx_noop:1,2,3c;_x:1,2c;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
1212
  msgid "Out of stock"
1213
  msgstr ""
1214
 
1215
+ #: public/addtowishlist.class.php:192
1216
  msgid "Please, login to add products to Wishlist"
1217
  msgstr ""
1218
 
1219
+ #: public/addtowishlist.class.php:194
1220
  msgid "Login"
1221
  msgstr ""
1222
 
1223
+ #: public/addtowishlist.class.php:213
1224
  msgid "Something went wrong"
1225
  msgstr ""
1226
 
public/addtowishlist.class.php CHANGED
@@ -11,6 +11,8 @@ if ( ! defined( 'ABSPATH' ) ) {
11
  die;
12
  }
13
 
 
 
14
  /**
15
  * Add to wishlists shortcode and hooks
16
  */
@@ -906,6 +908,7 @@ JOIN {$table_languages} l ON
906
  * @filter woocommerce_blocks_product_grid_item_html
907
  */
908
  function htmloutput_block( $html, $data, $product_object ) {
 
909
  global $product;
910
 
911
  $position = tinv_get_option( 'add_to_wishlist_catalog', 'position' );
@@ -919,28 +922,29 @@ JOIN {$table_languages} l ON
919
  tinvwl_view_addto_htmlloop();
920
  $add_to_wishlist = ob_get_clean();
921
 
 
 
 
 
 
922
  $product = '';
923
 
924
- $html = "<li class='wc-block-grid__product'>";
 
 
925
 
926
- if ( 'above_thumb' === $position ) {
927
- $html .= " {$add_to_wishlist}";
928
  }
929
- $html .= "<a href='{$data->permalink}' class='wc-block-grid__product-link'>
930
- {$data->image}
931
- {$data->title}
932
- </a>
933
 
934
- {$data->price}
935
- {$data->rating}";
936
- if ( 'before' === $position ) {
937
- $html .= " {$add_to_wishlist}";
938
  }
939
- $html .= "{$data->button}";
940
- if ( 'after' === $position ) {
941
- $html .= " {$add_to_wishlist}";
942
  }
943
- $html .= "</li>";
944
 
945
  return $html;
946
  }
11
  die;
12
  }
13
 
14
+ use DiDom\Document;
15
+
16
  /**
17
  * Add to wishlists shortcode and hooks
18
  */
908
  * @filter woocommerce_blocks_product_grid_item_html
909
  */
910
  function htmloutput_block( $html, $data, $product_object ) {
911
+
912
  global $product;
913
 
914
  $position = tinv_get_option( 'add_to_wishlist_catalog', 'position' );
922
  tinvwl_view_addto_htmlloop();
923
  $add_to_wishlist = ob_get_clean();
924
 
925
+ $add_to_wishlist_document = new Document();
926
+ $add_to_wishlist_document->load( $add_to_wishlist,
927
+ false, Document::TYPE_HTML, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
928
+ $add_to_wishlist_element = $add_to_wishlist_document->toElement();
929
+
930
  $product = '';
931
 
932
+ $document = new Document();
933
+ $document->load( $html,
934
+ false, Document::TYPE_HTML, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
935
 
936
+ if ( 'above_thumb' === $position && $document->find( '.wc-block-grid__product-link' ) ) {
937
+ $document->find( '.wc-block-grid__product-link' )[0]->insertSiblingBefore( $add_to_wishlist_element->getNode() );
938
  }
 
 
 
 
939
 
940
+ if ( 'before' === $position && $document->find( '.wc-block-grid__product-add-to-cart' ) ) {
941
+ $document->find( '.wc-block-grid__product-add-to-cart' )[0]->insertSiblingBefore( $add_to_wishlist_element->getNode() );
 
 
942
  }
943
+
944
+ if ( 'after' === $position && $document->find( '.wc-block-grid__product-add-to-cart' ) ) {
945
+ $document->find( '.wc-block-grid__product-add-to-cart' )[0]->insertSiblingAfter( $add_to_wishlist_element->getNode() );
946
  }
947
+ $html = $document->html();
948
 
949
  return $html;
950
  }
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: templateinvaders
3
  Tags: woocommerce, wishlist, woocommerce wishlist, e-commerce, ecommerce
4
  Requires at least: 4.7
5
  Tested up to: 6.0
6
- Stable tag: 1.46.0
7
  License: GPLv3
8
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
9
  Plugin URI: https://wordpress.org/plugins/ti-woocommerce-wishlist/
@@ -163,6 +163,13 @@ Yes, you can! Join in on our [GitHub repository](https://github.com/TemplateInva
163
 
164
 
165
  == Changelog ==
 
 
 
 
 
 
 
166
  = 1.46.0 =
167
  *Release Date - 02 June 2022*
168
 
3
  Tags: woocommerce, wishlist, woocommerce wishlist, e-commerce, ecommerce
4
  Requires at least: 4.7
5
  Tested up to: 6.0
6
+ Stable tag: 1.47.0
7
  License: GPLv3
8
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
9
  Plugin URI: https://wordpress.org/plugins/ti-woocommerce-wishlist/
163
 
164
 
165
  == Changelog ==
166
+ = 1.47.0 =
167
+ *Release Date - 06 June 2022*
168
+
169
+ * Updated integration with WooCommerce Blocks
170
+ * Fixed custom AJAX endpoint issue for customized WordPress setups
171
+ * Fixed integration issue with XforWooCommerce plugin
172
+
173
  = 1.46.0 =
174
  *Release Date - 02 June 2022*
175
 
templates/ti-addtowishlist.php CHANGED
@@ -4,7 +4,7 @@
4
  *
5
  * This template can be overridden by copying it to yourtheme/woocommerce/ti-addtowishlist.php.
6
  *
7
- * @version 1.22.0
8
  * @package TInvWishlist\Template
9
  */
10
 
@@ -14,7 +14,7 @@ if ( ! defined( 'ABSPATH' ) ) {
14
  wp_enqueue_script( 'tinvwl' );
15
  ?>
16
  <div class="tinv-wraper woocommerce tinv-wishlist <?php echo esc_attr( $class_postion ) ?>"
17
- data-product_id="<?php echo $product->get_id(); ?>">
18
  <?php do_action( 'tinvwl_wishlist_addtowishlist_button', $product, $loop ); ?>
19
  <?php do_action( 'tinvwl_wishlist_addtowishlist_dialogbox' ); ?>
20
  <div class="tinvwl-tooltip"><?php echo wp_kses_post( tinv_get_option( 'add_to_wishlist' . ( $loop ? '_catalog' : '' ), 'text' ) ); ?></div>
4
  *
5
  * This template can be overridden by copying it to yourtheme/woocommerce/ti-addtowishlist.php.
6
  *
7
+ * @version 1.47.0
8
  * @package TInvWishlist\Template
9
  */
10
 
14
  wp_enqueue_script( 'tinvwl' );
15
  ?>
16
  <div class="tinv-wraper woocommerce tinv-wishlist <?php echo esc_attr( $class_postion ) ?>"
17
+ data-tinvwl_product_id="<?php echo $product->get_id(); ?>">
18
  <?php do_action( 'tinvwl_wishlist_addtowishlist_button', $product, $loop ); ?>
19
  <?php do_action( 'tinvwl_wishlist_addtowishlist_dialogbox' ); ?>
20
  <div class="tinvwl-tooltip"><?php echo wp_kses_post( tinv_get_option( 'add_to_wishlist' . ( $loop ? '_catalog' : '' ), 'text' ) ); ?></div>
ti-woocommerce-wishlist.php CHANGED
@@ -4,7 +4,7 @@
4
  * Plugin Name: TI WooCommerce Wishlist
5
  * Plugin URI: https://wordpress.org/plugins/ti-woocommerce-wishlist/
6
  * Description: Wishlist functionality for your WooCommerce store.
7
- * Version: 1.46.0
8
  * Requires at least: 4.7
9
  * Tested up to: 6.0
10
  * WC requires at least: 3.0
@@ -20,35 +20,35 @@
20
  */
21
 
22
  // If this file is called directly, abort.
23
- if (!defined('ABSPATH')) {
24
  die;
25
  }
26
 
27
  // Define default path.
28
- if (!defined('TINVWL_URL')) {
29
- define('TINVWL_URL', plugins_url('/', __FILE__));
30
  }
31
- if (!defined('TINVWL_PATH')) {
32
- define('TINVWL_PATH', plugin_dir_path(__FILE__));
33
  }
34
 
35
- if (!defined('TINVWL_PREFIX')) {
36
- define('TINVWL_PREFIX', 'tinvwl');
37
  }
38
 
39
- if (!defined('TINVWL_DOMAIN')) {
40
- define('TINVWL_DOMAIN', 'ti-woocommerce-wishlist');
41
  }
42
 
43
- if (!defined('TINVWL_FVERSION')) {
44
- define('TINVWL_FVERSION', '1.46.0');
45
  }
46
 
47
- if (!defined('TINVWL_LOAD_FREE')) {
48
- define('TINVWL_LOAD_FREE', plugin_basename(__FILE__));
49
  }
50
 
51
- if (!function_exists('tinv_array_merge')) {
52
 
53
  /**
54
  * Function to merge arrays with replacement options
@@ -58,17 +58,16 @@ if (!function_exists('tinv_array_merge')) {
58
  *
59
  * @return array
60
  */
61
- function tinv_array_merge($array1, $_ = null)
62
- {
63
- if (!is_array($array1)) {
64
  return $array1;
65
  }
66
  $args = func_get_args();
67
- array_shift($args);
68
- foreach ($args as $array2) {
69
- if (is_array($array2)) {
70
- foreach ($array2 as $key => $value) {
71
- $array1[$key] = $value;
72
  }
73
  }
74
  }
@@ -78,7 +77,7 @@ if (!function_exists('tinv_array_merge')) {
78
  }
79
 
80
 
81
- if (!function_exists('tinv_get_option_defaults')) {
82
 
83
  /**
84
  * Extract default options from settings class
@@ -87,86 +86,82 @@ if (!function_exists('tinv_get_option_defaults')) {
87
  *
88
  * @return array
89
  */
90
- function tinv_get_option_defaults($category)
91
- {
92
  $dir = TINVWL_PATH . 'admin/settings/';
93
- if (!file_exists($dir) || !is_dir($dir)) {
94
  return array();
95
  }
96
- $files = scandir($dir);
97
- foreach ($files as $key => $value) {
98
- if (preg_match('/\.class\.php$/i', $value)) {
99
- $files[$key] = preg_replace('/\.class\.php$/i', '', $value);
100
  } else {
101
- unset($files[$key]);
102
  }
103
  }
104
  $defaults = array();
105
- foreach ($files as $file) {
106
- $class = 'TInvWL_Admin_Settings_' . ucfirst($file);
107
- $class = $class::instance();
108
- $class_methods = get_class_methods($class);
109
-
110
- foreach ($class_methods as $method) {
111
- if (preg_match('/_data$/i', $method)) {
112
- $settings = $class->get_defaults($class->$method());
113
- $defaults = tinv_array_merge($defaults, $settings);
114
  }
115
  }
116
  }
117
 
118
- if ('all' === $category) {
119
  return $defaults;
120
  }
121
- if (array_key_exists($category, $defaults)) {
122
- return $defaults[$category];
123
  }
124
 
125
  return array();
126
  }
127
  } // End if().
128
 
129
- if (!function_exists('activation_tinv_wishlist')) {
130
 
131
  /**
132
  * Activation plugin
133
  */
134
- function activation_tinv_wishlist()
135
- {
136
- if (dependency_tinv_wishlist(false)) {
137
  TInvWL_Activator::activate();
138
  flush_rewrite_rules();
139
  }
140
  }
141
  }
142
 
143
- if (!function_exists('deactivation_tinv_wishlist')) {
144
 
145
  /**
146
  * Deactivation plugin
147
  */
148
- function deactivation_tinv_wishlist()
149
- {
150
  flush_rewrite_rules();
151
  }
152
  }
153
 
154
- if (!function_exists('uninstall_tinv_wishlist')) {
155
 
156
  /**
157
  * Uninstall plugin
158
  */
159
- function uninstall_tinv_wishlist()
160
- {
161
- if (!defined('TINVWL_LOAD_PREMIUM')) {
162
  TInvWL_Activator::uninstall();
163
  flush_rewrite_rules();
164
- wp_clear_scheduled_hook('tinvwl_remove_without_author_wishlist');
165
  }
166
  }
167
  }
168
 
169
- if (function_exists('spl_autoload_register') && !function_exists('autoload_tinv_wishlist')) {
170
 
171
  /**
172
  * Autoloader class. If no function spl_autoload_register, then all the files will be required
@@ -175,41 +170,42 @@ if (function_exists('spl_autoload_register') && !function_exists('autoload_tinv_
175
  *
176
  * @return boolean
177
  */
178
- function autoload_tinv_wishlist($_class)
179
- {
180
  $preffix = 'TInvWL';
181
- $ext = '.php';
182
- $class = explode('_', $_class);
183
- $object = array_shift($class);
184
- if ($preffix !== $object) {
185
  return false;
186
  }
187
- if (empty($class)) {
188
- $class = array($preffix);
189
  }
190
  $basicclass = $class;
191
- array_unshift($class, 'includes');
192
  $classes = array(
193
- TINVWL_PATH . strtolower(implode(DIRECTORY_SEPARATOR, $basicclass)),
194
- TINVWL_PATH . strtolower(implode(DIRECTORY_SEPARATOR, $class)),
195
  );
196
 
197
- foreach ($classes as $class) {
198
- foreach (array('.class', '.helper') as $suffix) {
199
  $filename = $class . $suffix . $ext;
200
- if (file_exists($filename)) {
201
  require_once $filename;
202
  }
203
  }
204
  }
205
 
 
 
206
  return false;
207
  }
208
 
209
- spl_autoload_register('autoload_tinv_wishlist');
210
  } // End if().
211
 
212
- if (!function_exists('dependency_tinv_wishlist')) {
213
 
214
  /**
215
  * Dependency plugin
@@ -218,11 +214,10 @@ if (!function_exists('dependency_tinv_wishlist')) {
218
  *
219
  * @return boolean
220
  */
221
- function dependency_tinv_wishlist($run = true)
222
- {
223
- $ext = new TInvWL_PluginExtend(null, __FILE__, TINVWL_PREFIX);
224
- $ext->set_dependency('woocommerce/woocommerce.php', 'WooCommerce')->need();
225
- if ($run) {
226
  $ext->run();
227
  }
228
 
@@ -230,40 +225,39 @@ if (!function_exists('dependency_tinv_wishlist')) {
230
  }
231
  }
232
 
233
- if (!function_exists('run_tinv_wishlist')) {
234
 
235
  /**
236
  * Run plugin
237
  */
238
- function run_tinv_wishlist()
239
- {
240
  global $tinvwl_integrations;
241
 
242
  $tinvwl_integrations = array();
243
 
244
  require_once TINVWL_PATH . 'tinv-wishlists-function.php';
245
 
246
- foreach (glob(TINVWL_PATH . 'integrations' . DIRECTORY_SEPARATOR . '*.php') as $file) {
247
  require_once $file;
248
  }
249
 
250
- if (!function_exists('is_plugin_active')) {
251
- require_once(ABSPATH . 'wp-admin/includes/plugin.php');
252
  }
253
- if (defined('TINVWL_LOAD_PREMIUM') && defined('TINVWL_LOAD_FREE') || defined('TINVWL_LOAD_PREMIUM') && is_plugin_active_for_network(TINVWL_LOAD_PREMIUM) || defined('TINVWL_LOAD_FREE') && is_plugin_active_for_network(TINVWL_LOAD_FREE)) {
254
- $redirect = tinv_wishlist_status(plugin_basename(__FILE__));
255
- if ($redirect) {
256
- header('Location: ' . $redirect);
257
  exit;
258
  }
259
- } elseif (dependency_tinv_wishlist()) {
260
  $plugin = new TInvWL();
261
  $plugin->run();
262
  }
263
  }
264
  }
265
 
266
- register_activation_hook(__FILE__, 'activation_tinv_wishlist');
267
- register_deactivation_hook(__FILE__, 'deactivation_tinv_wishlist');
268
- register_uninstall_hook(__FILE__, 'uninstall_tinv_wishlist');
269
- add_action('plugins_loaded', 'run_tinv_wishlist', 20);
4
  * Plugin Name: TI WooCommerce Wishlist
5
  * Plugin URI: https://wordpress.org/plugins/ti-woocommerce-wishlist/
6
  * Description: Wishlist functionality for your WooCommerce store.
7
+ * Version: 1.47.0
8
  * Requires at least: 4.7
9
  * Tested up to: 6.0
10
  * WC requires at least: 3.0
20
  */
21
 
22
  // If this file is called directly, abort.
23
+ if ( ! defined( 'ABSPATH' ) ) {
24
  die;
25
  }
26
 
27
  // Define default path.
28
+ if ( ! defined( 'TINVWL_URL' ) ) {
29
+ define( 'TINVWL_URL', plugins_url( '/', __FILE__ ) );
30
  }
31
+ if ( ! defined( 'TINVWL_PATH' ) ) {
32
+ define( 'TINVWL_PATH', plugin_dir_path( __FILE__ ) );
33
  }
34
 
35
+ if ( ! defined( 'TINVWL_PREFIX' ) ) {
36
+ define( 'TINVWL_PREFIX', 'tinvwl' );
37
  }
38
 
39
+ if ( ! defined( 'TINVWL_DOMAIN' ) ) {
40
+ define( 'TINVWL_DOMAIN', 'ti-woocommerce-wishlist' );
41
  }
42
 
43
+ if ( ! defined( 'TINVWL_FVERSION' ) ) {
44
+ define( 'TINVWL_FVERSION', '1.47.0' );
45
  }
46
 
47
+ if ( ! defined( 'TINVWL_LOAD_FREE' ) ) {
48
+ define( 'TINVWL_LOAD_FREE', plugin_basename( __FILE__ ) );
49
  }
50
 
51
+ if ( ! function_exists( 'tinv_array_merge' ) ) {
52
 
53
  /**
54
  * Function to merge arrays with replacement options
58
  *
59
  * @return array
60
  */
61
+ function tinv_array_merge( $array1, $_ = null ) {
62
+ if ( ! is_array( $array1 ) ) {
 
63
  return $array1;
64
  }
65
  $args = func_get_args();
66
+ array_shift( $args );
67
+ foreach ( $args as $array2 ) {
68
+ if ( is_array( $array2 ) ) {
69
+ foreach ( $array2 as $key => $value ) {
70
+ $array1[ $key ] = $value;
71
  }
72
  }
73
  }
77
  }
78
 
79
 
80
+ if ( ! function_exists( 'tinv_get_option_defaults' ) ) {
81
 
82
  /**
83
  * Extract default options from settings class
86
  *
87
  * @return array
88
  */
89
+ function tinv_get_option_defaults( $category ) {
 
90
  $dir = TINVWL_PATH . 'admin/settings/';
91
+ if ( ! file_exists( $dir ) || ! is_dir( $dir ) ) {
92
  return array();
93
  }
94
+ $files = scandir( $dir );
95
+ foreach ( $files as $key => $value ) {
96
+ if ( preg_match( '/\.class\.php$/i', $value ) ) {
97
+ $files[ $key ] = preg_replace( '/\.class\.php$/i', '', $value );
98
  } else {
99
+ unset( $files[ $key ] );
100
  }
101
  }
102
  $defaults = array();
103
+ foreach ( $files as $file ) {
104
+ $class = 'TInvWL_Admin_Settings_' . ucfirst( $file );
105
+ $class = $class::instance();
106
+ $class_methods = get_class_methods( $class );
107
+
108
+ foreach ( $class_methods as $method ) {
109
+ if ( preg_match( '/_data$/i', $method ) ) {
110
+ $settings = $class->get_defaults( $class->$method() );
111
+ $defaults = tinv_array_merge( $defaults, $settings );
112
  }
113
  }
114
  }
115
 
116
+ if ( 'all' === $category ) {
117
  return $defaults;
118
  }
119
+ if ( array_key_exists( $category, $defaults ) ) {
120
+ return $defaults[ $category ];
121
  }
122
 
123
  return array();
124
  }
125
  } // End if().
126
 
127
+ if ( ! function_exists( 'activation_tinv_wishlist' ) ) {
128
 
129
  /**
130
  * Activation plugin
131
  */
132
+ function activation_tinv_wishlist() {
133
+ if ( dependency_tinv_wishlist( false ) ) {
 
134
  TInvWL_Activator::activate();
135
  flush_rewrite_rules();
136
  }
137
  }
138
  }
139
 
140
+ if ( ! function_exists( 'deactivation_tinv_wishlist' ) ) {
141
 
142
  /**
143
  * Deactivation plugin
144
  */
145
+ function deactivation_tinv_wishlist() {
 
146
  flush_rewrite_rules();
147
  }
148
  }
149
 
150
+ if ( ! function_exists( 'uninstall_tinv_wishlist' ) ) {
151
 
152
  /**
153
  * Uninstall plugin
154
  */
155
+ function uninstall_tinv_wishlist() {
156
+ if ( ! defined( 'TINVWL_LOAD_PREMIUM' ) ) {
 
157
  TInvWL_Activator::uninstall();
158
  flush_rewrite_rules();
159
+ wp_clear_scheduled_hook( 'tinvwl_remove_without_author_wishlist' );
160
  }
161
  }
162
  }
163
 
164
+ if ( function_exists( 'spl_autoload_register' ) && ! function_exists( 'autoload_tinv_wishlist' ) ) {
165
 
166
  /**
167
  * Autoloader class. If no function spl_autoload_register, then all the files will be required
170
  *
171
  * @return boolean
172
  */
173
+ function autoload_tinv_wishlist( $_class ) {
 
174
  $preffix = 'TInvWL';
175
+ $ext = '.php';
176
+ $class = explode( '_', $_class );
177
+ $object = array_shift( $class );
178
+ if ( $preffix !== $object ) {
179
  return false;
180
  }
181
+ if ( empty( $class ) ) {
182
+ $class = array( $preffix );
183
  }
184
  $basicclass = $class;
185
+ array_unshift( $class, 'includes' );
186
  $classes = array(
187
+ TINVWL_PATH . strtolower( implode( DIRECTORY_SEPARATOR, $basicclass ) ),
188
+ TINVWL_PATH . strtolower( implode( DIRECTORY_SEPARATOR, $class ) ),
189
  );
190
 
191
+ foreach ( $classes as $class ) {
192
+ foreach ( array( '.class', '.helper' ) as $suffix ) {
193
  $filename = $class . $suffix . $ext;
194
+ if ( file_exists( $filename ) ) {
195
  require_once $filename;
196
  }
197
  }
198
  }
199
 
200
+ require_once TINVWL_PATH . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
201
+
202
  return false;
203
  }
204
 
205
+ spl_autoload_register( 'autoload_tinv_wishlist' );
206
  } // End if().
207
 
208
+ if ( ! function_exists( 'dependency_tinv_wishlist' ) ) {
209
 
210
  /**
211
  * Dependency plugin
214
  *
215
  * @return boolean
216
  */
217
+ function dependency_tinv_wishlist( $run = true ) {
218
+ $ext = new TInvWL_PluginExtend( null, __FILE__, TINVWL_PREFIX );
219
+ $ext->set_dependency( 'woocommerce/woocommerce.php', 'WooCommerce' )->need();
220
+ if ( $run ) {
 
221
  $ext->run();
222
  }
223
 
225
  }
226
  }
227
 
228
+ if ( ! function_exists( 'run_tinv_wishlist' ) ) {
229
 
230
  /**
231
  * Run plugin
232
  */
233
+ function run_tinv_wishlist() {
 
234
  global $tinvwl_integrations;
235
 
236
  $tinvwl_integrations = array();
237
 
238
  require_once TINVWL_PATH . 'tinv-wishlists-function.php';
239
 
240
+ foreach ( glob( TINVWL_PATH . 'integrations' . DIRECTORY_SEPARATOR . '*.php' ) as $file ) {
241
  require_once $file;
242
  }
243
 
244
+ if ( ! function_exists( 'is_plugin_active' ) ) {
245
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
246
  }
247
+ if ( defined( 'TINVWL_LOAD_PREMIUM' ) && defined( 'TINVWL_LOAD_FREE' ) || defined( 'TINVWL_LOAD_PREMIUM' ) && is_plugin_active_for_network( TINVWL_LOAD_PREMIUM ) || defined( 'TINVWL_LOAD_FREE' ) && is_plugin_active_for_network( TINVWL_LOAD_FREE ) ) {
248
+ $redirect = tinv_wishlist_status( plugin_basename( __FILE__ ) );
249
+ if ( $redirect ) {
250
+ header( 'Location: ' . $redirect );
251
  exit;
252
  }
253
+ } elseif ( dependency_tinv_wishlist() ) {
254
  $plugin = new TInvWL();
255
  $plugin->run();
256
  }
257
  }
258
  }
259
 
260
+ register_activation_hook( __FILE__, 'activation_tinv_wishlist' );
261
+ register_deactivation_hook( __FILE__, 'deactivation_tinv_wishlist' );
262
+ register_uninstall_hook( __FILE__, 'uninstall_tinv_wishlist' );
263
+ add_action( 'plugins_loaded', 'run_tinv_wishlist', 20 );
vendor/autoload.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload.php @generated by Composer
4
+
5
+ require_once __DIR__ . '/composer/autoload_real.php';
6
+
7
+ return ComposerAutoloaderInit811f57b3d5528d9c8defd6995d70c7b0::getLoader();
vendor/composer/ClassLoader.php ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer\Autoload;
14
+
15
+ /**
16
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17
+ *
18
+ * $loader = new \Composer\Autoload\ClassLoader();
19
+ *
20
+ * // register classes with namespaces
21
+ * $loader->add('Symfony\Component', __DIR__.'/component');
22
+ * $loader->add('Symfony', __DIR__.'/framework');
23
+ *
24
+ * // activate the autoloader
25
+ * $loader->register();
26
+ *
27
+ * // to enable searching the include path (eg. for PEAR packages)
28
+ * $loader->setUseIncludePath(true);
29
+ *
30
+ * In this example, if you try to use a class in the Symfony\Component
31
+ * namespace or one of its children (Symfony\Component\Console for instance),
32
+ * the autoloader will first look for the class under the component/
33
+ * directory, and it will then fallback to the framework/ directory if not
34
+ * found before giving up.
35
+ *
36
+ * This class is loosely based on the Symfony UniversalClassLoader.
37
+ *
38
+ * @author Fabien Potencier <fabien@symfony.com>
39
+ * @author Jordi Boggiano <j.boggiano@seld.be>
40
+ * @see https://www.php-fig.org/psr/psr-0/
41
+ * @see https://www.php-fig.org/psr/psr-4/
42
+ */
43
+ class ClassLoader
44
+ {
45
+ private $vendorDir;
46
+
47
+ // PSR-4
48
+ private $prefixLengthsPsr4 = array();
49
+ private $prefixDirsPsr4 = array();
50
+ private $fallbackDirsPsr4 = array();
51
+
52
+ // PSR-0
53
+ private $prefixesPsr0 = array();
54
+ private $fallbackDirsPsr0 = array();
55
+
56
+ private $useIncludePath = false;
57
+ private $classMap = array();
58
+ private $classMapAuthoritative = false;
59
+ private $missingClasses = array();
60
+ private $apcuPrefix;
61
+
62
+ private static $registeredLoaders = array();
63
+
64
+ public function __construct($vendorDir = null)
65
+ {
66
+ $this->vendorDir = $vendorDir;
67
+ }
68
+
69
+ public function getPrefixes()
70
+ {
71
+ if (!empty($this->prefixesPsr0)) {
72
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
73
+ }
74
+
75
+ return array();
76
+ }
77
+
78
+ public function getPrefixesPsr4()
79
+ {
80
+ return $this->prefixDirsPsr4;
81
+ }
82
+
83
+ public function getFallbackDirs()
84
+ {
85
+ return $this->fallbackDirsPsr0;
86
+ }
87
+
88
+ public function getFallbackDirsPsr4()
89
+ {
90
+ return $this->fallbackDirsPsr4;
91
+ }
92
+
93
+ public function getClassMap()
94
+ {
95
+ return $this->classMap;
96
+ }
97
+
98
+ /**
99
+ * @param array $classMap Class to filename map
100
+ */
101
+ public function addClassMap(array $classMap)
102
+ {
103
+ if ($this->classMap) {
104
+ $this->classMap = array_merge($this->classMap, $classMap);
105
+ } else {
106
+ $this->classMap = $classMap;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Registers a set of PSR-0 directories for a given prefix, either
112
+ * appending or prepending to the ones previously set for this prefix.
113
+ *
114
+ * @param string $prefix The prefix
115
+ * @param array|string $paths The PSR-0 root directories
116
+ * @param bool $prepend Whether to prepend the directories
117
+ */
118
+ public function add($prefix, $paths, $prepend = false)
119
+ {
120
+ if (!$prefix) {
121
+ if ($prepend) {
122
+ $this->fallbackDirsPsr0 = array_merge(
123
+ (array) $paths,
124
+ $this->fallbackDirsPsr0
125
+ );
126
+ } else {
127
+ $this->fallbackDirsPsr0 = array_merge(
128
+ $this->fallbackDirsPsr0,
129
+ (array) $paths
130
+ );
131
+ }
132
+
133
+ return;
134
+ }
135
+
136
+ $first = $prefix[0];
137
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
138
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
139
+
140
+ return;
141
+ }
142
+ if ($prepend) {
143
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
144
+ (array) $paths,
145
+ $this->prefixesPsr0[$first][$prefix]
146
+ );
147
+ } else {
148
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
149
+ $this->prefixesPsr0[$first][$prefix],
150
+ (array) $paths
151
+ );
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Registers a set of PSR-4 directories for a given namespace, either
157
+ * appending or prepending to the ones previously set for this namespace.
158
+ *
159
+ * @param string $prefix The prefix/namespace, with trailing '\\'
160
+ * @param array|string $paths The PSR-4 base directories
161
+ * @param bool $prepend Whether to prepend the directories
162
+ *
163
+ * @throws \InvalidArgumentException
164
+ */
165
+ public function addPsr4($prefix, $paths, $prepend = false)
166
+ {
167
+ if (!$prefix) {
168
+ // Register directories for the root namespace.
169
+ if ($prepend) {
170
+ $this->fallbackDirsPsr4 = array_merge(
171
+ (array) $paths,
172
+ $this->fallbackDirsPsr4
173
+ );
174
+ } else {
175
+ $this->fallbackDirsPsr4 = array_merge(
176
+ $this->fallbackDirsPsr4,
177
+ (array) $paths
178
+ );
179
+ }
180
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
181
+ // Register directories for a new namespace.
182
+ $length = strlen($prefix);
183
+ if ('\\' !== $prefix[$length - 1]) {
184
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
185
+ }
186
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
187
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
188
+ } elseif ($prepend) {
189
+ // Prepend directories for an already registered namespace.
190
+ $this->prefixDirsPsr4[$prefix] = array_merge(
191
+ (array) $paths,
192
+ $this->prefixDirsPsr4[$prefix]
193
+ );
194
+ } else {
195
+ // Append directories for an already registered namespace.
196
+ $this->prefixDirsPsr4[$prefix] = array_merge(
197
+ $this->prefixDirsPsr4[$prefix],
198
+ (array) $paths
199
+ );
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Registers a set of PSR-0 directories for a given prefix,
205
+ * replacing any others previously set for this prefix.
206
+ *
207
+ * @param string $prefix The prefix
208
+ * @param array|string $paths The PSR-0 base directories
209
+ */
210
+ public function set($prefix, $paths)
211
+ {
212
+ if (!$prefix) {
213
+ $this->fallbackDirsPsr0 = (array) $paths;
214
+ } else {
215
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Registers a set of PSR-4 directories for a given namespace,
221
+ * replacing any others previously set for this namespace.
222
+ *
223
+ * @param string $prefix The prefix/namespace, with trailing '\\'
224
+ * @param array|string $paths The PSR-4 base directories
225
+ *
226
+ * @throws \InvalidArgumentException
227
+ */
228
+ public function setPsr4($prefix, $paths)
229
+ {
230
+ if (!$prefix) {
231
+ $this->fallbackDirsPsr4 = (array) $paths;
232
+ } else {
233
+ $length = strlen($prefix);
234
+ if ('\\' !== $prefix[$length - 1]) {
235
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
236
+ }
237
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
238
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Turns on searching the include path for class files.
244
+ *
245
+ * @param bool $useIncludePath
246
+ */
247
+ public function setUseIncludePath($useIncludePath)
248
+ {
249
+ $this->useIncludePath = $useIncludePath;
250
+ }
251
+
252
+ /**
253
+ * Can be used to check if the autoloader uses the include path to check
254
+ * for classes.
255
+ *
256
+ * @return bool
257
+ */
258
+ public function getUseIncludePath()
259
+ {
260
+ return $this->useIncludePath;
261
+ }
262
+
263
+ /**
264
+ * Turns off searching the prefix and fallback directories for classes
265
+ * that have not been registered with the class map.
266
+ *
267
+ * @param bool $classMapAuthoritative
268
+ */
269
+ public function setClassMapAuthoritative($classMapAuthoritative)
270
+ {
271
+ $this->classMapAuthoritative = $classMapAuthoritative;
272
+ }
273
+
274
+ /**
275
+ * Should class lookup fail if not found in the current class map?
276
+ *
277
+ * @return bool
278
+ */
279
+ public function isClassMapAuthoritative()
280
+ {
281
+ return $this->classMapAuthoritative;
282
+ }
283
+
284
+ /**
285
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
286
+ *
287
+ * @param string|null $apcuPrefix
288
+ */
289
+ public function setApcuPrefix($apcuPrefix)
290
+ {
291
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
292
+ }
293
+
294
+ /**
295
+ * The APCu prefix in use, or null if APCu caching is not enabled.
296
+ *
297
+ * @return string|null
298
+ */
299
+ public function getApcuPrefix()
300
+ {
301
+ return $this->apcuPrefix;
302
+ }
303
+
304
+ /**
305
+ * Registers this instance as an autoloader.
306
+ *
307
+ * @param bool $prepend Whether to prepend the autoloader or not
308
+ */
309
+ public function register($prepend = false)
310
+ {
311
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
312
+
313
+ if (null === $this->vendorDir) {
314
+ return;
315
+ }
316
+
317
+ if ($prepend) {
318
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
319
+ } else {
320
+ unset(self::$registeredLoaders[$this->vendorDir]);
321
+ self::$registeredLoaders[$this->vendorDir] = $this;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Unregisters this instance as an autoloader.
327
+ */
328
+ public function unregister()
329
+ {
330
+ spl_autoload_unregister(array($this, 'loadClass'));
331
+
332
+ if (null !== $this->vendorDir) {
333
+ unset(self::$registeredLoaders[$this->vendorDir]);
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Loads the given class or interface.
339
+ *
340
+ * @param string $class The name of the class
341
+ * @return true|null True if loaded, null otherwise
342
+ */
343
+ public function loadClass($class)
344
+ {
345
+ if ($file = $this->findFile($class)) {
346
+ includeFile($file);
347
+
348
+ return true;
349
+ }
350
+
351
+ return null;
352
+ }
353
+
354
+ /**
355
+ * Finds the path to the file where the class is defined.
356
+ *
357
+ * @param string $class The name of the class
358
+ *
359
+ * @return string|false The path if found, false otherwise
360
+ */
361
+ public function findFile($class)
362
+ {
363
+ // class map lookup
364
+ if (isset($this->classMap[$class])) {
365
+ return $this->classMap[$class];
366
+ }
367
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
368
+ return false;
369
+ }
370
+ if (null !== $this->apcuPrefix) {
371
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
372
+ if ($hit) {
373
+ return $file;
374
+ }
375
+ }
376
+
377
+ $file = $this->findFileWithExtension($class, '.php');
378
+
379
+ // Search for Hack files if we are running on HHVM
380
+ if (false === $file && defined('HHVM_VERSION')) {
381
+ $file = $this->findFileWithExtension($class, '.hh');
382
+ }
383
+
384
+ if (null !== $this->apcuPrefix) {
385
+ apcu_add($this->apcuPrefix.$class, $file);
386
+ }
387
+
388
+ if (false === $file) {
389
+ // Remember that this class does not exist.
390
+ $this->missingClasses[$class] = true;
391
+ }
392
+
393
+ return $file;
394
+ }
395
+
396
+ /**
397
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
398
+ *
399
+ * @return self[]
400
+ */
401
+ public static function getRegisteredLoaders()
402
+ {
403
+ return self::$registeredLoaders;
404
+ }
405
+
406
+ private function findFileWithExtension($class, $ext)
407
+ {
408
+ // PSR-4 lookup
409
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
410
+
411
+ $first = $class[0];
412
+ if (isset($this->prefixLengthsPsr4[$first])) {
413
+ $subPath = $class;
414
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
415
+ $subPath = substr($subPath, 0, $lastPos);
416
+ $search = $subPath . '\\';
417
+ if (isset($this->prefixDirsPsr4[$search])) {
418
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
419
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
420
+ if (file_exists($file = $dir . $pathEnd)) {
421
+ return $file;
422
+ }
423
+ }
424
+ }
425
+ }
426
+ }
427
+
428
+ // PSR-4 fallback dirs
429
+ foreach ($this->fallbackDirsPsr4 as $dir) {
430
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
431
+ return $file;
432
+ }
433
+ }
434
+
435
+ // PSR-0 lookup
436
+ if (false !== $pos = strrpos($class, '\\')) {
437
+ // namespaced class name
438
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
439
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
440
+ } else {
441
+ // PEAR-like class name
442
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
443
+ }
444
+
445
+ if (isset($this->prefixesPsr0[$first])) {
446
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
447
+ if (0 === strpos($class, $prefix)) {
448
+ foreach ($dirs as $dir) {
449
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
450
+ return $file;
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+
457
+ // PSR-0 fallback dirs
458
+ foreach ($this->fallbackDirsPsr0 as $dir) {
459
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
460
+ return $file;
461
+ }
462
+ }
463
+
464
+ // PSR-0 include paths.
465
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
466
+ return $file;
467
+ }
468
+
469
+ return false;
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Scope isolated include.
475
+ *
476
+ * Prevents access to $this/self from included files.
477
+ */
478
+ function includeFile($file)
479
+ {
480
+ include $file;
481
+ }
vendor/composer/InstalledVersions.php ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer;
14
+
15
+ use Composer\Autoload\ClassLoader;
16
+ use Composer\Semver\VersionParser;
17
+
18
+ /**
19
+ * This class is copied in every Composer installed project and available to all
20
+ *
21
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
22
+ *
23
+ * To require it's presence, you can require `composer-runtime-api ^2.0`
24
+ */
25
+ class InstalledVersions
26
+ {
27
+ private static $installed;
28
+ private static $canGetVendors;
29
+ private static $installedByVendor = array();
30
+
31
+ /**
32
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
33
+ *
34
+ * @return string[]
35
+ * @psalm-return list<string>
36
+ */
37
+ public static function getInstalledPackages()
38
+ {
39
+ $packages = array();
40
+ foreach (self::getInstalled() as $installed) {
41
+ $packages[] = array_keys($installed['versions']);
42
+ }
43
+
44
+ if (1 === \count($packages)) {
45
+ return $packages[0];
46
+ }
47
+
48
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
49
+ }
50
+
51
+ /**
52
+ * Returns a list of all package names with a specific type e.g. 'library'
53
+ *
54
+ * @param string $type
55
+ * @return string[]
56
+ * @psalm-return list<string>
57
+ */
58
+ public static function getInstalledPackagesByType($type)
59
+ {
60
+ $packagesByType = array();
61
+
62
+ foreach (self::getInstalled() as $installed) {
63
+ foreach ($installed['versions'] as $name => $package) {
64
+ if (isset($package['type']) && $package['type'] === $type) {
65
+ $packagesByType[] = $name;
66
+ }
67
+ }
68
+ }
69
+
70
+ return $packagesByType;
71
+ }
72
+
73
+ /**
74
+ * Checks whether the given package is installed
75
+ *
76
+ * This also returns true if the package name is provided or replaced by another package
77
+ *
78
+ * @param string $packageName
79
+ * @param bool $includeDevRequirements
80
+ * @return bool
81
+ */
82
+ public static function isInstalled($packageName, $includeDevRequirements = true)
83
+ {
84
+ foreach (self::getInstalled() as $installed) {
85
+ if (isset($installed['versions'][$packageName])) {
86
+ return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
87
+ }
88
+ }
89
+
90
+ return false;
91
+ }
92
+
93
+ /**
94
+ * Checks whether the given package satisfies a version constraint
95
+ *
96
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
97
+ *
98
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
99
+ *
100
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
101
+ * @param string $packageName
102
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
103
+ * @return bool
104
+ */
105
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
106
+ {
107
+ $constraint = $parser->parseConstraints($constraint);
108
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
109
+
110
+ return $provided->matches($constraint);
111
+ }
112
+
113
+ /**
114
+ * Returns a version constraint representing all the range(s) which are installed for a given package
115
+ *
116
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
117
+ * whether a given version of a package is installed, and not just whether it exists
118
+ *
119
+ * @param string $packageName
120
+ * @return string Version constraint usable with composer/semver
121
+ */
122
+ public static function getVersionRanges($packageName)
123
+ {
124
+ foreach (self::getInstalled() as $installed) {
125
+ if (!isset($installed['versions'][$packageName])) {
126
+ continue;
127
+ }
128
+
129
+ $ranges = array();
130
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
131
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
132
+ }
133
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
134
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
135
+ }
136
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
137
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
138
+ }
139
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
140
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
141
+ }
142
+
143
+ return implode(' || ', $ranges);
144
+ }
145
+
146
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
147
+ }
148
+
149
+ /**
150
+ * @param string $packageName
151
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
152
+ */
153
+ public static function getVersion($packageName)
154
+ {
155
+ foreach (self::getInstalled() as $installed) {
156
+ if (!isset($installed['versions'][$packageName])) {
157
+ continue;
158
+ }
159
+
160
+ if (!isset($installed['versions'][$packageName]['version'])) {
161
+ return null;
162
+ }
163
+
164
+ return $installed['versions'][$packageName]['version'];
165
+ }
166
+
167
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
168
+ }
169
+
170
+ /**
171
+ * @param string $packageName
172
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
173
+ */
174
+ public static function getPrettyVersion($packageName)
175
+ {
176
+ foreach (self::getInstalled() as $installed) {
177
+ if (!isset($installed['versions'][$packageName])) {
178
+ continue;
179
+ }
180
+
181
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
182
+ return null;
183
+ }
184
+
185
+ return $installed['versions'][$packageName]['pretty_version'];
186
+ }
187
+
188
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
189
+ }
190
+
191
+ /**
192
+ * @param string $packageName
193
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
194
+ */
195
+ public static function getReference($packageName)
196
+ {
197
+ foreach (self::getInstalled() as $installed) {
198
+ if (!isset($installed['versions'][$packageName])) {
199
+ continue;
200
+ }
201
+
202
+ if (!isset($installed['versions'][$packageName]['reference'])) {
203
+ return null;
204
+ }
205
+
206
+ return $installed['versions'][$packageName]['reference'];
207
+ }
208
+
209
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
210
+ }
211
+
212
+ /**
213
+ * @param string $packageName
214
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
215
+ */
216
+ public static function getInstallPath($packageName)
217
+ {
218
+ foreach (self::getInstalled() as $installed) {
219
+ if (!isset($installed['versions'][$packageName])) {
220
+ continue;
221
+ }
222
+
223
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
224
+ }
225
+
226
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
227
+ }
228
+
229
+ /**
230
+ * @return array
231
+ * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}
232
+ */
233
+ public static function getRootPackage()
234
+ {
235
+ $installed = self::getInstalled();
236
+
237
+ return $installed[0]['root'];
238
+ }
239
+
240
+ /**
241
+ * Returns the raw installed.php data for custom implementations
242
+ *
243
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
244
+ * @return array[]
245
+ * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}
246
+ */
247
+ public static function getRawData()
248
+ {
249
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
250
+
251
+ if (null === self::$installed) {
252
+ // only require the installed.php file if this file is loaded from its dumped location,
253
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
254
+ if (substr(__DIR__, -8, 1) !== 'C') {
255
+ self::$installed = include __DIR__ . '/installed.php';
256
+ } else {
257
+ self::$installed = array();
258
+ }
259
+ }
260
+
261
+ return self::$installed;
262
+ }
263
+
264
+ /**
265
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
266
+ *
267
+ * @return array[]
268
+ * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
269
+ */
270
+ public static function getAllRawData()
271
+ {
272
+ return self::getInstalled();
273
+ }
274
+
275
+ /**
276
+ * Lets you reload the static array from another file
277
+ *
278
+ * This is only useful for complex integrations in which a project needs to use
279
+ * this class but then also needs to execute another project's autoloader in process,
280
+ * and wants to ensure both projects have access to their version of installed.php.
281
+ *
282
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
283
+ * the data it needs from this class, then call reload() with
284
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
285
+ * the project in which it runs can then also use this class safely, without
286
+ * interference between PHPUnit's dependencies and the project's dependencies.
287
+ *
288
+ * @param array[] $data A vendor/composer/installed.php data set
289
+ * @return void
290
+ *
291
+ * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data
292
+ */
293
+ public static function reload($data)
294
+ {
295
+ self::$installed = $data;
296
+ self::$installedByVendor = array();
297
+ }
298
+
299
+ /**
300
+ * @return array[]
301
+ * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
302
+ */
303
+ private static function getInstalled()
304
+ {
305
+ if (null === self::$canGetVendors) {
306
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
307
+ }
308
+
309
+ $installed = array();
310
+
311
+ if (self::$canGetVendors) {
312
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
313
+ if (isset(self::$installedByVendor[$vendorDir])) {
314
+ $installed[] = self::$installedByVendor[$vendorDir];
315
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
316
+ $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
317
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
318
+ self::$installed = $installed[count($installed) - 1];
319
+ }
320
+ }
321
+ }
322
+ }
323
+
324
+ if (null === self::$installed) {
325
+ // only require the installed.php file if this file is loaded from its dumped location,
326
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
327
+ if (substr(__DIR__, -8, 1) !== 'C') {
328
+ self::$installed = require __DIR__ . '/installed.php';
329
+ } else {
330
+ self::$installed = array();
331
+ }
332
+ }
333
+ $installed[] = self::$installed;
334
+
335
+ return $installed;
336
+ }
337
+ }
vendor/composer/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Copyright (c) Nils Adermann, Jordi Boggiano
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished
9
+ to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
vendor/composer/autoload_classmap.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_classmap.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
10
+ );
vendor/composer/autoload_namespaces.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_namespaces.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_psr4.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_psr4.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'DiDom\\' => array($vendorDir . '/imangazaliev/didom/src/DiDom'),
10
+ );
vendor/composer/autoload_real.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_real.php @generated by Composer
4
+
5
+ class ComposerAutoloaderInit811f57b3d5528d9c8defd6995d70c7b0
6
+ {
7
+ private static $loader;
8
+
9
+ public static function loadClassLoader($class)
10
+ {
11
+ if ('Composer\Autoload\ClassLoader' === $class) {
12
+ require __DIR__ . '/ClassLoader.php';
13
+ }
14
+ }
15
+
16
+ /**
17
+ * @return \Composer\Autoload\ClassLoader
18
+ */
19
+ public static function getLoader()
20
+ {
21
+ if (null !== self::$loader) {
22
+ return self::$loader;
23
+ }
24
+
25
+ require __DIR__ . '/platform_check.php';
26
+
27
+ spl_autoload_register(array('ComposerAutoloaderInit811f57b3d5528d9c8defd6995d70c7b0', 'loadClassLoader'), true, true);
28
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
29
+ spl_autoload_unregister(array('ComposerAutoloaderInit811f57b3d5528d9c8defd6995d70c7b0', 'loadClassLoader'));
30
+
31
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
32
+ if ($useStaticLoader) {
33
+ require __DIR__ . '/autoload_static.php';
34
+
35
+ call_user_func(\Composer\Autoload\ComposerStaticInit811f57b3d5528d9c8defd6995d70c7b0::getInitializer($loader));
36
+ } else {
37
+ $map = require __DIR__ . '/autoload_namespaces.php';
38
+ foreach ($map as $namespace => $path) {
39
+ $loader->set($namespace, $path);
40
+ }
41
+
42
+ $map = require __DIR__ . '/autoload_psr4.php';
43
+ foreach ($map as $namespace => $path) {
44
+ $loader->setPsr4($namespace, $path);
45
+ }
46
+
47
+ $classMap = require __DIR__ . '/autoload_classmap.php';
48
+ if ($classMap) {
49
+ $loader->addClassMap($classMap);
50
+ }
51
+ }
52
+
53
+ $loader->register(true);
54
+
55
+ return $loader;
56
+ }
57
+ }
vendor/composer/autoload_static.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_static.php @generated by Composer
4
+
5
+ namespace Composer\Autoload;
6
+
7
+ class ComposerStaticInit811f57b3d5528d9c8defd6995d70c7b0
8
+ {
9
+ public static $prefixLengthsPsr4 = array (
10
+ 'D' =>
11
+ array (
12
+ 'DiDom\\' => 6,
13
+ ),
14
+ );
15
+
16
+ public static $prefixDirsPsr4 = array (
17
+ 'DiDom\\' =>
18
+ array (
19
+ 0 => __DIR__ . '/..' . '/imangazaliev/didom/src/DiDom',
20
+ ),
21
+ );
22
+
23
+ public static $classMap = array (
24
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
25
+ );
26
+
27
+ public static function getInitializer(ClassLoader $loader)
28
+ {
29
+ return \Closure::bind(function () use ($loader) {
30
+ $loader->prefixLengthsPsr4 = ComposerStaticInit811f57b3d5528d9c8defd6995d70c7b0::$prefixLengthsPsr4;
31
+ $loader->prefixDirsPsr4 = ComposerStaticInit811f57b3d5528d9c8defd6995d70c7b0::$prefixDirsPsr4;
32
+ $loader->classMap = ComposerStaticInit811f57b3d5528d9c8defd6995d70c7b0::$classMap;
33
+
34
+ }, null, ClassLoader::class);
35
+ }
36
+ }
vendor/composer/installed.json ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "packages": [
3
+ {
4
+ "name": "imangazaliev/didom",
5
+ "version": "1.18",
6
+ "version_normalized": "1.18.0.0",
7
+ "source": {
8
+ "type": "git",
9
+ "url": "https://github.com/Imangazaliev/DiDOM.git",
10
+ "reference": "346db1ea94a0f6ead225c2358af770bf33659cf7"
11
+ },
12
+ "dist": {
13
+ "type": "zip",
14
+ "url": "https://api.github.com/repos/Imangazaliev/DiDOM/zipball/346db1ea94a0f6ead225c2358af770bf33659cf7",
15
+ "reference": "346db1ea94a0f6ead225c2358af770bf33659cf7",
16
+ "shasum": ""
17
+ },
18
+ "require": {
19
+ "ext-dom": "*",
20
+ "ext-iconv": "*",
21
+ "php": ">=5.4"
22
+ },
23
+ "require-dev": {
24
+ "phpunit/phpunit": "^4.8"
25
+ },
26
+ "time": "2021-07-27T18:50:53+00:00",
27
+ "type": "library",
28
+ "installation-source": "dist",
29
+ "autoload": {
30
+ "psr-4": {
31
+ "DiDom\\": "src/DiDom/"
32
+ }
33
+ },
34
+ "notification-url": "https://packagist.org/downloads/",
35
+ "license": [
36
+ "MIT"
37
+ ],
38
+ "authors": [
39
+ {
40
+ "name": "Imangazaliev Muhammad",
41
+ "email": "imangazalievm@gmail.com"
42
+ }
43
+ ],
44
+ "description": "Simple and fast HTML parser",
45
+ "homepage": "https://github.com/Imangazaliev/DiDOM",
46
+ "keywords": [
47
+ "didom",
48
+ "html",
49
+ "parser",
50
+ "xml"
51
+ ],
52
+ "support": {
53
+ "issues": "https://github.com/Imangazaliev/DiDOM/issues",
54
+ "source": "https://github.com/Imangazaliev/DiDOM/tree/1.18"
55
+ },
56
+ "install-path": "../imangazaliev/didom"
57
+ }
58
+ ],
59
+ "dev": true,
60
+ "dev-package-names": []
61
+ }
vendor/composer/installed.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php return array(
2
+ 'root' => array(
3
+ 'pretty_version' => 'dev-master',
4
+ 'version' => 'dev-master',
5
+ 'type' => 'library',
6
+ 'install_path' => __DIR__ . '/../../',
7
+ 'aliases' => array(),
8
+ 'reference' => '00a6ddf750efcd55313c22c9c0dbdd37b09b38e2',
9
+ 'name' => '__root__',
10
+ 'dev' => true,
11
+ ),
12
+ 'versions' => array(
13
+ '__root__' => array(
14
+ 'pretty_version' => 'dev-master',
15
+ 'version' => 'dev-master',
16
+ 'type' => 'library',
17
+ 'install_path' => __DIR__ . '/../../',
18
+ 'aliases' => array(),
19
+ 'reference' => '00a6ddf750efcd55313c22c9c0dbdd37b09b38e2',
20
+ 'dev_requirement' => false,
21
+ ),
22
+ 'imangazaliev/didom' => array(
23
+ 'pretty_version' => '1.18',
24
+ 'version' => '1.18.0.0',
25
+ 'type' => 'library',
26
+ 'install_path' => __DIR__ . '/../imangazaliev/didom',
27
+ 'aliases' => array(),
28
+ 'reference' => '346db1ea94a0f6ead225c2358af770bf33659cf7',
29
+ 'dev_requirement' => false,
30
+ ),
31
+ ),
32
+ );
vendor/composer/platform_check.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // platform_check.php @generated by Composer
4
+
5
+ $issues = array();
6
+
7
+ if (!(PHP_VERSION_ID >= 50400)) {
8
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 5.4.0". You are running ' . PHP_VERSION . '.';
9
+ }
10
+
11
+ if ($issues) {
12
+ if (!headers_sent()) {
13
+ header('HTTP/1.1 500 Internal Server Error');
14
+ }
15
+ if (!ini_get('display_errors')) {
16
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
17
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
18
+ } elseif (!headers_sent()) {
19
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
20
+ }
21
+ }
22
+ trigger_error(
23
+ 'Composer detected issues in your platform: ' . implode(' ', $issues),
24
+ E_USER_ERROR
25
+ );
26
+ }
vendor/imangazaliev/didom/CHANGELOG.md ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### 1.13
2
+
3
+ - Add `Element::outerHtml()` method
4
+ - Add `Element::prependChild()` method
5
+ - Add `Element::insertBefore()` and `Element::insertAfter()` methods
6
+ - Add `Element::style()` method for more convenient inline styles manipulation
7
+ - Add `Element::classes()` method for more convenient class manipulation
8
+
9
+ ### 1.12
10
+
11
+ - Many fixes and improvements
12
+
13
+ ### 1.11.1
14
+
15
+ - Fix bug with unregistered PHP functions in XPath in `Document::has()` and `Document::count()` methods
16
+
17
+ ### 1.11
18
+
19
+ - Add `Element::isElementNode()` method
20
+ - Add ability to retrieve only specific attributes in `Element::attributes()` method
21
+ - Add `Element::removeAllAttributes()` method
22
+ - Add ability to specify selector and node type in `Element::previousSibling()` and `Element::nextSibling()` methods
23
+ - Add `Element::previousSiblings()` and `Element::nextSiblings()` methods
24
+ - Many minor fixes and improvements
25
+
26
+ ### 1.10.6
27
+
28
+ - Fix bug with XML document loading
29
+
30
+ ### v1.10.5
31
+
32
+ - Fix issue #85
33
+
34
+ ### 1.10.4
35
+
36
+ - Use `mb_convert_encoding()` in the Encoder if it is available
37
+
38
+ ### v1.10.3
39
+
40
+ - Add `Element::removeChild()` and `Element::removeChildren()` methods
41
+ - Fix bug in `Element::matches()` method
42
+ - `Element::matches()` method now returns false if node is not `DOMElement`
43
+ - Add `Element::hasChildren()` method
44
+
45
+ ### 1.10.2
46
+
47
+ - Fix bug in setInnerHtml: can't rewrite existing content
48
+ - Throw `InvalidSelectorException` instead of `InvalidArgumentException` when selector is empty
49
+
50
+ ### 1.10.1
51
+
52
+ - Fix attributes `ends-with` XPath
53
+ - Method `Element::matches()` now can check children nodes
54
+
55
+ ### 1.10
56
+
57
+ - Fix HTML saving mechanism
58
+ - Throw `InvalidSelectorException` instead of `RuntimeException` in Query class
59
+
60
+ ### 1.9.1
61
+
62
+ - Add ability to search in owner document using current node as context
63
+ - Bugs fixed
64
+
65
+ ### 1.9.0
66
+
67
+ - Methods `Document::appendChild()` and `Element::appendChild()` now return appended node(s)
68
+ - Add ability to search elements in context
69
+
70
+ ### 1.8.8
71
+
72
+ - Bugs fixed
73
+
74
+ ### 1.8.7
75
+
76
+ - Add `Element::getLineNo()` method
77
+
78
+ ### 1.8.6
79
+
80
+ - Fix issue #55
81
+
82
+ ### 1.8.5
83
+
84
+ - Add support of `DOMComment`
85
+
86
+ ### 1.8.4
87
+
88
+ - Add ability to create an element by selector
89
+ - Add closest method
90
+
91
+ ### 1.8.3
92
+
93
+ - Add method `Element::isTextNode()`
94
+ - Many minor fixes
95
+
96
+ ### 1.8.2
97
+
98
+ - Add ability to check that element matches selector
99
+ - Add ability counting nodes by selector
100
+ - Many minor fixes
101
+
102
+ ### 1.8.1
103
+
104
+ - Small fix
105
+
106
+ ### 1.8
107
+
108
+ - Bug fixes
109
+ - Add support of ~ selector
110
+ - Add ability to direct search by CSS selector
111
+ - Add setInnerHtml method
112
+ - Add attributes method
113
+
114
+ ### 1.7.4
115
+
116
+ - Add support of text nodes
117
+
118
+ ### 1.7.3
119
+
120
+ - Bug fix
121
+
122
+ ### 1.7.2
123
+
124
+ - Fixed behavior of nth-child pseudo class
125
+ - Add nth-of-type pseudo class
126
+
127
+ ### 1.7.1
128
+
129
+ - Add pseudo class has and more attribute options
130
+
131
+ ### 1.7.0
132
+
133
+ - Bug fixes
134
+ - Add methods `previousSibling`, `nextSibling`, `child`, `firstChild`, `lastChild`, `children`, `getDocument` to the Element
135
+ - Changed behavior of parent method. Now it returns parent node instead of owner document
136
+
137
+ ### 1.6.8
138
+
139
+ - Bug fix
140
+
141
+ ### 1.6.5
142
+
143
+ - Added ability to get an element attribute by CSS selector
144
+
145
+ ### 1.6.4
146
+
147
+ - Added handling of `DOMText` and `DOMAttr` in `Document::find()`
148
+
149
+ ### 1.6.3
150
+
151
+ - Added ability to get inner HTML
152
+
153
+ ### 1.6.2
154
+
155
+ - Added the ability to pass options when load HTML or XML
156
+
157
+ ### 1.6.1
158
+
159
+ - Added the ability to pass an array of nodes to appendChild
160
+ - Added the ability to pass options when converting to HTML or XML
161
+ - Added the ability to add child elements to the element
162
+
163
+ ### 1.6
164
+
165
+ - Added support for XML
166
+ - Added the ability to search element by part of attribute name or value
167
+ - Added support for pseudo-class "contains"
168
+ - Added the ability to clone a node
169
+
170
+ ### 1.5.1
171
+
172
+ - Added ability to remove and replace nodes
173
+ - Added ability to specify encoding when converting the element into the document
174
+
175
+ ### 1.5
176
+
177
+ - Fixed problem with incorrect encoding
178
+ - Added ability to set the value of the element
179
+ - Added ability to specify encoding when creating document
180
+
181
+ ### 1.4
182
+
183
+ - Added the ability to specify the return type element (`DiDom\Element` or `DOMElement`)
184
+
185
+ ### 1.3.2
186
+
187
+ - Bug fixed
188
+
189
+ ### 1.3.1
190
+
191
+ - Bugs fixed
192
+ - Added the ability to pass element attributes in the constructor
193
+
194
+ ### 1.3
195
+
196
+ - Bugs fixed
197
+
198
+ ### 1.2
199
+
200
+ - Bugs fixed
201
+ - Added the ability to compare Element\Document
202
+ - Added the ability to format HTML code of the document when outputting
203
+
204
+ ### 1.1
205
+
206
+ - Added cache control
207
+ - Converter from CSS to XPath replaced by faster
208
+
209
+ ### 1.0
210
+
211
+ - First release
vendor/imangazaliev/didom/LICENSE ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2015 Muhammad Imangazaliev
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is furnished
8
+ to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
vendor/imangazaliev/didom/README-RU.md ADDED
@@ -0,0 +1,833 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DiDOM
2
+
3
+ [![Build Status](https://travis-ci.org/Imangazaliev/DiDOM.svg?branch=master)](https://travis-ci.org/Imangazaliev/DiDOM)
4
+ [![Total Downloads](https://poser.pugx.org/imangazaliev/didom/downloads)](https://packagist.org/packages/imangazaliev/didom)
5
+ [![Latest Stable Version](https://poser.pugx.org/imangazaliev/didom/v/stable)](https://packagist.org/packages/imangazaliev/didom)
6
+ [![License](https://poser.pugx.org/imangazaliev/didom/license)](https://packagist.org/packages/imangazaliev/didom)
7
+
8
+ [English version](README.md)
9
+
10
+ DiDOM - простая и быстрая библиотека для парсинга HTML.
11
+
12
+ ## Содержание
13
+
14
+ - [Установка](#Установка)
15
+ - [Быстрый старт](#Быстрый-старт)
16
+ - [Создание нового документа](#Создание-нового-документа)
17
+ - [Поиск элементов](#Поиск-элементов)
18
+ - [Проверка наличия элемента](#Проверка-наличия-элемента)
19
+ - [Подсчет количества элементов](#Подсчет-количества-элементов)
20
+ - [Поиск в элементе](#Поиск-в-элементе)
21
+ - [Поддерживамые селекторы](#Поддерживамые-селекторы)
22
+ - [Изменение содержимого](#Изменение-содержимого)
23
+ - [Вывод содержимого](#Вывод-содержимого)
24
+ - [Работа с элементами](#Работа-с-элементами)
25
+ - [Создание нового элемента](#Создание-нового-элемента)
26
+ - [Получение названия элемента](#Получение-названия-элемента)
27
+ - [Получение родительского элемента](#Получение-родительского-элемента)
28
+ - [Получение соседних элементов](#Получение-соседних-элементов)
29
+ - [Получение дочерних элементов](#Получение-соседних-элементов)
30
+ - [Получение документа](#Получение-документа)
31
+ - [Работа с атрибутами элемента](#Работа-с-атрибутами-элемента)
32
+ - [Сравнение элементов](#Сравнение-элементов)
33
+ - [Добавление дочерних элементов](#Добавление-дочерних-элементов)
34
+ - [Замена элемента](#Замена-элемента)
35
+ - [Удаление элемента](#Удаление-элемента)
36
+ - [Работа с кэшем](#Работа-с-кэшем)
37
+ - [Прочее](#Прочее)
38
+ - [Сравнение с другими парсерами](#Сравнение-с-другими-парсерами)
39
+
40
+ ## Установка
41
+
42
+ Для установки DiDOM выполните команду:
43
+
44
+ composer require imangazaliev/didom
45
+
46
+ ## Быстрый старт
47
+
48
+ ```php
49
+ use DiDom\Document;
50
+
51
+ $document = new Document('http://www.news.com/', true);
52
+
53
+ $posts = $document->find('.post');
54
+
55
+ foreach($posts as $post) {
56
+ echo $post->text(), "\n";
57
+ }
58
+ ```
59
+
60
+ ## Создание нового документа
61
+
62
+ DiDom позволяет загрузить HTML несколькими способами:
63
+
64
+ ##### Через конструктор
65
+
66
+ ```php
67
+ // в первом параметре передается строка с HTML
68
+ $document = new Document($html);
69
+
70
+ // путь к файлу
71
+ $document = new Document('page.html', true);
72
+
73
+ // или URL
74
+ $document = new Document('http://www.example.com/', true);
75
+
76
+ // также можно создать документ из DOMDocument
77
+ $domDocument = new DOMDocument();
78
+ $document = new Document($domDocument);
79
+ ```
80
+
81
+ Сигнатура:
82
+
83
+ ```php
84
+ __construct($string = null, $isFile = false, $encoding = 'UTF-8', $type = Document::TYPE_HTML)
85
+ ```
86
+
87
+ `$isFile` - указывает, что загружается файл. По умолчанию - `false`.
88
+
89
+ `$encoding` - кодировка документа. По умолчанию - UTF-8.
90
+
91
+ `$type` - тип документа (HTML - `Document::TYPE_HTML`, XML - `Document::TYPE_XML`). По умолчанию - `Document::TYPE_HTML`.
92
+
93
+ ##### Через отдельные методы
94
+
95
+ ```php
96
+ $document = new Document();
97
+
98
+ $document->loadHtml($html);
99
+
100
+ $document->loadHtmlFile('page.html');
101
+
102
+ $document->loadHtmlFile('http://www.example.com/');
103
+ ```
104
+
105
+ Для загрузки XML есть соответствующие методы `loadXml` и `loadXmlFile`.
106
+
107
+ При загрузке документа через эти методы, парсеру можно передать дополнительные [опции](http://php.net/manual/ru/libxml.constants.php):
108
+
109
+ ```php
110
+ $document->loadHtml($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
111
+ $document->loadHtmlFile($url, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
112
+
113
+ $document->loadXml($xml, LIBXML_PARSEHUGE);
114
+ $document->loadXmlFile($url, LIBXML_PARSEHUGE);
115
+ ```
116
+
117
+ ## Поиск элементов
118
+
119
+ В качестве выражения для поиска можно передать CSS-селектор или XPath. Для этого в первом параметре нужно передать само выражение, а во втором - его тип (по умолчанию - `Query::TYPE_CSS`):
120
+
121
+ ##### Через метод `find()`:
122
+
123
+ ```php
124
+ use DiDom\Document;
125
+ use DiDom\Query;
126
+
127
+ ...
128
+
129
+ // CSS-селектор
130
+ $posts = $document->find('.post');
131
+
132
+ // эквивалентно
133
+ $posts = $document->find('.post', Query::TYPE_CSS);
134
+
135
+ // XPath-выражение
136
+ $posts = $document->find("//div[contains(@class, 'post')]", Query::TYPE_XPATH);
137
+ ```
138
+
139
+ Метод вернет массив с элементами (экземпляры класса `DiDom\Element`) или пустой массив, если не найден ни один элемент, соответствующий выражению.
140
+
141
+ При желании можно получить массив узлов без преобразования в Element или текст (`DOMElement`/`DOMText`/`DOMComment`/`DOMAttr`, в зависимости от выражения), для этого необходимо передать в качестве третьего параметра `false`.
142
+
143
+ ##### Через метод `first()`:
144
+
145
+ Возвращает первый найденный элемент или `null`, если не найдено ни одного элемента.
146
+
147
+ Принимает те же параметры, что и метод `find()`.
148
+
149
+ ##### Через магический метод `__invoke()`:
150
+
151
+ ```php
152
+ $posts = $document('.post');
153
+ ```
154
+
155
+ Принимает те же параметры, что и метод `find()`.
156
+
157
+ **Внимание:** использование данного метода нежелательно, т.к. в будущем он может быть удален.
158
+
159
+ ##### Через метод `xpath()`:
160
+
161
+ ```php
162
+ $posts = $document->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]");
163
+ ```
164
+
165
+ ## Проверка наличия элемента
166
+
167
+ Проверить наличие элемента можно с помощью метода `has()`:
168
+
169
+ ```php
170
+ if ($document->has('.post')) {
171
+ // код
172
+ }
173
+ ```
174
+
175
+ Если нужно проверить наличие элемента, а затем получить его, то можно сделать так:
176
+
177
+ ```php
178
+ if ($document->has('.post')) {
179
+ $elements = $document->find('.post');
180
+
181
+ // код
182
+ }
183
+ ```
184
+
185
+ но быстрее так:
186
+
187
+ ```php
188
+ $elements = $document->find('.post');
189
+
190
+ if (count($elements) > 0) {
191
+ // код
192
+ }
193
+ ```
194
+
195
+ т.к. в первом случае выполняется два запроса.
196
+
197
+ ## Подсчет количества элементов
198
+
199
+ Метод `count()` позволяет подсчитать количество дочерних элементов, соотвествующих селектору:
200
+
201
+ ```php
202
+ // выведет количество ссылок в документе
203
+ echo $document->count('a');
204
+ ```
205
+
206
+ ```php
207
+ // выведет количество пунктов в списке
208
+ echo $document->first('ul')->count('> li');
209
+ ```
210
+
211
+ ## Поиск в элементе
212
+
213
+ Методы `find()`, `first()`, `xpath()`, `has()`, `count()` доступны также и для элемента.
214
+
215
+ Пример:
216
+
217
+ ```php
218
+ echo $document->find('nav')[0]->first('ul.menu')->xpath('//li')[0]->text();
219
+ ```
220
+
221
+ #### Метод `findInDocument()`
222
+
223
+ При изменении, замене или удалении элемента, найденного в другом элементе, документ не будет изменен. Данное поведение связано с тем, что в методе `find()` класса `Element` (а, соответственно, и в методах `first()` и `xpath`) создается новый документ, в котором и производится поиск.
224
+
225
+ Для поиска элементов в исходном документе необходимо использовать методы `findInDocument()` и `firstInDocument()`:
226
+
227
+ ```php
228
+ // ничего не выйдет
229
+ $document->first('head')->first('title')->remove();
230
+
231
+ // а вот так да
232
+ $document->first('head')->firstInDocument('title')->remove();
233
+ ```
234
+
235
+ **Внимание:** методы `findInDocument()` и `firstInDocument()` работают только для элементов, которые принадлежат какому-либо документу, либо созданых через `new Element(...)`. Если элемент не принадлежит к какому-либо документу, будет выброшено исключение `LogicException`;
236
+
237
+ ## Поддерживамые селекторы
238
+
239
+ DiDom поддерживает поиск по:
240
+
241
+ - тэгу
242
+ - классу, идентификатору, имени и значению атрибута
243
+ - псевдоклассам:
244
+ - first-, last-, nth-child
245
+ - empty и not-empty
246
+ - contains
247
+ - has
248
+
249
+ ```php
250
+ // все ссылки
251
+ $document->find('a');
252
+
253
+ // любой элемент с id = "foo" и классом "bar"
254
+ $document->find('#foo.bar');
255
+
256
+ // любой элемент, у которого есть атрибут "name"
257
+ $document->find('[name]');
258
+
259
+ // эквивалентно
260
+ $document->find('*[name]');
261
+
262
+ // поле ввода с именем "foo"
263
+ $document->find('input[name=foo]');
264
+ $document->find('input[name=\'foo\']');
265
+ $document->find('input[name="foo"]');
266
+
267
+ // поле ввода с именем "foo" и значением "bar"
268
+ $document->find('input[name="foo"][value="bar"]');
269
+
270
+ // поле ввода, название которого НЕ равно "foo"
271
+ $document->find('input[name!="foo"]');
272
+
273
+ // любой элемент, у которого есть атрибут,
274
+ // начинающийся с "data-" и равный "foo"
275
+ $document->find('*[^data-=foo]');
276
+
277
+ // все ссылки, у которых адрес начинается с https
278
+ $document->find('a[href^=https]');
279
+
280
+ // все изображения с расширением png
281
+ $document->find('img[src$=png]');
282
+
283
+ // все ссылки, содержащие в своем адресе строку "example.com"
284
+ $document->find('a[href*=example.com]');
285
+
286
+ // все ссылки, содержащие в атрибуте data-foo значение bar отделенное пробелом
287
+ $document->find('a[data-foo~=bar]');
288
+
289
+ // текст всех ссылок с классом "foo" (массив строк)
290
+ $document->find('a.foo::text');
291
+
292
+ // эквивалентно
293
+ $document->find('a.foo::text()');
294
+
295
+ // адрес и текст подсказки всех полей с классом "bar"
296
+ $document->find('a.bar::attr(href|title)');
297
+
298
+ // все ссылки, которые являются прямыми потомками текущего элемента
299
+ $element->find('> a');
300
+ ```
301
+
302
+ ## Изменение содержимого
303
+
304
+ ### Изменение HTML
305
+
306
+ ```php
307
+ $element->setInnerHtml('<a href="#">Foo</a>');
308
+ ```
309
+
310
+ ### Изменение значения
311
+
312
+ ```php
313
+ $element->setValue('Foo');
314
+ ```
315
+
316
+ ## Вывод содержимого
317
+
318
+ ### Получение HTML
319
+
320
+ ##### Через метод `html()`:
321
+
322
+ ```php
323
+ // HTML-код документа
324
+ echo $document->html();
325
+
326
+ // HTML-код элемента
327
+ echo $document->first('.post')->html();
328
+ ```
329
+
330
+ ##### Приведение к строке:
331
+
332
+ ```php
333
+ // HTML-код документа
334
+ $html = (string) $document;
335
+
336
+ // HTML-код элемента
337
+ $html = (string) $document->first('.post');
338
+ ```
339
+
340
+ **Внимание:** использование данного способа нежелательно, т.к. в будущем он может быть удален.
341
+
342
+ ##### Форматирование HTML при выводе
343
+
344
+ ```php
345
+ echo $document->format()->html();
346
+ ```
347
+
348
+ Метод `format()` отсутствует у элемента, поэтому, если нужно получить отформатированный HTML-код элемента, необходимо сначала преобразовать его в документ:
349
+
350
+ ```php
351
+ $html = $element->toDocument()->format()->html();
352
+ ```
353
+
354
+ #### Внутренний HTML
355
+
356
+ ```php
357
+ $innerHtml = $element->innerHtml();
358
+ ```
359
+
360
+ Метод `innerHtml()` отсутствует у документа, поэтому, если нужно получить внутренний HTML-код документа, необходимо сначала преобразовать его в элемент:
361
+
362
+ ```php
363
+ $innerHtml = $document->toElement()->innerHtml();
364
+ ```
365
+
366
+ ### Получение XML
367
+
368
+ ```php
369
+ // XML-код документа
370
+ echo $document->xml();
371
+
372
+ // XML-код элемента
373
+ echo $document->first('book')->xml();
374
+ ```
375
+
376
+ ### Получение содержимого
377
+
378
+ Возвращает текстовое содержимое узла и его потомков:
379
+
380
+ ```php
381
+ echo $element->text();
382
+ ```
383
+
384
+ ## Создание нового элемента
385
+
386
+ ### Создание экземпляра класса
387
+
388
+ ```php
389
+ use DiDom\Element;
390
+
391
+ $element = new Element('span', 'Hello');
392
+
393
+ // выведет "<span>Hello</span>"
394
+ echo $element->html();
395
+ ```
396
+
397
+ Первым параметром передается название элемента, вторым - его значение (необязательно), третьим - атрибуты элемента (необязательно).
398
+
399
+ Пример создания элемента с атрибутами:
400
+
401
+ ```php
402
+ $attributes = ['name' => 'description', 'placeholder' => 'Enter description of item'];
403
+
404
+ $element = new Element('textarea', 'Text', $attributes);
405
+ ```
406
+
407
+ Элемент можно создать и из экземпляра класса `DOMElement`:
408
+
409
+ ```php
410
+ use DiDom\Element;
411
+ use DOMElement;
412
+
413
+ $domElement = new DOMElement('span', 'Hello');
414
+ $element = new Element($domElement);
415
+ ```
416
+
417
+ #### Изменение элемента, созданного из `DOMElement`
418
+
419
+ Экземпляры класса `DOMElement`, созданные через конструктор (`new DOMElement(...)`), являются неизменяемыми, поэтому и элементы (экземпляры класса `DiDom\Element`), созданные из таких объектов, так же являются неизменяемыми.
420
+
421
+ Пример:
422
+
423
+ ```php
424
+ $element = new Element('span', 'Hello');
425
+
426
+ // добавит атрибут "id" со значением "greeting"
427
+ $element->attr('id', 'greeting');
428
+
429
+ $domElement = new DOMElement('span', 'Hello');
430
+ $element = new Element($domElement);
431
+
432
+ // будет выброшено исключение
433
+ // DOMException with message 'No Modification Allowed Error'
434
+ $element->attr('id', 'greeting');
435
+ ```
436
+
437
+ ### С помощью метода `Document::createElement()`
438
+
439
+ ```php
440
+ $document = new Document($html);
441
+
442
+ $element = $document->createElement('span', 'Hello');
443
+ ```
444
+
445
+ ### С помощью CSS-селектора
446
+
447
+ Первый параметр - селектор, второй - значение, третий - массив с атрибутами.
448
+
449
+ Атрибуты элемента могут быть указаны как в селекторе, так и переданы отдельно в третьем параметре.
450
+
451
+ Если название атрибута в массиве совпадает с названием атрибута из селектора, будет использовано значение, указанное в селекторе.
452
+
453
+ ```php
454
+ $document = new Document($html);
455
+
456
+ $element = $document->createElementBySelector('div.block', 'Foo', [
457
+ 'id' => '#content',
458
+ 'class' => '.container',
459
+ ]);
460
+ ```
461
+
462
+ Можно так же использовать статический метод `createBySelector` класса `Element`:
463
+
464
+ ```php
465
+ $element = Element::createBySelector('div.block', 'Foo', [
466
+ 'id' => '#content',
467
+ 'class' => '.container',
468
+ ]);
469
+ ```
470
+
471
+ ## Получение названия элемента
472
+
473
+ ```php
474
+ $element->tag;
475
+ ```
476
+
477
+ ## Получение родительского элемента
478
+
479
+ ```php
480
+ $element->parent();
481
+ ```
482
+
483
+ Так же можно получить родительский элемент, соответствующий селектору:
484
+
485
+ ```php
486
+ $element->closest('.foo');
487
+ ```
488
+
489
+ Вернет родительский элемент, у которого есть класс `foo`. Если подходящий элемент не найден, метод вернет `null`.
490
+
491
+ ## Получение соседних элементов
492
+
493
+ Первый аргумент - CSS-селектор, второй - тип узла (`DOMElement`, `DOMText` или `DOMComment`).
494
+
495
+ Если оба аргумента опущены, будет осуществлен поиск узлов любого типа.
496
+
497
+ Если селектор указан, а тип узла нет, будет использован тип `DOMElement`.
498
+
499
+ **Внимание:** Селектор можно использовать только с типом `DOMElement`.
500
+
501
+ ```php
502
+ // предыдущий элемент
503
+ $item->previousSibling();
504
+
505
+ // предыдущий элемент, соответствующий селектору
506
+ $item->previousSibling('span');
507
+
508
+ // предыдущий элемент типа DOMElement
509
+ $item->previousSibling(null, 'DOMElement');
510
+
511
+ // предыдущий элемент типа DOMComment
512
+ $item->previousSibling(null, 'DOMComment');
513
+ ```
514
+
515
+ ```php
516
+ // все предыдущие элементы
517
+ $item->previousSiblings();
518
+
519
+ // все предыдущие элементы, соответствующие селектору
520
+ $item->previousSiblings('span');
521
+
522
+ // все предыдущие элементы типа DOMElement
523
+ $item->previousSiblings(null, 'DOMElement');
524
+
525
+ // все предыдущие элементы типа DOMComment
526
+ $item->previousSiblings(null, 'DOMComment');
527
+ ```
528
+
529
+ ```php
530
+ // следующий элемент
531
+ $item->nextSibling();
532
+
533
+ // следующий элемент, соответствующий селектору
534
+ $item->nextSibling('span');
535
+
536
+ // следующий элемент типа DOMElement
537
+ $item->nextSibling(null, 'DOMElement');
538
+
539
+ // следующий элемент типа DOMComment
540
+ $item->nextSibling(null, 'DOMComment');
541
+ ```
542
+
543
+ ```php
544
+ // все последующие элементы
545
+ $item->nextSiblings();
546
+
547
+ // все последующие элементы, соответствующие селектору
548
+ $item->nextSiblings('span');
549
+
550
+ // все последующие элементы типа DOMElement
551
+ $item->nextSiblings(null, 'DOMElement');
552
+
553
+ // все последующие элементы типа DOMComment
554
+ $item->nextSiblings(null, 'DOMComment');
555
+ ```
556
+
557
+ ## Получение дочерних элементов
558
+
559
+ ```php
560
+ $html = '<div>Foo<span>Bar</span><!--Baz--></div>';
561
+
562
+ $document = new Document($html);
563
+
564
+ $div = $document->first('div');
565
+
566
+ // элемент (DOMElement)
567
+ // string(3) "Bar"
568
+ var_dump($div->child(1)->text());
569
+
570
+ // текстовый узел (DOMText)
571
+ // string(3) "Foo"
572
+ var_dump($div->firstChild()->text());
573
+
574
+ // комментарий (DOMComment)
575
+ // string(3) "Baz"
576
+ var_dump($div->lastChild()->text());
577
+
578
+ // array(3) { ... }
579
+ var_dump($div->children());
580
+ ```
581
+
582
+ ## Получение документа
583
+
584
+ ```php
585
+ $document = new Document($html);
586
+
587
+ $element = $document->first('input[name=email]');
588
+
589
+ $document2 = $element->getDocument();
590
+
591
+ // bool(true)
592
+ var_dump($document->is($document2));
593
+ ```
594
+
595
+ ## Работа с атрибутами элемента
596
+
597
+ #### Создание/изменение атрибута
598
+
599
+ ##### Через метод `setAttribute`:
600
+ ```php
601
+ $element->setAttribute('name', 'username');
602
+ ```
603
+
604
+ ##### Через метод `attr`:
605
+ ```php
606
+ $element->attr('name', 'username');
607
+ ```
608
+
609
+ ##### Через магический метод `__set`:
610
+ ```php
611
+ $element->name = 'username';
612
+ ```
613
+
614
+ #### Получение значения атрибута
615
+
616
+ ##### Через метод `getAttribute`:
617
+ ```php
618
+ $username = $element->getAttribute('value');
619
+ ```
620
+
621
+ ##### Через метод `attr`:
622
+ ```php
623
+ $username = $element->attr('value');
624
+ ```
625
+
626
+ ##### Через магический метод `__get`:
627
+ ```php
628
+ $username = $element->name;
629
+ ```
630
+
631
+ Если атрибут не найден, вернет `null`.
632
+
633
+ #### Проверка наличия атрибута
634
+
635
+ ##### Через метод `hasAttribute`:
636
+ ```php
637
+ if ($element->hasAttribute('name')) {
638
+ // код
639
+ }
640
+ ```
641
+
642
+ ##### Через магический метод `__isset`:
643
+ ```php
644
+ if (isset($element->name)) {
645
+ // код
646
+ }
647
+ ```
648
+
649
+ #### Удаление атрибута:
650
+
651
+ ##### Через метод `removeAttribute`:
652
+ ```php
653
+ $element->removeAttribute('name');
654
+ ```
655
+
656
+ ##### Через магический метод `__unset`:
657
+ ```php
658
+ unset($element->name);
659
+ ```
660
+
661
+ #### Получение всех атрибутов:
662
+
663
+ ```php
664
+ var_dump($element->attributes());
665
+ ```
666
+
667
+ #### Получение определенных атрибутов:
668
+
669
+ ```php
670
+ var_dump($element->attributes(['name', 'type']));
671
+ ```
672
+
673
+ #### Удаление всех атрибутов:
674
+
675
+ ```php
676
+ $element->removeAllAttributes();
677
+ ```
678
+
679
+ #### Удаление всех атрибутов, за исключением указанных:
680
+
681
+ ```php
682
+ $element->removeAllAttributes(['name', 'type']);
683
+ ```
684
+
685
+ ## Сравнение элементов
686
+
687
+ ```php
688
+ $element = new Element('span', 'hello');
689
+ $element2 = new Element('span', 'hello');
690
+
691
+ // bool(true)
692
+ var_dump($element->is($element));
693
+
694
+ // bool(false)
695
+ var_dump($element->is($element2));
696
+ ```
697
+
698
+ ## Добавление дочерних элементов
699
+
700
+ ```php
701
+ $list = new Element('ul');
702
+
703
+ $item = new Element('li', 'Item 1');
704
+
705
+ $list->appendChild($item);
706
+
707
+ $items = [
708
+ new Element('li', 'Item 2'),
709
+ new Element('li', 'Item 3'),
710
+ ];
711
+
712
+ $list->appendChild($items);
713
+ ```
714
+
715
+ ## Замена элемента
716
+
717
+ ```php
718
+ $title = new Element('title', 'foo');
719
+
720
+ $document->first('title')->replace($title);
721
+ ```
722
+
723
+ **Внимание:** заменить можно только те элементы, которые были найдены непосредственно в документе:
724
+
725
+ ```php
726
+ // ничего не выйдет
727
+ $document->first('head')->first('title')->replace($title);
728
+
729
+ // а вот так да
730
+ $document->first('head title')->replace($title);
731
+ ```
732
+
733
+ Подробнее об этом в разделе [Поиск в элементе](#Поиск-в-элементе).
734
+
735
+ ## Удаление элемента
736
+
737
+ ```php
738
+ $document->first('title')->remove();
739
+ ```
740
+
741
+ **Внимание:** удалить можно только те элементы, которые были найдены непосредственно в документе:
742
+
743
+ ```php
744
+ // ничего не выйдет
745
+ $document->first('head')->first('title')->remove();
746
+
747
+ // а вот так да
748
+ $document->first('head title')->remove();
749
+ ```
750
+
751
+ Подробнее об этом в разделе [Поиск в элементе](#Поиск-в-элементе).
752
+
753
+ ## Работа с кэшем
754
+
755
+ Кэш - массив XPath-выражений, полученных из CSS.
756
+
757
+ #### Получение кэша
758
+
759
+ ```php
760
+ use DiDom\Query;
761
+
762
+ ...
763
+
764
+ $xpath = Query::compile('h2');
765
+ $compiled = Query::getCompiled();
766
+
767
+ // array('h2' => '//h2')
768
+ var_dump($compiled);
769
+ ```
770
+
771
+ #### Установка кэша
772
+
773
+ ```php
774
+ Query::setCompiled(['h2' => '//h2']);
775
+ ```
776
+
777
+ ## Прочее
778
+
779
+ #### `preserveWhiteSpace`
780
+
781
+ По умолчанию сохранение пробелов между тегами отключено.
782
+
783
+ Включать опцию `preserveWhiteSpace` следует до загрузки документа:
784
+
785
+ ```php
786
+ $document = new Document();
787
+
788
+ $document->preserveWhiteSpace();
789
+
790
+ $document->loadXml($xml);
791
+ ```
792
+
793
+ #### `matches`
794
+
795
+ Возвращает `true`, если элемент соответсвует селектору:
796
+
797
+ ```php
798
+ // вернет true, если элемент это div с идентификатором content
799
+ $element->matches('div#content');
800
+
801
+ // строгое соответствие
802
+ // вернет true, если элемент это div с идентификатором content и ничего более
803
+ // если у элемента будут какие-либо другие атрибуты, метод вернет false
804
+ $element->matches('div#content', true);
805
+ ```
806
+
807
+ #### `isElementNode`
808
+
809
+ Проверяет, является ли элемент узлом типа DOMElement:
810
+
811
+ ```php
812
+ $element->isElementNode();
813
+ ```
814
+
815
+ #### `isTextNode`
816
+
817
+ Проверяет, является ли элемент текстовым узлом (DOMText):
818
+
819
+ ```php
820
+ $element->isTextNode();
821
+ ```
822
+
823
+ #### `isCommentNode`
824
+
825
+ Проверяет, является ли элемент комментарием (DOMComment):
826
+
827
+ ```php
828
+ $element->isCommentNode();
829
+ ```
830
+
831
+ ## Сравнение с другими парсерами
832
+
833
+ [Сравнение с другими парсерами](https://github.com/Imangazaliev/DiDOM/wiki/Сравнение-с-другими-парсерами-(1.6.3))
vendor/imangazaliev/didom/README.md ADDED
@@ -0,0 +1,675 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DiDOM
2
+
3
+ [![Build Status](https://travis-ci.com/Imangazaliev/DiDOM.svg?branch=master)](https://travis-ci.com/Imangazaliev/DiDOM)
4
+ [![Total Downloads](https://poser.pugx.org/imangazaliev/didom/downloads)](https://packagist.org/packages/imangazaliev/didom)
5
+ [![Latest Stable Version](https://poser.pugx.org/imangazaliev/didom/v/stable)](https://packagist.org/packages/imangazaliev/didom)
6
+ [![License](https://poser.pugx.org/imangazaliev/didom/license)](https://packagist.org/packages/imangazaliev/didom)
7
+
8
+ [README на русском](README-RU.md)
9
+
10
+ DiDOM - simple and fast HTML parser.
11
+
12
+ ## Contents
13
+
14
+ - [Installation](#installation)
15
+ - [Quick start](#quick-start)
16
+ - [Creating new document](#creating-new-document)
17
+ - [Search for elements](#search-for-elements)
18
+ - [Verify if element exists](#verify-if-element-exists)
19
+ - [Search in element](#search-in-element)
20
+ - [Supported selectors](#supported-selectors)
21
+ - [Output](#output)
22
+ - [Working with elements](#working-with-elements)
23
+ - [Creating a new element](#creating-a-new-element)
24
+ - [Getting the name of an element](#getting-the-name-of-an-element)
25
+ - [Getting parent element](#getting-parent-element)
26
+ - [Getting sibling elements](#getting-sibling-elements)
27
+ - [Getting the child elements](#getting-the-child-elements)
28
+ - [Getting document](#getting-document)
29
+ - [Working with element attributes](#working-with-element-attributes)
30
+ - [Comparing elements](#comparing-elements)
31
+ - [Adding a child element](#adding-a-child-element)
32
+ - [Replacing element](#replacing-element)
33
+ - [Removing element](#removing-element)
34
+ - [Working with cache](#working-with-cache)
35
+ - [Miscellaneous](#miscellaneous)
36
+ - [Comparison with other parsers](#comparison-with-other-parsers)
37
+
38
+ ## Installation
39
+
40
+ To install DiDOM run the command:
41
+
42
+ composer require imangazaliev/didom
43
+
44
+ ## Quick start
45
+
46
+ ```php
47
+ use DiDom\Document;
48
+
49
+ $document = new Document('http://www.news.com/', true);
50
+
51
+ $posts = $document->find('.post');
52
+
53
+ foreach($posts as $post) {
54
+ echo $post->text(), "\n";
55
+ }
56
+ ```
57
+
58
+ ## Creating new document
59
+
60
+ DiDom allows to load HTML in several ways:
61
+
62
+ ##### With constructor
63
+
64
+ ```php
65
+ // the first parameter is a string with HTML
66
+ $document = new Document($html);
67
+
68
+ // file path
69
+ $document = new Document('page.html', true);
70
+
71
+ // or URL
72
+ $document = new Document('http://www.example.com/', true);
73
+ ```
74
+
75
+ The second parameter specifies if you need to load file. Default is `false`.
76
+
77
+ Signature:
78
+
79
+ ```php
80
+ __construct($string = null, $isFile = false, $encoding = 'UTF-8', $type = Document::TYPE_HTML)
81
+ ```
82
+
83
+ `$string` - an HTML or XML string or a file path.
84
+
85
+ `$isFile` - indicates that the first parameter is a path to a file.
86
+
87
+ `$encoding` - the document encoding.
88
+
89
+ `$type` - the document type (HTML - `Document::TYPE_HTML`, XML - `Document::TYPE_XML`).
90
+
91
+ ##### With separate methods
92
+
93
+ ```php
94
+ $document = new Document();
95
+
96
+ $document->loadHtml($html);
97
+
98
+ $document->loadHtmlFile('page.html');
99
+
100
+ $document->loadHtmlFile('http://www.example.com/');
101
+ ```
102
+
103
+ There are two methods available for loading XML: `loadXml` and `loadXmlFile`.
104
+
105
+ These methods accept additional [options](http://php.net/manual/en/libxml.constants.php):
106
+
107
+ ```php
108
+ $document->loadHtml($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
109
+ $document->loadHtmlFile($url, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
110
+
111
+ $document->loadXml($xml, LIBXML_PARSEHUGE);
112
+ $document->loadXmlFile($url, LIBXML_PARSEHUGE);
113
+ ```
114
+
115
+ ## Search for elements
116
+
117
+ DiDOM accepts CSS selector or XPath as an expression for search. You need to path expression as the first parameter, and specify its type in the second one (default type is `Query::TYPE_CSS`):
118
+
119
+ ##### With method `find()`:
120
+
121
+ ```php
122
+ use DiDom\Document;
123
+ use DiDom\Query;
124
+
125
+ ...
126
+
127
+ // CSS selector
128
+ $posts = $document->find('.post');
129
+
130
+ // XPath
131
+ $posts = $document->find("//div[contains(@class, 'post')]", Query::TYPE_XPATH);
132
+ ```
133
+
134
+ If the elements that match a given expression are found, then method returns an array of instances of `DiDom\Element`, otherwise - an empty array. You could also get an array of `DOMElement` objects. To get this, pass `false` as the third parameter.
135
+
136
+ ##### With magic method `__invoke()`:
137
+
138
+ ```php
139
+ $posts = $document('.post');
140
+ ```
141
+
142
+ **Warning:** using this method is undesirable because it may be removed in the future.
143
+
144
+ ##### With method `xpath()`:
145
+
146
+ ```php
147
+ $posts = $document->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]");
148
+ ```
149
+
150
+ You can do search inside an element:
151
+
152
+ ```php
153
+ echo $document->find('nav')[0]->first('ul.menu')->xpath('//li')[0]->text();
154
+ ```
155
+
156
+ ### Verify if element exists
157
+
158
+ To verify if element exist use `has()` method:
159
+
160
+ ```php
161
+ if ($document->has('.post')) {
162
+ // code
163
+ }
164
+ ```
165
+
166
+ If you need to check if element exist and then get it:
167
+
168
+ ```php
169
+ if ($document->has('.post')) {
170
+ $elements = $document->find('.post');
171
+ // code
172
+ }
173
+ ```
174
+
175
+ but it would be faster like this:
176
+
177
+ ```php
178
+ if (count($elements = $document->find('.post')) > 0) {
179
+ // code
180
+ }
181
+ ```
182
+
183
+ because in the first case it makes two queries.
184
+
185
+ ## Search in element
186
+
187
+ Methods `find()`, `first()`, `xpath()`, `has()`, `count()` are available in Element too.
188
+
189
+ Example:
190
+
191
+ ```php
192
+ echo $document->find('nav')[0]->first('ul.menu')->xpath('//li')[0]->text();
193
+ ```
194
+
195
+ #### Method `findInDocument()`
196
+
197
+ If you change, replace, or remove an element that was found in another element, the document will not be changed. This happens because method `find()` of `Element` class (a, respectively, the `first ()` and `xpath` methods) creates a new document to search.
198
+
199
+ To search for elements in the source document, you must use the methods `findInDocument()` and `firstInDocument()`:
200
+
201
+ ```php
202
+ // nothing will happen
203
+ $document->first('head')->first('title')->remove();
204
+
205
+ // but this will do
206
+ $document->first('head')->firstInDocument('title')->remove();
207
+ ```
208
+
209
+ **Warning:** methods `findInDocument()` and `firstInDocument()` work only for elements, which belong to a document, and for elements created via `new Element(...)`. If an element does not belong to a document, `LogicException` will be thrown;
210
+
211
+ ## Supported selectors
212
+
213
+ DiDom supports search by:
214
+
215
+ - tag
216
+ - class, ID, name and value of an attribute
217
+ - pseudo-classes:
218
+ - first-, last-, nth-child
219
+ - empty and not-empty
220
+ - contains
221
+ - has
222
+
223
+ ```php
224
+ // all links
225
+ $document->find('a');
226
+
227
+ // any element with id = "foo" and "bar" class
228
+ $document->find('#foo.bar');
229
+
230
+ // any element with attribute "name"
231
+ $document->find('[name]');
232
+ // the same as
233
+ $document->find('*[name]');
234
+
235
+ // input field with the name "foo"
236
+ $document->find('input[name=foo]');
237
+ $document->find('input[name=\'bar\']');
238
+ $document->find('input[name="baz"]');
239
+
240
+ // any element that has an attribute starting with "data-" and the value "foo"
241
+ $document->find('*[^data-=foo]');
242
+
243
+ // all links starting with https
244
+ $document->find('a[href^=https]');
245
+
246
+ // all images with the extension png
247
+ $document->find('img[src$=png]');
248
+
249
+ // all links containing the string "example.com"
250
+ $document->find('a[href*=example.com]');
251
+
252
+ // text of the links with "foo" class
253
+ $document->find('a.foo::text');
254
+
255
+ // address and title of all the fields with "bar" class
256
+ $document->find('a.bar::attr(href|title)');
257
+ ```
258
+
259
+ ## Output
260
+
261
+ ### Getting HTML
262
+
263
+ ##### With method `html()`:
264
+
265
+ ```php
266
+ $posts = $document->find('.post');
267
+
268
+ echo $posts[0]->html();
269
+ ```
270
+
271
+ ##### Casting to string:
272
+
273
+ ```php
274
+ $html = (string) $posts[0];
275
+ ```
276
+
277
+ ##### Formatting HTML output
278
+
279
+ ```php
280
+ $html = $document->format()->html();
281
+ ```
282
+
283
+ An element does not have `format()` method, so if you need to output formatted HTML of the element, then first you have to convert it to a document:
284
+
285
+
286
+ ```php
287
+ $html = $element->toDocument()->format()->html();
288
+ ```
289
+
290
+ #### Inner HTML
291
+
292
+ ```php
293
+ $innerHtml = $element->innerHtml();
294
+ ```
295
+
296
+ Document does not have the method `innerHtml()`, therefore, if you need to get inner HTML of a document, convert it into an element first:
297
+
298
+ ```php
299
+ $innerHtml = $document->toElement()->innerHtml();
300
+ ```
301
+
302
+ ### Getting XML
303
+
304
+ ```php
305
+ echo $document->xml();
306
+
307
+ echo $document->first('book')->xml();
308
+ ```
309
+
310
+ ### Getting content
311
+
312
+ ```php
313
+ $posts = $document->find('.post');
314
+
315
+ echo $posts[0]->text();
316
+ ```
317
+
318
+ ## Creating a new element
319
+
320
+ ### Creating an instance of the class
321
+
322
+ ```php
323
+ use DiDom\Element;
324
+
325
+ $element = new Element('span', 'Hello');
326
+
327
+ // Outputs "<span>Hello</span>"
328
+ echo $element->html();
329
+ ```
330
+
331
+ First parameter is a name of an attribute, the second one is its value (optional), the third one is element attributes (optional).
332
+
333
+ An example of creating an element with attributes:
334
+
335
+ ```php
336
+ $attributes = ['name' => 'description', 'placeholder' => 'Enter description of item'];
337
+
338
+ $element = new Element('textarea', 'Text', $attributes);
339
+ ```
340
+
341
+ An element can be created from an instance of the class `DOMElement`:
342
+
343
+ ```php
344
+ use DiDom\Element;
345
+ use DOMElement;
346
+
347
+ $domElement = new DOMElement('span', 'Hello');
348
+
349
+ $element = new Element($domElement);
350
+ ```
351
+
352
+ ### Using the method `createElement`
353
+
354
+ ```php
355
+ $document = new Document($html);
356
+
357
+ $element = $document->createElement('span', 'Hello');
358
+ ```
359
+
360
+ ## Getting the name of an element
361
+
362
+ ```php
363
+ $element->tag;
364
+ ```
365
+
366
+ ## Getting parent element
367
+
368
+ ```php
369
+ $document = new Document($html);
370
+
371
+ $input = $document->find('input[name=email]')[0];
372
+
373
+ var_dump($input->parent());
374
+ ```
375
+
376
+ ## Getting sibling elements
377
+
378
+ ```php
379
+ $document = new Document($html);
380
+
381
+ $item = $document->find('ul.menu > li')[1];
382
+
383
+ var_dump($item->previousSibling());
384
+
385
+ var_dump($item->nextSibling());
386
+ ```
387
+
388
+ ## Getting the child elements
389
+
390
+ ```php
391
+ $html = '<div>Foo<span>Bar</span><!--Baz--></div>';
392
+
393
+ $document = new Document($html);
394
+
395
+ $div = $document->first('div');
396
+
397
+ // element node (DOMElement)
398
+ // string(3) "Bar"
399
+ var_dump($div->child(1)->text());
400
+
401
+ // text node (DOMText)
402
+ // string(3) "Foo"
403
+ var_dump($div->firstChild()->text());
404
+
405
+ // comment node (DOMComment)
406
+ // string(3) "Baz"
407
+ var_dump($div->lastChild()->text());
408
+
409
+ // array(3) { ... }
410
+ var_dump($div->children());
411
+ ```
412
+
413
+ ## Getting document
414
+
415
+ ```php
416
+ $document = new Document($html);
417
+
418
+ $element = $document->find('input[name=email]')[0];
419
+
420
+ $document2 = $element->getDocument();
421
+
422
+ // bool(true)
423
+ var_dump($document->is($document2));
424
+ ```
425
+
426
+ ## Working with element attributes
427
+
428
+ #### Creating/updating an attribute
429
+
430
+ ##### With method `setAttribute`:
431
+ ```php
432
+ $element->setAttribute('name', 'username');
433
+ ```
434
+
435
+ ##### With method `attr`:
436
+ ```php
437
+ $element->attr('name', 'username');
438
+ ```
439
+
440
+ ##### With magic method `__set`:
441
+ ```php
442
+ $element->name = 'username';
443
+ ```
444
+
445
+ #### Getting value of an attribute
446
+
447
+ ##### With method `getAttribute`:
448
+
449
+ ```php
450
+ $username = $element->getAttribute('value');
451
+ ```
452
+
453
+ ##### With method `attr`:
454
+
455
+ ```php
456
+ $username = $element->attr('value');
457
+ ```
458
+
459
+ ##### With magic method `__get`:
460
+
461
+ ```php
462
+ $username = $element->name;
463
+ ```
464
+
465
+ Returns `null` if attribute is not found.
466
+
467
+ #### Verify if attribute exists
468
+
469
+ ##### With method `hasAttribute`:
470
+
471
+ ```php
472
+ if ($element->hasAttribute('name')) {
473
+ // code
474
+ }
475
+ ```
476
+
477
+ ##### With magic method `__isset`:
478
+
479
+ ```php
480
+ if (isset($element->name)) {
481
+ // code
482
+ }
483
+ ```
484
+
485
+ #### Removing attribute:
486
+
487
+ ##### With method `removeAttribute`:
488
+
489
+ ```php
490
+ $element->removeAttribute('name');
491
+ ```
492
+
493
+ ##### With magic method `__unset`:
494
+
495
+ ```php
496
+ unset($element->name);
497
+ ```
498
+
499
+ ## Comparing elements
500
+
501
+ ```php
502
+ $element = new Element('span', 'hello');
503
+ $element2 = new Element('span', 'hello');
504
+
505
+ // bool(true)
506
+ var_dump($element->is($element));
507
+
508
+ // bool(false)
509
+ var_dump($element->is($element2));
510
+ ```
511
+
512
+ ## Appending child elements
513
+
514
+ ```php
515
+ $list = new Element('ul');
516
+
517
+ $item = new Element('li', 'Item 1');
518
+
519
+ $list->appendChild($item);
520
+
521
+ $items = [
522
+ new Element('li', 'Item 2'),
523
+ new Element('li', 'Item 3'),
524
+ ];
525
+
526
+ $list->appendChild($items);
527
+ ```
528
+
529
+ ## Adding a child element
530
+
531
+ ```php
532
+ $list = new Element('ul');
533
+
534
+ $item = new Element('li', 'Item 1');
535
+ $items = [
536
+ new Element('li', 'Item 2'),
537
+ new Element('li', 'Item 3'),
538
+ ];
539
+
540
+ $list->appendChild($item);
541
+ $list->appendChild($items);
542
+ ```
543
+
544
+ ## Replacing element
545
+
546
+ ```php
547
+ $element = new Element('span', 'hello');
548
+
549
+ $document->find('.post')[0]->replace($element);
550
+ ```
551
+
552
+ **Waning:** you can replace only those elements that were found directly in the document:
553
+
554
+ ```php
555
+ // nothing will happen
556
+ $document->first('head')->first('title')->replace($title);
557
+
558
+ // but this will do
559
+ $document->first('head title')->replace($title);
560
+ ```
561
+
562
+ More about this in section [Search for elements](#search-for-elements).
563
+
564
+ ## Removing element
565
+
566
+ ```php
567
+ $document->find('.post')[0]->remove();
568
+ ```
569
+
570
+ **Warning:** you can remove only those elements that were found directly in the document:
571
+
572
+ ```php
573
+ // nothing will happen
574
+ $document->first('head')->first('title')->remove();
575
+
576
+ // but this will do
577
+ $document->first('head title')->remove();
578
+ ```
579
+
580
+ More about this in section [Search for elements](#search-for-elements).
581
+
582
+ ## Working with cache
583
+
584
+ Cache is an array of XPath expressions, that were converted from CSS.
585
+
586
+ #### Getting from cache
587
+
588
+ ```php
589
+ use DiDom\Query;
590
+
591
+ ...
592
+
593
+ $xpath = Query::compile('h2');
594
+ $compiled = Query::getCompiled();
595
+
596
+ // array('h2' => '//h2')
597
+ var_dump($compiled);
598
+ ```
599
+
600
+ #### Cache setting
601
+
602
+ ```php
603
+ Query::setCompiled(['h2' => '//h2']);
604
+ ```
605
+
606
+ ## Miscellaneous
607
+
608
+ #### `preserveWhiteSpace`
609
+
610
+ By default, whitespace preserving is disabled.
611
+
612
+ You can enable the `preserveWhiteSpace` option before loading the document:
613
+
614
+ ```php
615
+ $document = new Document();
616
+
617
+ $document->preserveWhiteSpace();
618
+
619
+ $document->loadXml($xml);
620
+ ```
621
+
622
+ #### `count`
623
+
624
+ The `count ()` method counts children that match the selector:
625
+
626
+ ```php
627
+ // prints the number of links in the document
628
+ echo $document->count('a');
629
+ ```
630
+
631
+ ```php
632
+ // prints the number of items in the list
633
+ echo $document->first('ul')->count('li');
634
+ ```
635
+
636
+ #### `matches`
637
+
638
+ Returns `true` if the node matches the selector:
639
+
640
+ ```php
641
+ $element->matches('div#content');
642
+
643
+ // strict match
644
+ // returns true if the element is a div with id equals content and nothing else
645
+ // if the element has any other attributes the method returns false
646
+ $element->matches('div#content', true);
647
+ ```
648
+
649
+ #### `isElementNode`
650
+
651
+ Checks whether an element is an element (DOMElement):
652
+
653
+ ```php
654
+ $element->isElementNode();
655
+ ```
656
+
657
+ #### `isTextNode`
658
+
659
+ Checks whether an element is a text node (DOMText):
660
+
661
+ ```php
662
+ $element->isTextNode();
663
+ ```
664
+
665
+ #### `isCommentNode`
666
+
667
+ Checks whether the element is a comment (DOMComment):
668
+
669
+ ```php
670
+ $element->isCommentNode();
671
+ ```
672
+
673
+ ## Comparison with other parsers
674
+
675
+ [Comparison with other parsers](https://github.com/Imangazaliev/DiDOM/wiki/Comparison-with-other-parsers-(1.0))
vendor/imangazaliev/didom/composer.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "imangazaliev/didom",
3
+ "description": "Simple and fast HTML parser",
4
+ "type": "library",
5
+ "keywords": ["didom", "parser", "html", "xml"],
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/Imangazaliev/DiDOM",
8
+ "authors": [
9
+ {
10
+ "name": "Imangazaliev Muhammad",
11
+ "email": "imangazalievm@gmail.com"
12
+ }
13
+ ],
14
+ "require": {
15
+ "php": ">=5.4",
16
+ "ext-dom": "*",
17
+ "ext-iconv": "*"
18
+ },
19
+ "require-dev": {
20
+ "phpunit/phpunit": "^4.8"
21
+ },
22
+ "autoload": {
23
+ "psr-4": {
24
+ "DiDom\\": "src/DiDom/"
25
+ }
26
+ },
27
+ "autoload-dev": {
28
+ "psr-4": {
29
+ "DiDom\\Tests\\": "tests/"
30
+ }
31
+ },
32
+ "config": {
33
+ "platform": {
34
+ "php": "5.4"
35
+ }
36
+ }
37
+ }
vendor/imangazaliev/didom/composer.lock ADDED
@@ -0,0 +1,1049 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_readme": [
3
+ "This file locks the dependencies of your project to a known state",
4
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5
+ "This file is @generated automatically"
6
+ ],
7
+ "hash": "7d413af5e53b1204b6c430dbd32ca728",
8
+ "content-hash": "fa5a5b325a0458a9fe05c67fb6b0e719",
9
+ "packages": [],
10
+ "packages-dev": [
11
+ {
12
+ "name": "doctrine/instantiator",
13
+ "version": "1.0.5",
14
+ "source": {
15
+ "type": "git",
16
+ "url": "https://github.com/doctrine/instantiator.git",
17
+ "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
18
+ },
19
+ "dist": {
20
+ "type": "zip",
21
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
22
+ "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
23
+ "shasum": ""
24
+ },
25
+ "require": {
26
+ "php": ">=5.3,<8.0-DEV"
27
+ },
28
+ "require-dev": {
29
+ "athletic/athletic": "~0.1.8",
30
+ "ext-pdo": "*",
31
+ "ext-phar": "*",
32
+ "phpunit/phpunit": "~4.0",
33
+ "squizlabs/php_codesniffer": "~2.0"
34
+ },
35
+ "type": "library",
36
+ "extra": {
37
+ "branch-alias": {
38
+ "dev-master": "1.0.x-dev"
39
+ }
40
+ },
41
+ "autoload": {
42
+ "psr-4": {
43
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
44
+ }
45
+ },
46
+ "notification-url": "https://packagist.org/downloads/",
47
+ "license": [
48
+ "MIT"
49
+ ],
50
+ "authors": [
51
+ {
52
+ "name": "Marco Pivetta",
53
+ "email": "ocramius@gmail.com",
54
+ "homepage": "http://ocramius.github.com/"
55
+ }
56
+ ],
57
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
58
+ "homepage": "https://github.com/doctrine/instantiator",
59
+ "keywords": [
60
+ "constructor",
61
+ "instantiate"
62
+ ],
63
+ "time": "2015-06-14 21:17:01"
64
+ },
65
+ {
66
+ "name": "phpdocumentor/reflection-docblock",
67
+ "version": "2.0.5",
68
+ "source": {
69
+ "type": "git",
70
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
71
+ "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b"
72
+ },
73
+ "dist": {
74
+ "type": "zip",
75
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b",
76
+ "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b",
77
+ "shasum": ""
78
+ },
79
+ "require": {
80
+ "php": ">=5.3.3"
81
+ },
82
+ "require-dev": {
83
+ "phpunit/phpunit": "~4.0"
84
+ },
85
+ "suggest": {
86
+ "dflydev/markdown": "~1.0",
87
+ "erusev/parsedown": "~1.0"
88
+ },
89
+ "type": "library",
90
+ "extra": {
91
+ "branch-alias": {
92
+ "dev-master": "2.0.x-dev"
93
+ }
94
+ },
95
+ "autoload": {
96
+ "psr-0": {
97
+ "phpDocumentor": [
98
+ "src/"
99
+ ]
100
+ }
101
+ },
102
+ "notification-url": "https://packagist.org/downloads/",
103
+ "license": [
104
+ "MIT"
105
+ ],
106
+ "authors": [
107
+ {
108
+ "name": "Mike van Riel",
109
+ "email": "mike.vanriel@naenius.com"
110
+ }
111
+ ],
112
+ "time": "2016-01-25 08:17:30"
113
+ },
114
+ {
115
+ "name": "phpspec/prophecy",
116
+ "version": "1.8.1",
117
+ "source": {
118
+ "type": "git",
119
+ "url": "https://github.com/phpspec/prophecy.git",
120
+ "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76"
121
+ },
122
+ "dist": {
123
+ "type": "zip",
124
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
125
+ "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
126
+ "shasum": ""
127
+ },
128
+ "require": {
129
+ "doctrine/instantiator": "^1.0.2",
130
+ "php": "^5.3|^7.0",
131
+ "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
132
+ "sebastian/comparator": "^1.1|^2.0|^3.0",
133
+ "sebastian/recursion-context": "^1.0|^2.0|^3.0"
134
+ },
135
+ "require-dev": {
136
+ "phpspec/phpspec": "^2.5|^3.2",
137
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
138
+ },
139
+ "type": "library",
140
+ "extra": {
141
+ "branch-alias": {
142
+ "dev-master": "1.8.x-dev"
143
+ }
144
+ },
145
+ "autoload": {
146
+ "psr-4": {
147
+ "Prophecy\\": "src/Prophecy"
148
+ }
149
+ },
150
+ "notification-url": "https://packagist.org/downloads/",
151
+ "license": [
152
+ "MIT"
153
+ ],
154
+ "authors": [
155
+ {
156
+ "name": "Konstantin Kudryashov",
157
+ "email": "ever.zet@gmail.com",
158
+ "homepage": "http://everzet.com"
159
+ },
160
+ {
161
+ "name": "Marcello Duarte",
162
+ "email": "marcello.duarte@gmail.com"
163
+ }
164
+ ],
165
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
166
+ "homepage": "https://github.com/phpspec/prophecy",
167
+ "keywords": [
168
+ "Double",
169
+ "Dummy",
170
+ "fake",
171
+ "mock",
172
+ "spy",
173
+ "stub"
174
+ ],
175
+ "time": "2019-06-13 12:50:23"
176
+ },
177
+ {
178
+ "name": "phpunit/php-code-coverage",
179
+ "version": "2.2.4",
180
+ "source": {
181
+ "type": "git",
182
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
183
+ "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
184
+ },
185
+ "dist": {
186
+ "type": "zip",
187
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
188
+ "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
189
+ "shasum": ""
190
+ },
191
+ "require": {
192
+ "php": ">=5.3.3",
193
+ "phpunit/php-file-iterator": "~1.3",
194
+ "phpunit/php-text-template": "~1.2",
195
+ "phpunit/php-token-stream": "~1.3",
196
+ "sebastian/environment": "^1.3.2",
197
+ "sebastian/version": "~1.0"
198
+ },
199
+ "require-dev": {
200
+ "ext-xdebug": ">=2.1.4",
201
+ "phpunit/phpunit": "~4"
202
+ },
203
+ "suggest": {
204
+ "ext-dom": "*",
205
+ "ext-xdebug": ">=2.2.1",
206
+ "ext-xmlwriter": "*"
207
+ },
208
+ "type": "library",
209
+ "extra": {
210
+ "branch-alias": {
211
+ "dev-master": "2.2.x-dev"
212
+ }
213
+ },
214
+ "autoload": {
215
+ "classmap": [
216
+ "src/"
217
+ ]
218
+ },
219
+ "notification-url": "https://packagist.org/downloads/",
220
+ "license": [
221
+ "BSD-3-Clause"
222
+ ],
223
+ "authors": [
224
+ {
225
+ "name": "Sebastian Bergmann",
226
+ "email": "sb@sebastian-bergmann.de",
227
+ "role": "lead"
228
+ }
229
+ ],
230
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
231
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
232
+ "keywords": [
233
+ "coverage",
234
+ "testing",
235
+ "xunit"
236
+ ],
237
+ "time": "2015-10-06 15:47:00"
238
+ },
239
+ {
240
+ "name": "phpunit/php-file-iterator",
241
+ "version": "1.4.5",
242
+ "source": {
243
+ "type": "git",
244
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
245
+ "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
246
+ },
247
+ "dist": {
248
+ "type": "zip",
249
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
250
+ "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
251
+ "shasum": ""
252
+ },
253
+ "require": {
254
+ "php": ">=5.3.3"
255
+ },
256
+ "type": "library",
257
+ "extra": {
258
+ "branch-alias": {
259
+ "dev-master": "1.4.x-dev"
260
+ }
261
+ },
262
+ "autoload": {
263
+ "classmap": [
264
+ "src/"
265
+ ]
266
+ },
267
+ "notification-url": "https://packagist.org/downloads/",
268
+ "license": [
269
+ "BSD-3-Clause"
270
+ ],
271
+ "authors": [
272
+ {
273
+ "name": "Sebastian Bergmann",
274
+ "email": "sb@sebastian-bergmann.de",
275
+ "role": "lead"
276
+ }
277
+ ],
278
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
279
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
280
+ "keywords": [
281
+ "filesystem",
282
+ "iterator"
283
+ ],
284
+ "time": "2017-11-27 13:52:08"
285
+ },
286
+ {
287
+ "name": "phpunit/php-text-template",
288
+ "version": "1.2.1",
289
+ "source": {
290
+ "type": "git",
291
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
292
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
293
+ },
294
+ "dist": {
295
+ "type": "zip",
296
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
297
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
298
+ "shasum": ""
299
+ },
300
+ "require": {
301
+ "php": ">=5.3.3"
302
+ },
303
+ "type": "library",
304
+ "autoload": {
305
+ "classmap": [
306
+ "src/"
307
+ ]
308
+ },
309
+ "notification-url": "https://packagist.org/downloads/",
310
+ "license": [
311
+ "BSD-3-Clause"
312
+ ],
313
+ "authors": [
314
+ {
315
+ "name": "Sebastian Bergmann",
316
+ "email": "sebastian@phpunit.de",
317
+ "role": "lead"
318
+ }
319
+ ],
320
+ "description": "Simple template engine.",
321
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
322
+ "keywords": [
323
+ "template"
324
+ ],
325
+ "time": "2015-06-21 13:50:34"
326
+ },
327
+ {
328
+ "name": "phpunit/php-timer",
329
+ "version": "1.0.9",
330
+ "source": {
331
+ "type": "git",
332
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
333
+ "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
334
+ },
335
+ "dist": {
336
+ "type": "zip",
337
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
338
+ "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
339
+ "shasum": ""
340
+ },
341
+ "require": {
342
+ "php": "^5.3.3 || ^7.0"
343
+ },
344
+ "require-dev": {
345
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
346
+ },
347
+ "type": "library",
348
+ "extra": {
349
+ "branch-alias": {
350
+ "dev-master": "1.0-dev"
351
+ }
352
+ },
353
+ "autoload": {
354
+ "classmap": [
355
+ "src/"
356
+ ]
357
+ },
358
+ "notification-url": "https://packagist.org/downloads/",
359
+ "license": [
360
+ "BSD-3-Clause"
361
+ ],
362
+ "authors": [
363
+ {
364
+ "name": "Sebastian Bergmann",
365
+ "email": "sb@sebastian-bergmann.de",
366
+ "role": "lead"
367
+ }
368
+ ],
369
+ "description": "Utility class for timing",
370
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
371
+ "keywords": [
372
+ "timer"
373
+ ],
374
+ "time": "2017-02-26 11:10:40"
375
+ },
376
+ {
377
+ "name": "phpunit/php-token-stream",
378
+ "version": "1.4.12",
379
+ "source": {
380
+ "type": "git",
381
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
382
+ "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16"
383
+ },
384
+ "dist": {
385
+ "type": "zip",
386
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16",
387
+ "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16",
388
+ "shasum": ""
389
+ },
390
+ "require": {
391
+ "ext-tokenizer": "*",
392
+ "php": ">=5.3.3"
393
+ },
394
+ "require-dev": {
395
+ "phpunit/phpunit": "~4.2"
396
+ },
397
+ "type": "library",
398
+ "extra": {
399
+ "branch-alias": {
400
+ "dev-master": "1.4-dev"
401
+ }
402
+ },
403
+ "autoload": {
404
+ "classmap": [
405
+ "src/"
406
+ ]
407
+ },
408
+ "notification-url": "https://packagist.org/downloads/",
409
+ "license": [
410
+ "BSD-3-Clause"
411
+ ],
412
+ "authors": [
413
+ {
414
+ "name": "Sebastian Bergmann",
415
+ "email": "sebastian@phpunit.de"
416
+ }
417
+ ],
418
+ "description": "Wrapper around PHP's tokenizer extension.",
419
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
420
+ "keywords": [
421
+ "tokenizer"
422
+ ],
423
+ "time": "2017-12-04 08:55:13"
424
+ },
425
+ {
426
+ "name": "phpunit/phpunit",
427
+ "version": "4.8.36",
428
+ "source": {
429
+ "type": "git",
430
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
431
+ "reference": "46023de9a91eec7dfb06cc56cb4e260017298517"
432
+ },
433
+ "dist": {
434
+ "type": "zip",
435
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517",
436
+ "reference": "46023de9a91eec7dfb06cc56cb4e260017298517",
437
+ "shasum": ""
438
+ },
439
+ "require": {
440
+ "ext-dom": "*",
441
+ "ext-json": "*",
442
+ "ext-pcre": "*",
443
+ "ext-reflection": "*",
444
+ "ext-spl": "*",
445
+ "php": ">=5.3.3",
446
+ "phpspec/prophecy": "^1.3.1",
447
+ "phpunit/php-code-coverage": "~2.1",
448
+ "phpunit/php-file-iterator": "~1.4",
449
+ "phpunit/php-text-template": "~1.2",
450
+ "phpunit/php-timer": "^1.0.6",
451
+ "phpunit/phpunit-mock-objects": "~2.3",
452
+ "sebastian/comparator": "~1.2.2",
453
+ "sebastian/diff": "~1.2",
454
+ "sebastian/environment": "~1.3",
455
+ "sebastian/exporter": "~1.2",
456
+ "sebastian/global-state": "~1.0",
457
+ "sebastian/version": "~1.0",
458
+ "symfony/yaml": "~2.1|~3.0"
459
+ },
460
+ "suggest": {
461
+ "phpunit/php-invoker": "~1.1"
462
+ },
463
+ "bin": [
464
+ "phpunit"
465
+ ],
466
+ "type": "library",
467
+ "extra": {
468
+ "branch-alias": {
469
+ "dev-master": "4.8.x-dev"
470
+ }
471
+ },
472
+ "autoload": {
473
+ "classmap": [
474
+ "src/"
475
+ ]
476
+ },
477
+ "notification-url": "https://packagist.org/downloads/",
478
+ "license": [
479
+ "BSD-3-Clause"
480
+ ],
481
+ "authors": [
482
+ {
483
+ "name": "Sebastian Bergmann",
484
+ "email": "sebastian@phpunit.de",
485
+ "role": "lead"
486
+ }
487
+ ],
488
+ "description": "The PHP Unit Testing framework.",
489
+ "homepage": "https://phpunit.de/",
490
+ "keywords": [
491
+ "phpunit",
492
+ "testing",
493
+ "xunit"
494
+ ],
495
+ "time": "2017-06-21 08:07:12"
496
+ },
497
+ {
498
+ "name": "phpunit/phpunit-mock-objects",
499
+ "version": "2.3.8",
500
+ "source": {
501
+ "type": "git",
502
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
503
+ "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
504
+ },
505
+ "dist": {
506
+ "type": "zip",
507
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
508
+ "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
509
+ "shasum": ""
510
+ },
511
+ "require": {
512
+ "doctrine/instantiator": "^1.0.2",
513
+ "php": ">=5.3.3",
514
+ "phpunit/php-text-template": "~1.2",
515
+ "sebastian/exporter": "~1.2"
516
+ },
517
+ "require-dev": {
518
+ "phpunit/phpunit": "~4.4"
519
+ },
520
+ "suggest": {
521
+ "ext-soap": "*"
522
+ },
523
+ "type": "library",
524
+ "extra": {
525
+ "branch-alias": {
526
+ "dev-master": "2.3.x-dev"
527
+ }
528
+ },
529
+ "autoload": {
530
+ "classmap": [
531
+ "src/"
532
+ ]
533
+ },
534
+ "notification-url": "https://packagist.org/downloads/",
535
+ "license": [
536
+ "BSD-3-Clause"
537
+ ],
538
+ "authors": [
539
+ {
540
+ "name": "Sebastian Bergmann",
541
+ "email": "sb@sebastian-bergmann.de",
542
+ "role": "lead"
543
+ }
544
+ ],
545
+ "description": "Mock Object library for PHPUnit",
546
+ "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
547
+ "keywords": [
548
+ "mock",
549
+ "xunit"
550
+ ],
551
+ "abandoned": true,
552
+ "time": "2015-10-02 06:51:40"
553
+ },
554
+ {
555
+ "name": "sebastian/comparator",
556
+ "version": "1.2.4",
557
+ "source": {
558
+ "type": "git",
559
+ "url": "https://github.com/sebastianbergmann/comparator.git",
560
+ "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
561
+ },
562
+ "dist": {
563
+ "type": "zip",
564
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
565
+ "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
566
+ "shasum": ""
567
+ },
568
+ "require": {
569
+ "php": ">=5.3.3",
570
+ "sebastian/diff": "~1.2",
571
+ "sebastian/exporter": "~1.2 || ~2.0"
572
+ },
573
+ "require-dev": {
574
+ "phpunit/phpunit": "~4.4"
575
+ },
576
+ "type": "library",
577
+ "extra": {
578
+ "branch-alias": {
579
+ "dev-master": "1.2.x-dev"
580
+ }
581
+ },
582
+ "autoload": {
583
+ "classmap": [
584
+ "src/"
585
+ ]
586
+ },
587
+ "notification-url": "https://packagist.org/downloads/",
588
+ "license": [
589
+ "BSD-3-Clause"
590
+ ],
591
+ "authors": [
592
+ {
593
+ "name": "Jeff Welch",
594
+ "email": "whatthejeff@gmail.com"
595
+ },
596
+ {
597
+ "name": "Volker Dusch",
598
+ "email": "github@wallbash.com"
599
+ },
600
+ {
601
+ "name": "Bernhard Schussek",
602
+ "email": "bschussek@2bepublished.at"
603
+ },
604
+ {
605
+ "name": "Sebastian Bergmann",
606
+ "email": "sebastian@phpunit.de"
607
+ }
608
+ ],
609
+ "description": "Provides the functionality to compare PHP values for equality",
610
+ "homepage": "http://www.github.com/sebastianbergmann/comparator",
611
+ "keywords": [
612
+ "comparator",
613
+ "compare",
614
+ "equality"
615
+ ],
616
+ "time": "2017-01-29 09:50:25"
617
+ },
618
+ {
619
+ "name": "sebastian/diff",
620
+ "version": "1.4.3",
621
+ "source": {
622
+ "type": "git",
623
+ "url": "https://github.com/sebastianbergmann/diff.git",
624
+ "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
625
+ },
626
+ "dist": {
627
+ "type": "zip",
628
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
629
+ "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
630
+ "shasum": ""
631
+ },
632
+ "require": {
633
+ "php": "^5.3.3 || ^7.0"
634
+ },
635
+ "require-dev": {
636
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
637
+ },
638
+ "type": "library",
639
+ "extra": {
640
+ "branch-alias": {
641
+ "dev-master": "1.4-dev"
642
+ }
643
+ },
644
+ "autoload": {
645
+ "classmap": [
646
+ "src/"
647
+ ]
648
+ },
649
+ "notification-url": "https://packagist.org/downloads/",
650
+ "license": [
651
+ "BSD-3-Clause"
652
+ ],
653
+ "authors": [
654
+ {
655
+ "name": "Kore Nordmann",
656
+ "email": "mail@kore-nordmann.de"
657
+ },
658
+ {
659
+ "name": "Sebastian Bergmann",
660
+ "email": "sebastian@phpunit.de"
661
+ }
662
+ ],
663
+ "description": "Diff implementation",
664
+ "homepage": "https://github.com/sebastianbergmann/diff",
665
+ "keywords": [
666
+ "diff"
667
+ ],
668
+ "time": "2017-05-22 07:24:03"
669
+ },
670
+ {
671
+ "name": "sebastian/environment",
672
+ "version": "1.3.8",
673
+ "source": {
674
+ "type": "git",
675
+ "url": "https://github.com/sebastianbergmann/environment.git",
676
+ "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
677
+ },
678
+ "dist": {
679
+ "type": "zip",
680
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
681
+ "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
682
+ "shasum": ""
683
+ },
684
+ "require": {
685
+ "php": "^5.3.3 || ^7.0"
686
+ },
687
+ "require-dev": {
688
+ "phpunit/phpunit": "^4.8 || ^5.0"
689
+ },
690
+ "type": "library",
691
+ "extra": {
692
+ "branch-alias": {
693
+ "dev-master": "1.3.x-dev"
694
+ }
695
+ },
696
+ "autoload": {
697
+ "classmap": [
698
+ "src/"
699
+ ]
700
+ },
701
+ "notification-url": "https://packagist.org/downloads/",
702
+ "license": [
703
+ "BSD-3-Clause"
704
+ ],
705
+ "authors": [
706
+ {
707
+ "name": "Sebastian Bergmann",
708
+ "email": "sebastian@phpunit.de"
709
+ }
710
+ ],
711
+ "description": "Provides functionality to handle HHVM/PHP environments",
712
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
713
+ "keywords": [
714
+ "Xdebug",
715
+ "environment",
716
+ "hhvm"
717
+ ],
718
+ "time": "2016-08-18 05:49:44"
719
+ },
720
+ {
721
+ "name": "sebastian/exporter",
722
+ "version": "1.2.2",
723
+ "source": {
724
+ "type": "git",
725
+ "url": "https://github.com/sebastianbergmann/exporter.git",
726
+ "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
727
+ },
728
+ "dist": {
729
+ "type": "zip",
730
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
731
+ "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
732
+ "shasum": ""
733
+ },
734
+ "require": {
735
+ "php": ">=5.3.3",
736
+ "sebastian/recursion-context": "~1.0"
737
+ },
738
+ "require-dev": {
739
+ "ext-mbstring": "*",
740
+ "phpunit/phpunit": "~4.4"
741
+ },
742
+ "type": "library",
743
+ "extra": {
744
+ "branch-alias": {
745
+ "dev-master": "1.3.x-dev"
746
+ }
747
+ },
748
+ "autoload": {
749
+ "classmap": [
750
+ "src/"
751
+ ]
752
+ },
753
+ "notification-url": "https://packagist.org/downloads/",
754
+ "license": [
755
+ "BSD-3-Clause"
756
+ ],
757
+ "authors": [
758
+ {
759
+ "name": "Jeff Welch",
760
+ "email": "whatthejeff@gmail.com"
761
+ },
762
+ {
763
+ "name": "Volker Dusch",
764
+ "email": "github@wallbash.com"
765
+ },
766
+ {
767
+ "name": "Bernhard Schussek",
768
+ "email": "bschussek@2bepublished.at"
769
+ },
770
+ {
771
+ "name": "Sebastian Bergmann",
772
+ "email": "sebastian@phpunit.de"
773
+ },
774
+ {
775
+ "name": "Adam Harvey",
776
+ "email": "aharvey@php.net"
777
+ }
778
+ ],
779
+ "description": "Provides the functionality to export PHP variables for visualization",
780
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
781
+ "keywords": [
782
+ "export",
783
+ "exporter"
784
+ ],
785
+ "time": "2016-06-17 09:04:28"
786
+ },
787
+ {
788
+ "name": "sebastian/global-state",
789
+ "version": "1.1.1",
790
+ "source": {
791
+ "type": "git",
792
+ "url": "https://github.com/sebastianbergmann/global-state.git",
793
+ "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
794
+ },
795
+ "dist": {
796
+ "type": "zip",
797
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
798
+ "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
799
+ "shasum": ""
800
+ },
801
+ "require": {
802
+ "php": ">=5.3.3"
803
+ },
804
+ "require-dev": {
805
+ "phpunit/phpunit": "~4.2"
806
+ },
807
+ "suggest": {
808
+ "ext-uopz": "*"
809
+ },
810
+ "type": "library",
811
+ "extra": {
812
+ "branch-alias": {
813
+ "dev-master": "1.0-dev"
814
+ }
815
+ },
816
+ "autoload": {
817
+ "classmap": [
818
+ "src/"
819
+ ]
820
+ },
821
+ "notification-url": "https://packagist.org/downloads/",
822
+ "license": [
823
+ "BSD-3-Clause"
824
+ ],
825
+ "authors": [
826
+ {
827
+ "name": "Sebastian Bergmann",
828
+ "email": "sebastian@phpunit.de"
829
+ }
830
+ ],
831
+ "description": "Snapshotting of global state",
832
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
833
+ "keywords": [
834
+ "global state"
835
+ ],
836
+ "time": "2015-10-12 03:26:01"
837
+ },
838
+ {
839
+ "name": "sebastian/recursion-context",
840
+ "version": "1.0.5",
841
+ "source": {
842
+ "type": "git",
843
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
844
+ "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
845
+ },
846
+ "dist": {
847
+ "type": "zip",
848
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
849
+ "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
850
+ "shasum": ""
851
+ },
852
+ "require": {
853
+ "php": ">=5.3.3"
854
+ },
855
+ "require-dev": {
856
+ "phpunit/phpunit": "~4.4"
857
+ },
858
+ "type": "library",
859
+ "extra": {
860
+ "branch-alias": {
861
+ "dev-master": "1.0.x-dev"
862
+ }
863
+ },
864
+ "autoload": {
865
+ "classmap": [
866
+ "src/"
867
+ ]
868
+ },
869
+ "notification-url": "https://packagist.org/downloads/",
870
+ "license": [
871
+ "BSD-3-Clause"
872
+ ],
873
+ "authors": [
874
+ {
875
+ "name": "Jeff Welch",
876
+ "email": "whatthejeff@gmail.com"
877
+ },
878
+ {
879
+ "name": "Sebastian Bergmann",
880
+ "email": "sebastian@phpunit.de"
881
+ },
882
+ {
883
+ "name": "Adam Harvey",
884
+ "email": "aharvey@php.net"
885
+ }
886
+ ],
887
+ "description": "Provides functionality to recursively process PHP variables",
888
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
889
+ "time": "2016-10-03 07:41:43"
890
+ },
891
+ {
892
+ "name": "sebastian/version",
893
+ "version": "1.0.6",
894
+ "source": {
895
+ "type": "git",
896
+ "url": "https://github.com/sebastianbergmann/version.git",
897
+ "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
898
+ },
899
+ "dist": {
900
+ "type": "zip",
901
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
902
+ "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
903
+ "shasum": ""
904
+ },
905
+ "type": "library",
906
+ "autoload": {
907
+ "classmap": [
908
+ "src/"
909
+ ]
910
+ },
911
+ "notification-url": "https://packagist.org/downloads/",
912
+ "license": [
913
+ "BSD-3-Clause"
914
+ ],
915
+ "authors": [
916
+ {
917
+ "name": "Sebastian Bergmann",
918
+ "email": "sebastian@phpunit.de",
919
+ "role": "lead"
920
+ }
921
+ ],
922
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
923
+ "homepage": "https://github.com/sebastianbergmann/version",
924
+ "time": "2015-06-21 13:59:46"
925
+ },
926
+ {
927
+ "name": "symfony/polyfill-ctype",
928
+ "version": "v1.12.0",
929
+ "source": {
930
+ "type": "git",
931
+ "url": "https://github.com/symfony/polyfill-ctype.git",
932
+ "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
933
+ },
934
+ "dist": {
935
+ "type": "zip",
936
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
937
+ "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
938
+ "shasum": ""
939
+ },
940
+ "require": {
941
+ "php": ">=5.3.3"
942
+ },
943
+ "suggest": {
944
+ "ext-ctype": "For best performance"
945
+ },
946
+ "type": "library",
947
+ "extra": {
948
+ "branch-alias": {
949
+ "dev-master": "1.12-dev"
950
+ }
951
+ },
952
+ "autoload": {
953
+ "psr-4": {
954
+ "Symfony\\Polyfill\\Ctype\\": ""
955
+ },
956
+ "files": [
957
+ "bootstrap.php"
958
+ ]
959
+ },
960
+ "notification-url": "https://packagist.org/downloads/",
961
+ "license": [
962
+ "MIT"
963
+ ],
964
+ "authors": [
965
+ {
966
+ "name": "Gert de Pagter",
967
+ "email": "BackEndTea@gmail.com"
968
+ },
969
+ {
970
+ "name": "Symfony Community",
971
+ "homepage": "https://symfony.com/contributors"
972
+ }
973
+ ],
974
+ "description": "Symfony polyfill for ctype functions",
975
+ "homepage": "https://symfony.com",
976
+ "keywords": [
977
+ "compatibility",
978
+ "ctype",
979
+ "polyfill",
980
+ "portable"
981
+ ],
982
+ "time": "2019-08-06 08:03:45"
983
+ },
984
+ {
985
+ "name": "symfony/yaml",
986
+ "version": "v2.8.50",
987
+ "source": {
988
+ "type": "git",
989
+ "url": "https://github.com/symfony/yaml.git",
990
+ "reference": "02c1859112aa779d9ab394ae4f3381911d84052b"
991
+ },
992
+ "dist": {
993
+ "type": "zip",
994
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b",
995
+ "reference": "02c1859112aa779d9ab394ae4f3381911d84052b",
996
+ "shasum": ""
997
+ },
998
+ "require": {
999
+ "php": ">=5.3.9",
1000
+ "symfony/polyfill-ctype": "~1.8"
1001
+ },
1002
+ "type": "library",
1003
+ "extra": {
1004
+ "branch-alias": {
1005
+ "dev-master": "2.8-dev"
1006
+ }
1007
+ },
1008
+ "autoload": {
1009
+ "psr-4": {
1010
+ "Symfony\\Component\\Yaml\\": ""
1011
+ },
1012
+ "exclude-from-classmap": [
1013
+ "/Tests/"
1014
+ ]
1015
+ },
1016
+ "notification-url": "https://packagist.org/downloads/",
1017
+ "license": [
1018
+ "MIT"
1019
+ ],
1020
+ "authors": [
1021
+ {
1022
+ "name": "Fabien Potencier",
1023
+ "email": "fabien@symfony.com"
1024
+ },
1025
+ {
1026
+ "name": "Symfony Community",
1027
+ "homepage": "https://symfony.com/contributors"
1028
+ }
1029
+ ],
1030
+ "description": "Symfony Yaml Component",
1031
+ "homepage": "https://symfony.com",
1032
+ "time": "2018-11-11 11:18:13"
1033
+ }
1034
+ ],
1035
+ "aliases": [],
1036
+ "minimum-stability": "stable",
1037
+ "stability-flags": [],
1038
+ "prefer-stable": false,
1039
+ "prefer-lowest": false,
1040
+ "platform": {
1041
+ "php": ">=5.4",
1042
+ "ext-dom": "*",
1043
+ "ext-iconv": "*"
1044
+ },
1045
+ "platform-dev": [],
1046
+ "platform-overrides": {
1047
+ "php": "5.4"
1048
+ }
1049
+ }
vendor/imangazaliev/didom/src/DiDom/ClassAttribute.php ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use InvalidArgumentException;
6
+
7
+ class ClassAttribute
8
+ {
9
+ /**
10
+ * The DOM element instance.
11
+ *
12
+ * @var Element
13
+ */
14
+ protected $element;
15
+
16
+ /**
17
+ * @var string
18
+ */
19
+ protected $classesString = '';
20
+
21
+ /**
22
+ * @var string[]
23
+ */
24
+ protected $classes = [];
25
+
26
+ /**
27
+ * @param Element $element
28
+ *
29
+ * @throws InvalidArgumentException if parameter 1 is not an element node
30
+ */
31
+ public function __construct(Element $element)
32
+ {
33
+ if ( ! $element->isElementNode()) {
34
+ throw new InvalidArgumentException(sprintf('The element must contain DOMElement node'));
35
+ }
36
+
37
+ $this->element = $element;
38
+
39
+ $this->parseClassAttribute();
40
+ }
41
+
42
+ /**
43
+ * Parses class attribute of the element.
44
+ */
45
+ protected function parseClassAttribute()
46
+ {
47
+ if ( ! $this->element->hasAttribute('class')) {
48
+ // possible if class attribute has been removed
49
+ if ($this->classesString !== '') {
50
+ $this->classesString = '';
51
+ $this->classes = [];
52
+ }
53
+
54
+ return;
55
+ }
56
+
57
+ // if class attribute is not changed
58
+ if ($this->element->getAttribute('class') === $this->classesString) {
59
+ return;
60
+ }
61
+
62
+ // save class attribute as is (without trimming)
63
+ $this->classesString = $this->element->getAttribute('class');
64
+
65
+ $classesString = trim($this->classesString);
66
+
67
+ if ($classesString === '') {
68
+ $this->classes = [];
69
+
70
+ return;
71
+ }
72
+
73
+ $classes = explode(' ', $classesString);
74
+
75
+ $classes = array_map('trim', $classes);
76
+ $classes = array_filter($classes);
77
+ $classes = array_unique($classes);
78
+
79
+ $this->classes = array_values($classes);
80
+ }
81
+
82
+ /**
83
+ * Updates class attribute of the element.
84
+ */
85
+ protected function updateClassAttribute()
86
+ {
87
+ $this->classesString = implode(' ', $this->classes);
88
+
89
+ $this->element->setAttribute('class', $this->classesString);
90
+ }
91
+
92
+ /**
93
+ * @param string $className
94
+ *
95
+ * @return ClassAttribute
96
+ *
97
+ * @throws InvalidArgumentException if class name is not a string
98
+ */
99
+ public function add($className)
100
+ {
101
+ if ( ! is_string($className)) {
102
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($className) ? get_class($className) : gettype($className))));
103
+ }
104
+
105
+ $this->parseClassAttribute();
106
+
107
+ if (in_array($className, $this->classes, true)) {
108
+ return $this;
109
+ }
110
+
111
+ $this->classes[] = $className;
112
+
113
+ $this->updateClassAttribute();
114
+
115
+ return $this;
116
+ }
117
+
118
+ /**
119
+ * @param array $classNames
120
+ *
121
+ * @return ClassAttribute
122
+ *
123
+ * @throws InvalidArgumentException if class name is not a string
124
+ */
125
+ public function addMultiple(array $classNames)
126
+ {
127
+ $this->parseClassAttribute();
128
+
129
+ foreach ($classNames as $className) {
130
+ if ( ! is_string($className)) {
131
+ throw new InvalidArgumentException(sprintf('Class name must be a string, %s given', (is_object($className) ? get_class($className) : gettype($className))));
132
+ }
133
+
134
+ if (in_array($className, $this->classes, true)) {
135
+ continue;
136
+ }
137
+
138
+ $this->classes[] = $className;
139
+ }
140
+
141
+ $this->updateClassAttribute();
142
+
143
+ return $this;
144
+ }
145
+
146
+ /**
147
+ * @return string[]
148
+ */
149
+ public function getAll()
150
+ {
151
+ $this->parseClassAttribute();
152
+
153
+ return $this->classes;
154
+ }
155
+
156
+ /**
157
+ * @param string $className
158
+ *
159
+ * @return bool
160
+ */
161
+ public function contains($className)
162
+ {
163
+ if ( ! is_string($className)) {
164
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($className) ? get_class($className) : gettype($className))));
165
+ }
166
+
167
+ $this->parseClassAttribute();
168
+
169
+ return in_array($className, $this->classes, true);
170
+ }
171
+
172
+ /**
173
+ * @param string $className
174
+ *
175
+ * @return ClassAttribute
176
+ *
177
+ * @throws InvalidArgumentException if class name is not a string
178
+ */
179
+ public function remove($className)
180
+ {
181
+ if ( ! is_string($className)) {
182
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($className) ? get_class($className) : gettype($className))));
183
+ }
184
+
185
+ $this->parseClassAttribute();
186
+
187
+ $classIndex = array_search($className, $this->classes);
188
+
189
+ if ($classIndex === false) {
190
+ return $this;
191
+ }
192
+
193
+ unset($this->classes[$classIndex]);
194
+
195
+ $this->updateClassAttribute();
196
+
197
+ return $this;
198
+ }
199
+
200
+ /**
201
+ * @param array $classNames
202
+ *
203
+ * @return ClassAttribute
204
+ *
205
+ * @throws InvalidArgumentException if class name is not a string
206
+ */
207
+ public function removeMultiple(array $classNames)
208
+ {
209
+ $this->parseClassAttribute();
210
+
211
+ foreach ($classNames as $className) {
212
+ if ( ! is_string($className)) {
213
+ throw new InvalidArgumentException(sprintf('Class name must be a string, %s given', (is_object($className) ? get_class($className) : gettype($className))));
214
+ }
215
+
216
+ $classIndex = array_search($className, $this->classes);
217
+
218
+ if ($classIndex === false) {
219
+ continue;
220
+ }
221
+
222
+ unset($this->classes[$classIndex]);
223
+ }
224
+
225
+ $this->updateClassAttribute();
226
+
227
+ return $this;
228
+ }
229
+
230
+ /**
231
+ * @param string[] $exclusions
232
+ *
233
+ * @return ClassAttribute
234
+ */
235
+ public function removeAll(array $exclusions = [])
236
+ {
237
+ $this->parseClassAttribute();
238
+
239
+ $preservedClasses = [];
240
+
241
+ foreach ($exclusions as $className) {
242
+ if ( ! is_string($className)) {
243
+ throw new InvalidArgumentException(sprintf('Class name must be a string, %s given', (is_object($className) ? get_class($className) : gettype($className))));
244
+ }
245
+
246
+ if ( ! in_array($className, $this->classes, true)) {
247
+ continue;
248
+ }
249
+
250
+ $preservedClasses[] = $className;
251
+ }
252
+
253
+ $this->classes = $preservedClasses;
254
+
255
+ $this->updateClassAttribute();
256
+
257
+ return $this;
258
+ }
259
+
260
+ /**
261
+ * @return Element
262
+ */
263
+ public function getElement()
264
+ {
265
+ return $this->element;
266
+ }
267
+ }
vendor/imangazaliev/didom/src/DiDom/Document.php ADDED
@@ -0,0 +1,744 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use DiDom\Exceptions\InvalidSelectorException;
6
+ use DOMAttr;
7
+ use DOMCdataSection;
8
+ use DOMComment;
9
+ use DOMDocument;
10
+ use DOMElement;
11
+ use DOMNode;
12
+ use DOMText;
13
+ use DOMXPath;
14
+ use Exception;
15
+ use InvalidArgumentException;
16
+ use RuntimeException;
17
+
18
+ class Document
19
+ {
20
+ /**
21
+ * Types of a document.
22
+ *
23
+ * @const string
24
+ */
25
+ const TYPE_HTML = 'html';
26
+ const TYPE_XML = 'xml';
27
+
28
+ /**
29
+ * @var DOMDocument
30
+ */
31
+ protected $document;
32
+
33
+ /**
34
+ * @var string
35
+ */
36
+ protected $type;
37
+
38
+ /**
39
+ * @var string
40
+ */
41
+ protected $encoding;
42
+
43
+ /**
44
+ * @var array
45
+ */
46
+ protected $namespaces = [
47
+ 'php' => 'http://php.net/xpath'
48
+ ];
49
+
50
+ /**
51
+ * @param string|null $string An HTML or XML string or a file path
52
+ * @param bool $isFile Indicates that the first parameter is a path to a file
53
+ * @param string $encoding The document encoding
54
+ * @param string $type The document type
55
+ *
56
+ * @throws InvalidArgumentException if parameter 3 is not a string
57
+ */
58
+ public function __construct($string = null, $isFile = false, $encoding = 'UTF-8', $type = Document::TYPE_HTML)
59
+ {
60
+ if ($string instanceof DOMDocument) {
61
+ $this->document = $string;
62
+
63
+ return;
64
+ }
65
+
66
+ if ( ! is_string($encoding)) {
67
+ throw new InvalidArgumentException(sprintf('%s expects parameter 3 to be string, %s given', __METHOD__, gettype($encoding)));
68
+ }
69
+
70
+ $this->encoding = $encoding;
71
+
72
+ $this->document = new DOMDocument('1.0', $encoding);
73
+
74
+ $this->preserveWhiteSpace(false);
75
+
76
+ if ($string !== null) {
77
+ $this->load($string, $isFile, $type);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Creates a new document.
83
+ *
84
+ * @param string|null $string An HTML or XML string or a file path
85
+ * @param bool $isFile Indicates that the first parameter is a path to a file
86
+ * @param string $encoding The document encoding
87
+ * @param string $type The document type
88
+ *
89
+ * @return Document
90
+ */
91
+ public static function create($string = null, $isFile = false, $encoding = 'UTF-8', $type = Document::TYPE_HTML)
92
+ {
93
+ return new Document($string, $isFile, $encoding, $type);
94
+ }
95
+
96
+ /**
97
+ * Creates a new element node.
98
+ *
99
+ * @param string $name The tag name of the element
100
+ * @param string|null $value The value of the element
101
+ * @param array $attributes The attributes of the element
102
+ *
103
+ * @return Element created element
104
+ */
105
+ public function createElement($name, $value = null, array $attributes = [])
106
+ {
107
+ $node = $this->document->createElement($name);
108
+
109
+ return new Element($node, $value, $attributes);
110
+ }
111
+
112
+ /**
113
+ * Creates a new element node by CSS selector.
114
+ *
115
+ * @param string $selector
116
+ * @param string|null $value
117
+ * @param array $attributes
118
+ *
119
+ * @return Element
120
+ *
121
+ * @throws InvalidSelectorException
122
+ */
123
+ public function createElementBySelector($selector, $value = null, array $attributes = [])
124
+ {
125
+ $segments = Query::getSegments($selector);
126
+
127
+ $name = array_key_exists('tag', $segments) ? $segments['tag'] : 'div';
128
+
129
+ if (array_key_exists('attributes', $segments)) {
130
+ $attributes = array_merge($attributes, $segments['attributes']);
131
+ }
132
+
133
+ if (array_key_exists('id', $segments)) {
134
+ $attributes['id'] = $segments['id'];
135
+ }
136
+
137
+ if (array_key_exists('classes', $segments)) {
138
+ $attributes['class'] = implode(' ', $segments['classes']);
139
+ }
140
+
141
+ return $this->createElement($name, $value, $attributes);
142
+ }
143
+
144
+ /**
145
+ * @param string $content
146
+ *
147
+ * @return Element
148
+ */
149
+ public function createTextNode($content)
150
+ {
151
+ return new Element(new DOMText($content));
152
+ }
153
+
154
+ /**
155
+ * @param string $data
156
+ *
157
+ * @return Element
158
+ */
159
+ public function createComment($data)
160
+ {
161
+ return new Element(new DOMComment($data));
162
+ }
163
+
164
+ /**
165
+ * @param string $data
166
+ *
167
+ * @return Element
168
+ */
169
+ public function createCdataSection($data)
170
+ {
171
+ return new Element(new DOMCdataSection($data));
172
+ }
173
+
174
+ /**
175
+ * @return DocumentFragment
176
+ */
177
+ public function createDocumentFragment()
178
+ {
179
+ return new DocumentFragment($this->document->createDocumentFragment());
180
+ }
181
+
182
+ /**
183
+ * Adds a new child at the end of the children.
184
+ *
185
+ * @param Element|DOMNode|array $nodes The appended child
186
+ *
187
+ * @return Element|Element[]
188
+ *
189
+ * @throws InvalidArgumentException if one of elements of parameter 1 is not an instance of DOMNode or Element
190
+ */
191
+ public function appendChild($nodes)
192
+ {
193
+ $returnArray = true;
194
+
195
+ if ( ! is_array($nodes)) {
196
+ $nodes = [$nodes];
197
+
198
+ $returnArray = false;
199
+ }
200
+
201
+ $result = [];
202
+
203
+ foreach ($nodes as $node) {
204
+ if ($node instanceof Element) {
205
+ $node = $node->getNode();
206
+ }
207
+
208
+ if ( ! $node instanceof DOMNode) {
209
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s\Element or DOMNode, %s given', __METHOD__, __NAMESPACE__, (is_object($node) ? get_class($node) : gettype($node))));
210
+ }
211
+
212
+ Errors::disable();
213
+
214
+ $cloned = $node->cloneNode(true);
215
+ $newNode = $this->document->importNode($cloned, true);
216
+
217
+ $result[] = $this->document->appendChild($newNode);
218
+
219
+ Errors::restore();
220
+ }
221
+
222
+ $result = array_map(function (DOMNode $node) {
223
+ return new Element($node);
224
+ }, $result);
225
+
226
+ return $returnArray ? $result : $result[0];
227
+ }
228
+
229
+ /**
230
+ * Set preserveWhiteSpace property.
231
+ *
232
+ * @param bool $value
233
+ *
234
+ * @return Document
235
+ */
236
+ public function preserveWhiteSpace($value = true)
237
+ {
238
+ if ( ! is_bool($value)) {
239
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be boolean, %s given', __METHOD__, gettype($value)));
240
+ }
241
+
242
+ $this->document->preserveWhiteSpace = $value;
243
+
244
+ return $this;
245
+ }
246
+
247
+ /**
248
+ * Load HTML or XML.
249
+ *
250
+ * @param string $string An HTML or XML string or a file path
251
+ * @param bool $isFile Indicates that the first parameter is a file path
252
+ * @param string $type The type of a document
253
+ * @param int|null $options libxml option constants
254
+ *
255
+ * @return Document
256
+ *
257
+ * @throws InvalidArgumentException if parameter 1 is not a string
258
+ * @throws InvalidArgumentException if parameter 3 is not a string
259
+ * @throws InvalidArgumentException if parameter 4 is not an integer or null
260
+ * @throws RuntimeException if the document type is invalid (not Document::TYPE_HTML or Document::TYPE_XML)
261
+ */
262
+ public function load($string, $isFile = false, $type = Document::TYPE_HTML, $options = null)
263
+ {
264
+ if ( ! is_string($string)) {
265
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($string) ? get_class($string) : gettype($string))));
266
+ }
267
+
268
+ if ( ! is_string($type)) {
269
+ throw new InvalidArgumentException(sprintf('%s expects parameter 3 to be string, %s given', __METHOD__, (is_object($type) ? get_class($type) : gettype($type))));
270
+ }
271
+
272
+ if ( ! in_array(strtolower($type), [Document::TYPE_HTML, Document::TYPE_XML], true)) {
273
+ throw new RuntimeException(sprintf('Document type must be "xml" or "html", %s given', $type));
274
+ }
275
+
276
+ if ($options === null) {
277
+ // LIBXML_HTML_NODEFDTD - prevents a default doctype being added when one is not found
278
+ $options = LIBXML_HTML_NODEFDTD;
279
+ }
280
+
281
+ if ( ! is_int($options)) {
282
+ throw new InvalidArgumentException(sprintf('%s expects parameter 4 to be integer, %s given', __METHOD__, (is_object($options) ? get_class($options) : gettype($options))));
283
+ }
284
+
285
+ $string = trim($string);
286
+
287
+ if ($isFile) {
288
+ $string = $this->loadFile($string);
289
+ }
290
+
291
+ if (strtolower($type) === Document::TYPE_HTML) {
292
+ $string = Encoder::convertToHtmlEntities($string, $this->encoding);
293
+ }
294
+
295
+ $this->type = strtolower($type);
296
+
297
+ Errors::disable();
298
+
299
+ if ($this->type === Document::TYPE_HTML) {
300
+ $this->document->loadHtml($string, $options);
301
+ } else {
302
+ $this->document->loadXml($string, $options);
303
+ }
304
+
305
+ Errors::restore();
306
+
307
+ return $this;
308
+ }
309
+
310
+ /**
311
+ * Load HTML from a string.
312
+ *
313
+ * @param string $html The HTML string
314
+ * @param int|null $options Additional parameters
315
+ *
316
+ * @return Document
317
+ *
318
+ * @throws InvalidArgumentException if parameter 1 is not a string
319
+ */
320
+ public function loadHtml($html, $options = null)
321
+ {
322
+ return $this->load($html, false, Document::TYPE_HTML, $options);
323
+ }
324
+
325
+ /**
326
+ * Load HTML from a file.
327
+ *
328
+ * @param string $filename The path to the HTML file
329
+ * @param int|null $options Additional parameters
330
+ *
331
+ * @return Document
332
+ *
333
+ * @throws InvalidArgumentException if parameter 1 not a string
334
+ * @throws RuntimeException if the file doesn't exist
335
+ * @throws RuntimeException if you are unable to load the file
336
+ */
337
+ public function loadHtmlFile($filename, $options = null)
338
+ {
339
+ return $this->load($filename, true, Document::TYPE_HTML, $options);
340
+ }
341
+
342
+ /**
343
+ * Load XML from a string.
344
+ *
345
+ * @param string $xml The XML string
346
+ * @param int|null $options Additional parameters
347
+ *
348
+ * @return Document
349
+ *
350
+ * @throws InvalidArgumentException if parameter 1 is not a string
351
+ */
352
+ public function loadXml($xml, $options = null)
353
+ {
354
+ return $this->load($xml, false, Document::TYPE_XML, $options);
355
+ }
356
+
357
+ /**
358
+ * Load XML from a file.
359
+ *
360
+ * @param string $filename The path to the XML file
361
+ * @param int|null $options Additional parameters
362
+ *
363
+ * @return Document
364
+ *
365
+ * @throws InvalidArgumentException if the file path is not a string
366
+ * @throws RuntimeException if the file doesn't exist
367
+ * @throws RuntimeException if you are unable to load the file
368
+ */
369
+ public function loadXmlFile($filename, $options = null)
370
+ {
371
+ return $this->load($filename, true, Document::TYPE_XML, $options);
372
+ }
373
+
374
+ /**
375
+ * Reads entire file into a string.
376
+ *
377
+ * @param string $filename The path to the file
378
+ *
379
+ * @return string
380
+ *
381
+ * @throws InvalidArgumentException if parameter 1 is not a string
382
+ * @throws RuntimeException if an error occurred
383
+ */
384
+ protected function loadFile($filename)
385
+ {
386
+ if ( ! is_string($filename)) {
387
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, gettype($filename)));
388
+ }
389
+
390
+ try {
391
+ $content = file_get_contents($filename);
392
+ } catch (Exception $exception) {
393
+ throw new RuntimeException(sprintf('Could not load file %s', $filename));
394
+ }
395
+
396
+ if ($content === false) {
397
+ throw new RuntimeException(sprintf('Could not load file %s', $filename));
398
+ }
399
+
400
+ return $content;
401
+ }
402
+
403
+ /**
404
+ * Checks the existence of the node.
405
+ *
406
+ * @param string $expression XPath expression or CSS selector
407
+ * @param string $type The type of the expression
408
+ *
409
+ * @return bool
410
+ */
411
+ public function has($expression, $type = Query::TYPE_CSS)
412
+ {
413
+ $expression = Query::compile($expression, $type);
414
+ $expression = sprintf('count(%s) > 0', $expression);
415
+
416
+ return $this->createXpath()->evaluate($expression);
417
+ }
418
+
419
+ /**
420
+ * Searches for a node in the DOM tree for a given XPath expression or CSS selector.
421
+ *
422
+ * @param string $expression XPath expression or a CSS selector
423
+ * @param string $type The type of the expression
424
+ * @param bool $wrapNode Returns array of Element if true, otherwise array of DOMElement
425
+ * @param DOMElement|null $contextNode The node in which the search will be performed
426
+ *
427
+ * @return Element[]|DOMElement[]
428
+ *
429
+ * @throws InvalidSelectorException if the selector is invalid
430
+ * @throws InvalidArgumentException if context node is not DOMElement
431
+ */
432
+ public function find($expression, $type = Query::TYPE_CSS, $wrapNode = true, $contextNode = null)
433
+ {
434
+ $expression = Query::compile($expression, $type);
435
+
436
+ if ($contextNode !== null) {
437
+ if ($contextNode instanceof Element) {
438
+ $contextNode = $contextNode->getNode();
439
+ }
440
+
441
+ if ( ! $contextNode instanceof DOMElement) {
442
+ throw new InvalidArgumentException(sprintf('Argument 4 passed to %s must be an instance of %s\Element or DOMElement, %s given', __METHOD__, __NAMESPACE__, (is_object($contextNode) ? get_class($contextNode) : gettype($contextNode))));
443
+ }
444
+
445
+ if ($type === Query::TYPE_CSS) {
446
+ $expression = '.' . $expression;
447
+ }
448
+ }
449
+
450
+ $nodeList = $this->createXpath()->query($expression, $contextNode);
451
+
452
+ $result = [];
453
+
454
+ if ($wrapNode) {
455
+ foreach ($nodeList as $node) {
456
+ $result[] = $this->wrapNode($node);
457
+ }
458
+ } else {
459
+ foreach ($nodeList as $node) {
460
+ $result[] = $node;
461
+ }
462
+ }
463
+
464
+ return $result;
465
+ }
466
+
467
+ /**
468
+ * Searches for a node in the DOM tree and returns first element or null.
469
+ *
470
+ * @param string $expression XPath expression or a CSS selector
471
+ * @param string $type The type of the expression
472
+ * @param bool $wrapNode Returns array of Element if true, otherwise array of DOMElement
473
+ * @param DOMElement|null $contextNode The node in which the search will be performed
474
+ *
475
+ * @return Element|DOMElement|null
476
+ *
477
+ * @throws InvalidSelectorException if the selector is invalid
478
+ */
479
+ public function first($expression, $type = Query::TYPE_CSS, $wrapNode = true, $contextNode = null)
480
+ {
481
+ $expression = Query::compile($expression, $type);
482
+
483
+ if ($contextNode !== null && $type === Query::TYPE_CSS) {
484
+ $expression = '.' . $expression;
485
+ }
486
+
487
+ $expression = sprintf('(%s)[1]', $expression);
488
+
489
+ $nodes = $this->find($expression, Query::TYPE_XPATH, false, $contextNode);
490
+
491
+ if (count($nodes) === 0) {
492
+ return null;
493
+ }
494
+
495
+ return $wrapNode ? $this->wrapNode($nodes[0]) : $nodes[0];
496
+ }
497
+
498
+ /**
499
+ * @param DOMElement|DOMText|DOMAttr $node
500
+ *
501
+ * @return Element|string
502
+ *
503
+ * @throws InvalidArgumentException if parameter 1 is not an instance of DOMElement, DOMText, DOMComment, DOMCdataSection or DOMAttr
504
+ */
505
+ protected function wrapNode($node)
506
+ {
507
+ switch (get_class($node)) {
508
+ case 'DOMElement':
509
+ case 'DOMComment':
510
+ case 'DOMCdataSection':
511
+ return new Element($node);
512
+
513
+ case 'DOMText':
514
+ return $node->data;
515
+
516
+ case 'DOMAttr':
517
+ return $node->value;
518
+ }
519
+
520
+ throw new InvalidArgumentException(sprintf('Unknown node type "%s"', get_class($node)));
521
+ }
522
+
523
+ /**
524
+ * Searches for a node in the DOM tree for a given XPath expression.
525
+ *
526
+ * @param string $expression XPath expression
527
+ * @param bool $wrapNode Returns array of Element if true, otherwise array of DOMElement
528
+ * @param DOMElement $contextNode The node in which the search will be performed
529
+ *
530
+ * @return Element[]|DOMElement[]
531
+ */
532
+ public function xpath($expression, $wrapNode = true, $contextNode = null)
533
+ {
534
+ return $this->find($expression, Query::TYPE_XPATH, $wrapNode, $contextNode);
535
+ }
536
+
537
+ /**
538
+ * Counts nodes for a given XPath expression or CSS selector.
539
+ *
540
+ * @param string $expression XPath expression or CSS selector
541
+ * @param string $type The type of the expression
542
+ *
543
+ * @return int
544
+ *
545
+ * @throws InvalidSelectorException
546
+ */
547
+ public function count($expression, $type = Query::TYPE_CSS)
548
+ {
549
+ $expression = Query::compile($expression, $type);
550
+ $expression = sprintf('count(%s)', $expression);
551
+
552
+ return (int) $this->createXpath()->evaluate($expression);
553
+ }
554
+
555
+ /**
556
+ * @return DOMXPath
557
+ */
558
+ public function createXpath()
559
+ {
560
+ $xpath = new DOMXPath($this->document);
561
+
562
+ foreach ($this->namespaces as $prefix => $namespace) {
563
+ $xpath->registerNamespace($prefix, $namespace);
564
+ }
565
+
566
+ $xpath->registerPhpFunctions();
567
+
568
+ return $xpath;
569
+ }
570
+
571
+ /**
572
+ * Register a namespace.
573
+ *
574
+ * @param string $prefix
575
+ * @param string $namespace
576
+ */
577
+ public function registerNamespace($prefix, $namespace)
578
+ {
579
+ if ( ! is_string($prefix)) {
580
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, (is_object($prefix) ? get_class($prefix) : gettype($prefix))));
581
+ }
582
+
583
+ if ( ! is_string($namespace)) {
584
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, (is_object($namespace) ? get_class($namespace) : gettype($namespace))));
585
+ }
586
+
587
+ $this->namespaces[$prefix] = $namespace;
588
+ }
589
+
590
+ /**
591
+ * Dumps the internal document into a string using HTML formatting.
592
+ *
593
+ * @return string The document html
594
+ */
595
+ public function html()
596
+ {
597
+ return trim($this->document->saveHTML($this->document));
598
+ }
599
+
600
+ /**
601
+ * Dumps the internal document into a string using XML formatting.
602
+ *
603
+ * @param int $options Additional options
604
+ *
605
+ * @return string The document xml
606
+ */
607
+ public function xml($options = 0)
608
+ {
609
+ return trim($this->document->saveXML($this->document, $options));
610
+ }
611
+
612
+ /**
613
+ * Nicely formats output with indentation and extra space.
614
+ *
615
+ * @param bool $format Formats output if true
616
+ *
617
+ * @return Document
618
+ */
619
+ public function format($format = true)
620
+ {
621
+ if ( ! is_bool($format)) {
622
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be boolean, %s given', __METHOD__, gettype($format)));
623
+ }
624
+
625
+ $this->document->formatOutput = $format;
626
+
627
+ return $this;
628
+ }
629
+
630
+ /**
631
+ * Get the text content of this node and its descendants.
632
+ *
633
+ * @return string
634
+ */
635
+ public function text()
636
+ {
637
+ return $this->getElement()->textContent;
638
+ }
639
+
640
+ /**
641
+ * Indicates if two documents are the same document.
642
+ *
643
+ * @param Document|DOMDocument $document The compared document
644
+ *
645
+ * @return bool
646
+ *
647
+ * @throws InvalidArgumentException if parameter 1 is not an instance of DOMDocument or Document
648
+ */
649
+ public function is($document)
650
+ {
651
+ if ($document instanceof Document) {
652
+ $element = $document->getElement();
653
+ } else {
654
+ if ( ! $document instanceof DOMDocument) {
655
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMDocument, %s given', __METHOD__, __CLASS__, (is_object($document) ? get_class($document) : gettype($document))));
656
+ }
657
+
658
+ $element = $document->documentElement;
659
+ }
660
+
661
+ if ($element === null) {
662
+ return false;
663
+ }
664
+
665
+ return $this->getElement()->isSameNode($element);
666
+ }
667
+
668
+ /**
669
+ * Returns the type of the document (XML or HTML).
670
+ *
671
+ * @return string
672
+ */
673
+ public function getType()
674
+ {
675
+ return $this->type;
676
+ }
677
+
678
+ /**
679
+ * Returns the encoding of the document.
680
+ *
681
+ * @return string
682
+ */
683
+ public function getEncoding()
684
+ {
685
+ return $this->encoding;
686
+ }
687
+
688
+ /**
689
+ * @return DOMDocument
690
+ */
691
+ public function getDocument()
692
+ {
693
+ return $this->document;
694
+ }
695
+
696
+ /**
697
+ * @return DOMElement
698
+ */
699
+ public function getElement()
700
+ {
701
+ return $this->document->documentElement;
702
+ }
703
+
704
+ /**
705
+ * @return Element
706
+ */
707
+ public function toElement()
708
+ {
709
+ if ($this->document->documentElement === null) {
710
+ throw new RuntimeException('Cannot convert empty document to Element');
711
+ }
712
+
713
+ return new Element($this->document->documentElement);
714
+ }
715
+
716
+ /**
717
+ * Convert the document to its string representation.
718
+ *
719
+ * @return string
720
+ */
721
+ public function __toString()
722
+ {
723
+ return $this->type === Document::TYPE_HTML ? $this->html() : $this->xml();
724
+ }
725
+
726
+ /**
727
+ * Searches for a node in the DOM tree for a given XPath expression or CSS selector.
728
+ *
729
+ * @param string $expression XPath expression or a CSS selector
730
+ * @param string $type The type of the expression
731
+ * @param bool $wrapNode Returns array of Element if true, otherwise array of DOMElement
732
+ * @param DOMElement|null $contextNode The node in which the search will be performed
733
+ *
734
+ * @return Element[]|DOMElement[]
735
+ *
736
+ * @throws InvalidSelectorException
737
+ *
738
+ * @deprecated Not longer recommended, use Document::find() instead.
739
+ */
740
+ public function __invoke($expression, $type = Query::TYPE_CSS, $wrapNode = true, $contextNode = null)
741
+ {
742
+ return $this->find($expression, $type, $wrapNode, $contextNode);
743
+ }
744
+ }
vendor/imangazaliev/didom/src/DiDom/DocumentFragment.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use DOMDocumentFragment;
6
+ use InvalidArgumentException;
7
+
8
+ /**
9
+ * @property string $tag
10
+ */
11
+ class DocumentFragment extends Node
12
+ {
13
+ /**
14
+ * @param DOMDocumentFragment $documentFragment
15
+ */
16
+ public function __construct($documentFragment)
17
+ {
18
+ if ( ! $documentFragment instanceof DOMDocumentFragment) {
19
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of DOMDocumentFragment, %s given', __METHOD__, (is_object($documentFragment) ? get_class($documentFragment) : gettype($documentFragment))));
20
+ }
21
+
22
+ $this->setNode($documentFragment);
23
+ }
24
+
25
+ /**
26
+ * Append raw XML data.
27
+ *
28
+ * @param string $data
29
+ */
30
+ public function appendXml($data)
31
+ {
32
+ $this->node->appendXML($data);
33
+ }
34
+ }
vendor/imangazaliev/didom/src/DiDom/Element.php ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use DiDom\Exceptions\InvalidSelectorException;
6
+ use DOMCdataSection;
7
+ use DOMComment;
8
+ use DOMDocument;
9
+ use DOMElement;
10
+ use DOMNode;
11
+ use DOMText;
12
+ use InvalidArgumentException;
13
+ use LogicException;
14
+ use RuntimeException;
15
+
16
+ /**
17
+ * @property string $tag
18
+ */
19
+ class Element extends Node
20
+ {
21
+ /**
22
+ * @var ClassAttribute
23
+ */
24
+ protected $classAttribute;
25
+
26
+ /**
27
+ * @var StyleAttribute
28
+ */
29
+ protected $styleAttribute;
30
+
31
+ /**
32
+ * @param DOMElement|DOMText|DOMComment|DOMCdataSection|string $tagName The tag name of an element
33
+ * @param string|null $value The value of an element
34
+ * @param array $attributes The attributes of an element
35
+ */
36
+ public function __construct($tagName, $value = null, array $attributes = [])
37
+ {
38
+ if (is_string($tagName)) {
39
+ $document = new DOMDocument('1.0', 'UTF-8');
40
+
41
+ $node = $document->createElement($tagName);
42
+
43
+ $this->setNode($node);
44
+ } else {
45
+ $this->setNode($tagName);
46
+ }
47
+
48
+ if ($value !== null) {
49
+ $this->setValue($value);
50
+ }
51
+
52
+ foreach ($attributes as $attrName => $attrValue) {
53
+ $this->setAttribute($attrName, $attrValue);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Creates a new element.
59
+ *
60
+ * @param DOMNode|string $name The tag name of an element
61
+ * @param string|null $value The value of an element
62
+ * @param array $attributes The attributes of an element
63
+ *
64
+ * @return Element
65
+ */
66
+ public static function create($name, $value = null, array $attributes = [])
67
+ {
68
+ return new Element($name, $value, $attributes);
69
+ }
70
+
71
+ /**
72
+ * Creates a new element node by CSS selector.
73
+ *
74
+ * @param string $selector
75
+ * @param string|null $value
76
+ * @param array $attributes
77
+ *
78
+ * @return Element
79
+ *
80
+ * @throws InvalidSelectorException
81
+ */
82
+ public static function createBySelector($selector, $value = null, array $attributes = [])
83
+ {
84
+ return Document::create()->createElementBySelector($selector, $value, $attributes);
85
+ }
86
+
87
+ /**
88
+ * Checks that the node matches selector.
89
+ *
90
+ * @param string $selector CSS selector
91
+ * @param bool $strict
92
+ *
93
+ * @return bool
94
+ *
95
+ * @throws InvalidSelectorException if the selector is invalid
96
+ * @throws InvalidArgumentException if the tag name is not a string
97
+ * @throws RuntimeException if the tag name is not specified in strict mode
98
+ */
99
+ public function matches($selector, $strict = false)
100
+ {
101
+ if ( ! is_string($selector)) {
102
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, gettype($selector)));
103
+ }
104
+
105
+ if ( ! $this->node instanceof DOMElement) {
106
+ return false;
107
+ }
108
+
109
+ if ($selector === '*') {
110
+ return true;
111
+ }
112
+
113
+ if ( ! $strict) {
114
+ $innerHtml = $this->html();
115
+ $html = "<root>$innerHtml</root>";
116
+
117
+ $selector = 'root > ' . trim($selector);
118
+
119
+ $document = new Document();
120
+
121
+ $document->loadHtml($html, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED);
122
+
123
+ return $document->has($selector);
124
+ }
125
+
126
+ $segments = Query::getSegments($selector);
127
+
128
+ if ( ! array_key_exists('tag', $segments)) {
129
+ throw new RuntimeException(sprintf('Tag name must be specified in %s', $selector));
130
+ }
131
+
132
+ if ($segments['tag'] !== $this->tag && $segments['tag'] !== '*') {
133
+ return false;
134
+ }
135
+
136
+ $segments['id'] = array_key_exists('id', $segments) ? $segments['id'] : null;
137
+
138
+ if ($segments['id'] !== $this->getAttribute('id')) {
139
+ return false;
140
+ }
141
+
142
+ $classes = $this->hasAttribute('class') ? explode(' ', trim($this->getAttribute('class'))) : [];
143
+
144
+ $segments['classes'] = array_key_exists('classes', $segments) ? $segments['classes'] : [];
145
+
146
+ $diff1 = array_diff($segments['classes'], $classes);
147
+ $diff2 = array_diff($classes, $segments['classes']);
148
+
149
+ if (count($diff1) > 0 || count($diff2) > 0) {
150
+ return false;
151
+ }
152
+
153
+ $attributes = $this->attributes();
154
+
155
+ unset($attributes['id'], $attributes['class']);
156
+
157
+ $segments['attributes'] = array_key_exists('attributes', $segments) ? $segments['attributes'] : [];
158
+
159
+ $diff1 = array_diff_assoc($segments['attributes'], $attributes);
160
+ $diff2 = array_diff_assoc($attributes, $segments['attributes']);
161
+
162
+ // if the attributes are not equal
163
+ if (count($diff1) > 0 || count($diff2) > 0) {
164
+ return false;
165
+ }
166
+
167
+ return true;
168
+ }
169
+
170
+ /**
171
+ * Determine if an attribute exists on the element.
172
+ *
173
+ * @param string $name The name of an attribute
174
+ *
175
+ * @return bool
176
+ */
177
+ public function hasAttribute($name)
178
+ {
179
+ return $this->node->hasAttribute($name);
180
+ }
181
+
182
+ /**
183
+ * Set an attribute on the element.
184
+ *
185
+ * @param string $name The name of an attribute
186
+ * @param string $value The value of an attribute
187
+ *
188
+ * @return Element
189
+ */
190
+ public function setAttribute($name, $value)
191
+ {
192
+ if (is_numeric($value)) {
193
+ $value = (string) $value;
194
+ }
195
+
196
+ if ( ! is_string($value) && $value !== null) {
197
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string or null, %s given', __METHOD__, (is_object($value) ? get_class($value) : gettype($value))));
198
+ }
199
+
200
+ $this->node->setAttribute($name, $value);
201
+
202
+ return $this;
203
+ }
204
+
205
+ /**
206
+ * Access to the element's attributes.
207
+ *
208
+ * @param string $name The name of an attribute
209
+ * @param string|null $default The value returned if the attribute doesn't exist
210
+ *
211
+ * @return string|null The value of an attribute or null if attribute doesn't exist
212
+ */
213
+ public function getAttribute($name, $default = null)
214
+ {
215
+ if ($this->hasAttribute($name)) {
216
+ return $this->node->getAttribute($name);
217
+ }
218
+
219
+ return $default;
220
+ }
221
+
222
+ /**
223
+ * Unset an attribute on the element.
224
+ *
225
+ * @param string $name The name of an attribute
226
+ *
227
+ * @return Element
228
+ */
229
+ public function removeAttribute($name)
230
+ {
231
+ $this->node->removeAttribute($name);
232
+
233
+ return $this;
234
+ }
235
+
236
+ /**
237
+ * Unset all attributes of the element.
238
+ *
239
+ * @param string[] $exclusions
240
+ *
241
+ * @return Element
242
+ */
243
+ public function removeAllAttributes(array $exclusions = [])
244
+ {
245
+ if ( ! $this->node instanceof DOMElement) {
246
+ return $this;
247
+ }
248
+
249
+ foreach ($this->attributes() as $name => $value) {
250
+ if (in_array($name, $exclusions, true)) {
251
+ continue;
252
+ }
253
+
254
+ $this->node->removeAttribute($name);
255
+ }
256
+
257
+ return $this;
258
+ }
259
+
260
+ /**
261
+ * Alias for getAttribute and setAttribute methods.
262
+ *
263
+ * @param string $name The name of an attribute
264
+ * @param string|null $value The value that will be returned an attribute doesn't exist
265
+ *
266
+ * @return string|null|Element
267
+ */
268
+ public function attr($name, $value = null)
269
+ {
270
+ if ($value === null) {
271
+ return $this->getAttribute($name);
272
+ }
273
+
274
+ return $this->setAttribute($name, $value);
275
+ }
276
+
277
+ /**
278
+ * Returns the node attributes or null, if it is not DOMElement.
279
+ *
280
+ * @param string[] $names
281
+ *
282
+ * @return array|null
283
+ */
284
+ public function attributes(array $names = null)
285
+ {
286
+ if ( ! $this->node instanceof DOMElement) {
287
+ return null;
288
+ }
289
+
290
+ if ($names === null) {
291
+ $result = [];
292
+
293
+ foreach ($this->node->attributes as $name => $attribute) {
294
+ $result[$name] = $attribute->value;
295
+ }
296
+
297
+ return $result;
298
+ }
299
+
300
+ $result = [];
301
+
302
+ foreach ($this->node->attributes as $name => $attribute) {
303
+ if (in_array($name, $names, true)) {
304
+ $result[$name] = $attribute->value;
305
+ }
306
+ }
307
+
308
+ return $result;
309
+ }
310
+
311
+ /**
312
+ * @return ClassAttribute
313
+ *
314
+ * @throws LogicException if the node is not an instance of DOMElement
315
+ */
316
+ public function classes()
317
+ {
318
+ if ($this->classAttribute !== null) {
319
+ return $this->classAttribute;
320
+ }
321
+
322
+ if ( ! $this->isElementNode()) {
323
+ throw new LogicException('Class attribute is available only for element nodes');
324
+ }
325
+
326
+ $this->classAttribute = new ClassAttribute($this);
327
+
328
+ return $this->classAttribute;
329
+ }
330
+
331
+ /**
332
+ * @return StyleAttribute
333
+ *
334
+ * @throws LogicException if the node is not an instance of DOMElement
335
+ */
336
+ public function style()
337
+ {
338
+ if ($this->styleAttribute !== null) {
339
+ return $this->styleAttribute;
340
+ }
341
+
342
+ if ( ! $this->isElementNode()) {
343
+ throw new LogicException('Style attribute is available only for element nodes');
344
+ }
345
+
346
+ $this->styleAttribute = new StyleAttribute($this);
347
+
348
+ return $this->styleAttribute;
349
+ }
350
+
351
+ /**
352
+ * Dynamically set an attribute on the element.
353
+ *
354
+ * @param string $name The name of an attribute
355
+ * @param string $value The value of an attribute
356
+ *
357
+ * @return Element
358
+ */
359
+ public function __set($name, $value)
360
+ {
361
+ return $this->setAttribute($name, $value);
362
+ }
363
+
364
+ /**
365
+ * Dynamically access the element's attributes.
366
+ *
367
+ * @param string $name The name of an attribute
368
+ *
369
+ * @return string|null
370
+ */
371
+ public function __get($name)
372
+ {
373
+ if ($name === 'tag') {
374
+ return $this->node->tagName;
375
+ }
376
+
377
+ return $this->getAttribute($name);
378
+ }
379
+
380
+ /**
381
+ * Determine if an attribute exists on the element.
382
+ *
383
+ * @param string $name The attribute name
384
+ *
385
+ * @return bool
386
+ */
387
+ public function __isset($name)
388
+ {
389
+ return $this->hasAttribute($name);
390
+ }
391
+
392
+ /**
393
+ * Unset an attribute on the model.
394
+ *
395
+ * @param string $name The name of an attribute
396
+ */
397
+ public function __unset($name)
398
+ {
399
+ $this->removeAttribute($name);
400
+ }
401
+ }
vendor/imangazaliev/didom/src/DiDom/Encoder.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ class Encoder
6
+ {
7
+ /**
8
+ * @param string $string
9
+ * @param string $encoding
10
+ *
11
+ * @return string
12
+ */
13
+ public static function convertToHtmlEntities($string, $encoding)
14
+ {
15
+ if (function_exists('mb_convert_encoding')) {
16
+ return mb_convert_encoding($string, 'HTML-ENTITIES', $encoding);
17
+ }
18
+
19
+ if ('UTF-8' !== $encoding) {
20
+ $string = iconv($encoding, 'UTF-8//IGNORE', $string);
21
+ }
22
+
23
+ return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'htmlEncodingCallback'], $string);
24
+ }
25
+
26
+ /**
27
+ * @param string[] $matches
28
+ *
29
+ * @return string
30
+ */
31
+ private static function htmlEncodingCallback($matches)
32
+ {
33
+ $characterIndex = 1;
34
+ $entities = '';
35
+
36
+ $codes = unpack('C*', htmlentities($matches[0], ENT_COMPAT, 'UTF-8'));
37
+
38
+ while (isset($codes[$characterIndex])) {
39
+ if (0x80 > $codes[$characterIndex]) {
40
+ $entities .= chr($codes[$characterIndex++]);
41
+
42
+ continue;
43
+ }
44
+
45
+ if (0xF0 <= $codes[$characterIndex]) {
46
+ $code = (($codes[$characterIndex++] - 0xF0) << 18) + (($codes[$characterIndex++] - 0x80) << 12) + (($codes[$characterIndex++] - 0x80) << 6) + $codes[$characterIndex++] - 0x80;
47
+ } elseif (0xE0 <= $codes[$characterIndex]) {
48
+ $code = (($codes[$characterIndex++] - 0xE0) << 12) + (($codes[$characterIndex++] - 0x80) << 6) + $codes[$characterIndex++] - 0x80;
49
+ } else {
50
+ $code = (($codes[$characterIndex++] - 0xC0) << 6) + $codes[$characterIndex++] - 0x80;
51
+ }
52
+
53
+ $entities .= '&#' . $code . ';';
54
+ }
55
+
56
+ return $entities;
57
+ }
58
+ }
vendor/imangazaliev/didom/src/DiDom/Errors.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ class Errors
6
+ {
7
+ /**
8
+ * @var bool
9
+ */
10
+ protected static $internalErrors;
11
+
12
+ /**
13
+ * @var bool
14
+ */
15
+ protected static $disableEntities;
16
+
17
+ /**
18
+ * Disable error reporting.
19
+ */
20
+ public static function disable()
21
+ {
22
+ self::$internalErrors = libxml_use_internal_errors(true);
23
+
24
+ if (\LIBXML_VERSION < 20900) {
25
+ self::$disableEntities = libxml_disable_entity_loader(true);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Restore error reporting.
31
+ *
32
+ * @param bool $clear
33
+ */
34
+ public static function restore($clear = true)
35
+ {
36
+ if ($clear) {
37
+ libxml_clear_errors();
38
+ }
39
+
40
+ libxml_use_internal_errors(self::$internalErrors);
41
+
42
+ if (\LIBXML_VERSION < 20900) {
43
+ libxml_disable_entity_loader(self::$disableEntities);
44
+ }
45
+ }
46
+ }
vendor/imangazaliev/didom/src/DiDom/Exceptions/InvalidSelectorException.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom\Exceptions;
4
+
5
+ use Exception;
6
+
7
+ class InvalidSelectorException extends Exception
8
+ {
9
+ //
10
+ }
vendor/imangazaliev/didom/src/DiDom/Node.php ADDED
@@ -0,0 +1,1185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use DiDom\Exceptions\InvalidSelectorException;
6
+ use DOMCdataSection;
7
+ use DOMComment;
8
+ use DOMDocument;
9
+ use DOMDocumentFragment;
10
+ use DOMElement;
11
+ use DOMNode;
12
+ use DOMText;
13
+ use InvalidArgumentException;
14
+ use LogicException;
15
+ use RuntimeException;
16
+
17
+ /**
18
+ * @property string $tag
19
+ */
20
+ abstract class Node
21
+ {
22
+ /**
23
+ * The DOM element instance.
24
+ *
25
+ * @var DOMElement|DOMText|DOMComment|DOMCdataSection|DOMDocumentFragment
26
+ */
27
+ protected $node;
28
+
29
+ /**
30
+ * Adds a new child at the start of the children.
31
+ *
32
+ * @param Node|DOMNode|array $nodes The prepended child
33
+ *
34
+ * @return Element|Element[]
35
+ *
36
+ * @throws LogicException if the current node has no owner document
37
+ * @throws InvalidArgumentException if one of elements of parameter 1 is not an instance of DOMNode or Element
38
+ */
39
+ public function prependChild($nodes)
40
+ {
41
+ if ($this->node->ownerDocument === null) {
42
+ throw new LogicException('Can not prepend a child to element without the owner document');
43
+ }
44
+
45
+ $returnArray = true;
46
+
47
+ if ( ! is_array($nodes)) {
48
+ $nodes = [$nodes];
49
+
50
+ $returnArray = false;
51
+ }
52
+
53
+ $nodes = array_reverse($nodes);
54
+
55
+ $result = [];
56
+
57
+ $referenceNode = $this->node->firstChild;
58
+
59
+ foreach ($nodes as $node) {
60
+ $result[] = $this->insertBefore($node, $referenceNode);
61
+
62
+ $referenceNode = $this->node->firstChild;
63
+ }
64
+
65
+ return $returnArray ? $result : $result[0];
66
+ }
67
+
68
+ /**
69
+ * Adds a new child at the end of the children.
70
+ *
71
+ * @param Node|DOMNode|array $nodes The appended child
72
+ *
73
+ * @return Element|Element[]
74
+ *
75
+ * @throws LogicException if the current node has no owner document
76
+ * @throws InvalidArgumentException if the provided argument is not an instance of DOMNode or Element
77
+ */
78
+ public function appendChild($nodes)
79
+ {
80
+ if ($this->node->ownerDocument === null) {
81
+ throw new LogicException('Can not append a child to element without the owner document');
82
+ }
83
+
84
+ $returnArray = true;
85
+
86
+ if ( ! is_array($nodes)) {
87
+ $nodes = [$nodes];
88
+
89
+ $returnArray = false;
90
+ }
91
+
92
+ $result = [];
93
+
94
+ Errors::disable();
95
+
96
+ foreach ($nodes as $node) {
97
+ if ($node instanceof Node) {
98
+ $node = $node->getNode();
99
+ }
100
+
101
+ if ( ! $node instanceof DOMNode) {
102
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($node) ? get_class($node) : gettype($node))));
103
+ }
104
+
105
+ $clonedNode = $node->cloneNode(true);
106
+ $newNode = $this->node->ownerDocument->importNode($clonedNode, true);
107
+
108
+ $result[] = $this->node->appendChild($newNode);
109
+ }
110
+
111
+ Errors::restore();
112
+
113
+ $result = array_map(function (DOMNode $node) {
114
+ return new Element($node);
115
+ }, $result);
116
+
117
+ return $returnArray ? $result : $result[0];
118
+ }
119
+
120
+ /**
121
+ * Adds a new child before a reference node.
122
+ *
123
+ * @param Node|DOMNode $node The new node
124
+ * @param Element|DOMNode|null $referenceNode The reference node
125
+ *
126
+ * @return Element
127
+ *
128
+ * @throws LogicException if the current node has no owner document
129
+ * @throws InvalidArgumentException if $node is not an instance of DOMNode or Element
130
+ * @throws InvalidArgumentException if $referenceNode is not an instance of DOMNode or Element
131
+ */
132
+ public function insertBefore($node, $referenceNode = null)
133
+ {
134
+ if ($this->node->ownerDocument === null) {
135
+ throw new LogicException('Can not insert a child to an element without the owner document');
136
+ }
137
+
138
+ if ($node instanceof Node) {
139
+ $node = $node->getNode();
140
+ }
141
+
142
+ if ( ! $node instanceof DOMNode) {
143
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($node) ? get_class($node) : gettype($node))));
144
+ }
145
+
146
+ if ($referenceNode !== null) {
147
+ if ($referenceNode instanceof Element) {
148
+ $referenceNode = $referenceNode->getNode();
149
+ }
150
+
151
+ if ( ! $referenceNode instanceof DOMNode) {
152
+ throw new InvalidArgumentException(sprintf('Argument 2 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($referenceNode) ? get_class($referenceNode) : gettype($referenceNode))));
153
+ }
154
+ }
155
+
156
+ Errors::disable();
157
+
158
+ $clonedNode = $node->cloneNode(true);
159
+ $newNode = $this->node->ownerDocument->importNode($clonedNode, true);
160
+
161
+ $insertedNode = $this->node->insertBefore($newNode, $referenceNode);
162
+
163
+ Errors::restore();
164
+
165
+ return new Element($insertedNode);
166
+ }
167
+
168
+ /**
169
+ * Adds a new child after a reference node.
170
+ *
171
+ * @param Node|DOMNode $node The new node
172
+ * @param Element|DOMNode|null $referenceNode The reference node
173
+ *
174
+ * @return Element
175
+ *
176
+ * @throws LogicException if the current node has no owner document
177
+ * @throws InvalidArgumentException if $node is not an instance of DOMNode or Element
178
+ * @throws InvalidArgumentException if $referenceNode is not an instance of DOMNode or Element
179
+ */
180
+ public function insertAfter($node, $referenceNode = null)
181
+ {
182
+ if ($referenceNode === null) {
183
+ return $this->insertBefore($node);
184
+ }
185
+
186
+ if ($referenceNode instanceof Node) {
187
+ $referenceNode = $referenceNode->getNode();
188
+ }
189
+
190
+ if ( ! $referenceNode instanceof DOMNode) {
191
+ throw new InvalidArgumentException(sprintf('Argument 2 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($referenceNode) ? get_class($referenceNode) : gettype($referenceNode))));
192
+ }
193
+
194
+ return $this->insertBefore($node, $referenceNode->nextSibling);
195
+ }
196
+
197
+ /**
198
+ * Adds a new sibling before a reference node.
199
+ *
200
+ * @param Node|DOMNode $node The new node
201
+ *
202
+ * @return Element
203
+ *
204
+ * @throws LogicException if the current node has no owner document
205
+ * @throws InvalidArgumentException if $node is not an instance of DOMNode or Element
206
+ * @throws InvalidArgumentException if $referenceNode is not an instance of DOMNode or Element
207
+ */
208
+ public function insertSiblingBefore($node)
209
+ {
210
+ if ($this->node->ownerDocument === null) {
211
+ throw new LogicException('Can not insert a child to an element without the owner document');
212
+ }
213
+
214
+ if ($this->parent() === null) {
215
+ throw new LogicException('Can not insert a child to an element without the parent element');
216
+ }
217
+
218
+ if ($node instanceof Node) {
219
+ $node = $node->getNode();
220
+ }
221
+
222
+ if ( ! $node instanceof DOMNode) {
223
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($node) ? get_class($node) : gettype($node))));
224
+ }
225
+
226
+ Errors::disable();
227
+
228
+ $clonedNode = $node->cloneNode(true);
229
+ $newNode = $this->node->ownerDocument->importNode($clonedNode, true);
230
+
231
+ $insertedNode = $this->parent()->getNode()->insertBefore($newNode, $this->node);
232
+
233
+ Errors::restore();
234
+
235
+ return new Element($insertedNode);
236
+ }
237
+
238
+ /**
239
+ * Adds a new sibling after a reference node.
240
+ *
241
+ * @param Node|DOMNode $node The new node
242
+ *
243
+ * @return Element
244
+ *
245
+ * @throws LogicException if the current node has no owner document
246
+ * @throws InvalidArgumentException if $node is not an instance of DOMNode or Element
247
+ * @throws InvalidArgumentException if $referenceNode is not an instance of DOMNode or Element
248
+ */
249
+ public function insertSiblingAfter($node)
250
+ {
251
+ if ($this->node->ownerDocument === null) {
252
+ throw new LogicException('Can not insert a child to an element without the owner document');
253
+ }
254
+
255
+ if ($this->parent() === null) {
256
+ throw new LogicException('Can not insert a child to an element without the parent element');
257
+ }
258
+
259
+ $nextSibling = $this->nextSibling();
260
+
261
+ // if the current node is the last child
262
+ if ($nextSibling === null) {
263
+ return $this->parent()->appendChild($node);
264
+ }
265
+
266
+ return $nextSibling->insertSiblingBefore($node);
267
+ }
268
+
269
+ /**
270
+ * Checks the existence of the node.
271
+ *
272
+ * @param string $expression XPath expression or CSS selector
273
+ * @param string $type The type of the expression
274
+ *
275
+ * @return bool
276
+ */
277
+ public function has($expression, $type = Query::TYPE_CSS)
278
+ {
279
+ return $this->toDocument()->has($expression, $type);
280
+ }
281
+
282
+ /**
283
+ * Searches for a node in the DOM tree for a given XPath expression or CSS selector.
284
+ *
285
+ * @param string $expression XPath expression or CSS selector
286
+ * @param string $type The type of the expression
287
+ * @param bool $wrapElement Returns array of Element if true, otherwise array of DOMElement
288
+ *
289
+ * @return Element[]|DOMElement[]
290
+ *
291
+ * @throws InvalidSelectorException
292
+ */
293
+ public function find($expression, $type = Query::TYPE_CSS, $wrapElement = true)
294
+ {
295
+ return $this->toDocument()->find($expression, $type, $wrapElement);
296
+ }
297
+
298
+ /**
299
+ * Searches for a node in the owner document using current node as context.
300
+ *
301
+ * @param string $expression XPath expression or CSS selector
302
+ * @param string $type The type of the expression
303
+ * @param bool $wrapNode Returns array of Element if true, otherwise array of DOMElement
304
+ *
305
+ * @return Element[]|DOMElement[]
306
+ *
307
+ * @throws LogicException if the current node has no owner document
308
+ * @throws InvalidSelectorException
309
+ */
310
+ public function findInDocument($expression, $type = Query::TYPE_CSS, $wrapNode = true)
311
+ {
312
+ $ownerDocument = $this->getDocument();
313
+
314
+ if ($ownerDocument === null) {
315
+ throw new LogicException('Can not search in context without the owner document');
316
+ }
317
+
318
+ return $ownerDocument->find($expression, $type, $wrapNode, $this->node);
319
+ }
320
+
321
+ /**
322
+ * Searches for a node in the DOM tree and returns first element or null.
323
+ *
324
+ * @param string $expression XPath expression or CSS selector
325
+ * @param string $type The type of the expression
326
+ * @param bool $wrapNode Returns Element if true, otherwise DOMElement
327
+ *
328
+ * @return Element|DOMElement|null
329
+ *
330
+ * @throws InvalidSelectorException
331
+ */
332
+ public function first($expression, $type = Query::TYPE_CSS, $wrapNode = true)
333
+ {
334
+ return $this->toDocument()->first($expression, $type, $wrapNode);
335
+ }
336
+
337
+ /**
338
+ * Searches for a node in the owner document using current node as context and returns first element or null.
339
+ *
340
+ * @param string $expression XPath expression or CSS selector
341
+ * @param string $type The type of the expression
342
+ * @param bool $wrapNode Returns Element if true, otherwise DOMElement
343
+ *
344
+ * @return Element|DOMElement|null
345
+ *
346
+ * @throws InvalidSelectorException
347
+ */
348
+ public function firstInDocument($expression, $type = Query::TYPE_CSS, $wrapNode = true)
349
+ {
350
+ $ownerDocument = $this->getDocument();
351
+
352
+ if ($ownerDocument === null) {
353
+ throw new LogicException('Can not search in context without the owner document');
354
+ }
355
+
356
+ return $ownerDocument->first($expression, $type, $wrapNode, $this->node);
357
+ }
358
+
359
+ /**
360
+ * Searches for a node in the DOM tree for a given XPath expression.
361
+ *
362
+ * @param string $expression XPath expression
363
+ * @param bool $wrapNode Returns array of Element if true, otherwise array of DOMElement
364
+ *
365
+ * @return Element[]|DOMElement[]
366
+ *
367
+ * @throws InvalidSelectorException
368
+ */
369
+ public function xpath($expression, $wrapNode = true)
370
+ {
371
+ return $this->find($expression, Query::TYPE_XPATH, $wrapNode);
372
+ }
373
+
374
+ /**
375
+ * Counts nodes for a given XPath expression or CSS selector.
376
+ *
377
+ * @param string $expression XPath expression or CSS selector
378
+ * @param string $type The type of the expression
379
+ *
380
+ * @return int
381
+ *
382
+ * @throws InvalidSelectorException
383
+ */
384
+ public function count($expression, $type = Query::TYPE_CSS)
385
+ {
386
+ return $this->toDocument()->count($expression, $type);
387
+ }
388
+
389
+ /**
390
+ * Dumps the node into a string using HTML formatting (including child nodes).
391
+ *
392
+ * @return string
393
+ */
394
+ public function html()
395
+ {
396
+ return $this->toDocument()->html();
397
+ }
398
+
399
+ /**
400
+ * Dumps the node into a string using HTML formatting (without child nodes).
401
+ *
402
+ * @return string
403
+ */
404
+ public function outerHtml()
405
+ {
406
+ $document = new DOMDocument();
407
+
408
+ $importedNode = $document->importNode($this->node);
409
+
410
+ return $document->saveHTML($importedNode);
411
+ }
412
+
413
+ /**
414
+ * Dumps the node descendants into a string using HTML formatting.
415
+ *
416
+ * @param string $delimiter
417
+ *
418
+ * @return string
419
+ */
420
+ public function innerHtml($delimiter = '')
421
+ {
422
+ $innerHtml = [];
423
+
424
+ foreach ($this->node->childNodes as $childNode) {
425
+ $innerHtml[] = $childNode->ownerDocument->saveHTML($childNode);
426
+ }
427
+
428
+ return implode($delimiter, $innerHtml);
429
+ }
430
+
431
+ /**
432
+ * Dumps the node descendants into a string using XML formatting.
433
+ *
434
+ * @param string $delimiter
435
+ *
436
+ * @return string
437
+ */
438
+ public function innerXml($delimiter = '')
439
+ {
440
+ $innerXml = [];
441
+
442
+ foreach ($this->node->childNodes as $childNode) {
443
+ $innerXml[] = $childNode->ownerDocument->saveXML($childNode);
444
+ }
445
+
446
+ return implode($delimiter, $innerXml);
447
+ }
448
+
449
+ /**
450
+ * Sets inner HTML.
451
+ *
452
+ * @param string $html
453
+ *
454
+ * @return static
455
+ *
456
+ * @throws InvalidArgumentException if passed argument is not a string
457
+ * @throws InvalidSelectorException
458
+ */
459
+ public function setInnerHtml($html)
460
+ {
461
+ if ( ! is_string($html)) {
462
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($html) ? get_class($html) : gettype($html))));
463
+ }
464
+
465
+ $this->removeChildren();
466
+
467
+ if ($html !== '') {
468
+ Errors::disable();
469
+
470
+ $html = "<htmlfragment>$html</htmlfragment>";
471
+
472
+ $document = new Document($html);
473
+
474
+ $fragment = $document->first('htmlfragment')->getNode();
475
+
476
+ foreach ($fragment->childNodes as $node) {
477
+ $newNode = $this->node->ownerDocument->importNode($node, true);
478
+
479
+ $this->node->appendChild($newNode);
480
+ }
481
+
482
+ Errors::restore();
483
+ }
484
+
485
+ return $this;
486
+ }
487
+
488
+ /**
489
+ * Dumps the node into a string using XML formatting.
490
+ *
491
+ * @param int $options Additional options
492
+ *
493
+ * @return string The node XML
494
+ */
495
+ public function xml($options = 0)
496
+ {
497
+ return $this->toDocument()->xml($options);
498
+ }
499
+
500
+ /**
501
+ * Get the text content of this node and its descendants.
502
+ *
503
+ * @return string The node value
504
+ */
505
+ public function text()
506
+ {
507
+ return $this->node->textContent;
508
+ }
509
+
510
+ /**
511
+ * Set the value of this node.
512
+ *
513
+ * @param string $value The new value of the node
514
+ *
515
+ * @return static
516
+ *
517
+ * @throws InvalidArgumentException if parameter 1 is not a string
518
+ */
519
+ public function setValue($value)
520
+ {
521
+ if (is_numeric($value)) {
522
+ $value = (string) $value;
523
+ }
524
+
525
+ if ( ! is_string($value) && $value !== null) {
526
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($value) ? get_class($value) : gettype($value))));
527
+ }
528
+
529
+ $this->node->nodeValue = $value;
530
+
531
+ return $this;
532
+ }
533
+
534
+ /**
535
+ * Returns true if the current node is a DOMElement instance.
536
+ *
537
+ * @return bool
538
+ */
539
+ public function isElementNode()
540
+ {
541
+ return $this->node instanceof DOMElement;
542
+ }
543
+
544
+ /**
545
+ * Returns true if the current node is a a DOMText instance.
546
+ *
547
+ * @return bool
548
+ */
549
+ public function isTextNode()
550
+ {
551
+ return $this->node instanceof DOMText;
552
+ }
553
+
554
+ /**
555
+ * Returns true if the current node is a DOMComment instance.
556
+ *
557
+ * @return bool
558
+ */
559
+ public function isCommentNode()
560
+ {
561
+ return $this->node instanceof DOMComment;
562
+ }
563
+
564
+ /**
565
+ * Returns true if the current node is a DOMCdataSection instance.
566
+ *
567
+ * @return bool
568
+ */
569
+ public function isCdataSectionNode()
570
+ {
571
+ return $this->node instanceof DOMCdataSection;
572
+ }
573
+
574
+ /**
575
+ * Indicates if two nodes are the same node.
576
+ *
577
+ * @param Element|DOMNode $node
578
+ *
579
+ * @return bool
580
+ *
581
+ * @throws InvalidArgumentException if parameter 1 is not an instance of DOMNode
582
+ */
583
+ public function is($node)
584
+ {
585
+ if ($node instanceof Node) {
586
+ $node = $node->getNode();
587
+ }
588
+
589
+ if ( ! $node instanceof DOMNode) {
590
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($node) ? get_class($node) : gettype($node))));
591
+ }
592
+
593
+ return $this->node->isSameNode($node);
594
+ }
595
+
596
+ /**
597
+ * @return Element|Document|null
598
+ */
599
+ public function parent()
600
+ {
601
+ if ($this->node->parentNode === null) {
602
+ return null;
603
+ }
604
+
605
+ if ($this->node->parentNode instanceof DOMDocument) {
606
+ return new Document($this->node->parentNode);
607
+ }
608
+
609
+ return new Element($this->node->parentNode);
610
+ }
611
+
612
+ /**
613
+ * Returns first parent node matches passed selector.
614
+ *
615
+ * @param string $selector
616
+ * @param bool $strict
617
+ *
618
+ * @return Element|null
619
+ *
620
+ * @throws InvalidSelectorException if the selector is invalid
621
+ */
622
+ public function closest($selector, $strict = false)
623
+ {
624
+ $node = $this;
625
+
626
+ while (true) {
627
+ $parent = $node->parent();
628
+
629
+ if ($parent === null || $parent instanceof Document) {
630
+ return null;
631
+ }
632
+
633
+ if ($parent->matches($selector, $strict)) {
634
+ return $parent;
635
+ }
636
+
637
+ $node = $parent;
638
+ }
639
+
640
+ return null;
641
+ }
642
+
643
+ /**
644
+ * @param string|null $selector
645
+ * @param string|null $nodeType
646
+ *
647
+ * @return Element|null
648
+ *
649
+ * @throws InvalidArgumentException if parameter 2 is not a string
650
+ * @throws RuntimeException if the node type is invalid
651
+ * @throws LogicException if the selector used with non DOMElement node type
652
+ * @throws InvalidSelectorException if the selector is invalid
653
+ */
654
+ public function previousSibling($selector = null, $nodeType = null)
655
+ {
656
+ if ($this->node->previousSibling === null) {
657
+ return null;
658
+ }
659
+
660
+ if ($selector === null && $nodeType === null) {
661
+ return new Element($this->node->previousSibling);
662
+ }
663
+
664
+ if ($selector !== null && $nodeType === null) {
665
+ $nodeType = 'DOMElement';
666
+ }
667
+
668
+ if ( ! is_string($nodeType)) {
669
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, gettype($nodeType)));
670
+ }
671
+
672
+ $allowedTypes = ['DOMElement', 'DOMText', 'DOMComment', 'DOMCdataSection'];
673
+
674
+ if ( ! in_array($nodeType, $allowedTypes, true)) {
675
+ throw new RuntimeException(sprintf('Unknown node type "%s". Allowed types: %s', $nodeType, implode(', ', $allowedTypes)));
676
+ }
677
+
678
+ if ($selector !== null && $nodeType !== 'DOMElement') {
679
+ throw new LogicException(sprintf('Selector can be used only with DOMElement node type, %s given', $nodeType));
680
+ }
681
+
682
+ $node = $this->node->previousSibling;
683
+
684
+ while ($node !== null) {
685
+ if (get_class($node) !== $nodeType) {
686
+ $node = $node->previousSibling;
687
+
688
+ continue;
689
+ }
690
+
691
+ $element = new Element($node);
692
+
693
+ if ($selector === null || $element->matches($selector)) {
694
+ return $element;
695
+ }
696
+
697
+ $node = $node->previousSibling;
698
+ }
699
+
700
+ return null;
701
+ }
702
+
703
+ /**
704
+ * @param string|null $selector
705
+ * @param string|null $nodeType
706
+ *
707
+ * @return Element[]
708
+ *
709
+ * @throws InvalidArgumentException if parameter 2 is not a string
710
+ * @throws RuntimeException if the node type is invalid
711
+ * @throws LogicException if the selector used with non DOMElement node type
712
+ * @throws InvalidSelectorException if the selector is invalid
713
+ */
714
+ public function previousSiblings($selector = null, $nodeType = null)
715
+ {
716
+ if ($this->node->previousSibling === null) {
717
+ return [];
718
+ }
719
+
720
+ if ($selector !== null && $nodeType === null) {
721
+ $nodeType = 'DOMElement';
722
+ }
723
+
724
+ if ($nodeType !== null) {
725
+ if ( ! is_string($nodeType)) {
726
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, gettype($nodeType)));
727
+ }
728
+
729
+ $allowedTypes = ['DOMElement', 'DOMText', 'DOMComment', 'DOMCdataSection'];
730
+
731
+ if ( ! in_array($nodeType, $allowedTypes, true)) {
732
+ throw new RuntimeException(sprintf('Unknown node type "%s". Allowed types: %s', $nodeType, implode(', ', $allowedTypes)));
733
+ }
734
+ }
735
+
736
+ if ($selector !== null && $nodeType !== 'DOMElement') {
737
+ throw new LogicException(sprintf('Selector can be used only with DOMElement node type, %s given', $nodeType));
738
+ }
739
+
740
+ $result = [];
741
+
742
+ $node = $this->node->previousSibling;
743
+
744
+ while ($node !== null) {
745
+ $element = new Element($node);
746
+
747
+ if ($nodeType === null) {
748
+ $result[] = $element;
749
+
750
+ $node = $node->previousSibling;
751
+
752
+ continue;
753
+ }
754
+
755
+ if (get_class($node) !== $nodeType) {
756
+ $node = $node->previousSibling;
757
+
758
+ continue;
759
+ }
760
+
761
+ if ($selector === null) {
762
+ $result[] = $element;
763
+
764
+ $node = $node->previousSibling;
765
+
766
+ continue;
767
+ }
768
+
769
+ if ($element->matches($selector)) {
770
+ $result[] = $element;
771
+ }
772
+
773
+ $node = $node->previousSibling;
774
+ }
775
+
776
+ return array_reverse($result);
777
+ }
778
+
779
+ /**
780
+ * @param string|null $selector
781
+ * @param string|null $nodeType
782
+ *
783
+ * @return Element|null
784
+ *
785
+ * @throws InvalidArgumentException if parameter 2 is not a string
786
+ * @throws RuntimeException if the node type is invalid
787
+ * @throws LogicException if the selector used with non DOMElement node type
788
+ * @throws InvalidSelectorException if the selector is invalid
789
+ */
790
+ public function nextSibling($selector = null, $nodeType = null)
791
+ {
792
+ if ($this->node->nextSibling === null) {
793
+ return null;
794
+ }
795
+
796
+ if ($selector === null && $nodeType === null) {
797
+ return new Element($this->node->nextSibling);
798
+ }
799
+
800
+ if ($selector !== null && $nodeType === null) {
801
+ $nodeType = 'DOMElement';
802
+ }
803
+
804
+ if ( ! is_string($nodeType)) {
805
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, gettype($nodeType)));
806
+ }
807
+
808
+ $allowedTypes = ['DOMElement', 'DOMText', 'DOMComment', 'DOMCdataSection'];
809
+
810
+ if ( ! in_array($nodeType, $allowedTypes, true)) {
811
+ throw new RuntimeException(sprintf('Unknown node type "%s". Allowed types: %s', $nodeType, implode(', ', $allowedTypes)));
812
+ }
813
+
814
+ if ($selector !== null && $nodeType !== 'DOMElement') {
815
+ throw new LogicException(sprintf('Selector can be used only with DOMElement node type, %s given', $nodeType));
816
+ }
817
+
818
+ $node = $this->node->nextSibling;
819
+
820
+ while ($node !== null) {
821
+ if (get_class($node) !== $nodeType) {
822
+ $node = $node->nextSibling;
823
+
824
+ continue;
825
+ }
826
+
827
+ $element = new Element($node);
828
+
829
+ if ($selector === null || $element->matches($selector)) {
830
+ return $element;
831
+ }
832
+
833
+ $node = $node->nextSibling;
834
+ }
835
+
836
+ return null;
837
+ }
838
+
839
+ /**
840
+ * @param string|null $selector
841
+ * @param string $nodeType
842
+ *
843
+ * @return Element[]
844
+ *
845
+ * @throws InvalidArgumentException if parameter 2 is not a string
846
+ * @throws RuntimeException if the node type is invalid
847
+ * @throws LogicException if the selector used with non DOMElement node type
848
+ * @throws InvalidSelectorException if the selector is invalid
849
+ */
850
+ public function nextSiblings($selector = null, $nodeType = null)
851
+ {
852
+ if ($this->node->nextSibling === null) {
853
+ return [];
854
+ }
855
+
856
+ if ($selector !== null && $nodeType === null) {
857
+ $nodeType = 'DOMElement';
858
+ }
859
+
860
+ if ($nodeType !== null) {
861
+ if ( ! is_string($nodeType)) {
862
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, gettype($nodeType)));
863
+ }
864
+
865
+ $allowedTypes = ['DOMElement', 'DOMText', 'DOMComment', 'DOMCdataSection'];
866
+
867
+ if ( ! in_array($nodeType, $allowedTypes, true)) {
868
+ throw new RuntimeException(sprintf('Unknown node type "%s". Allowed types: %s', $nodeType, implode(', ', $allowedTypes)));
869
+ }
870
+ }
871
+
872
+ if ($selector !== null && $nodeType !== 'DOMElement') {
873
+ throw new LogicException(sprintf('Selector can be used only with DOMElement node type, %s given', $nodeType));
874
+ }
875
+
876
+ $result = [];
877
+
878
+ $node = $this->node->nextSibling;
879
+
880
+ while ($node !== null) {
881
+ $element = new Element($node);
882
+
883
+ if ($nodeType === null) {
884
+ $result[] = $element;
885
+
886
+ $node = $node->nextSibling;
887
+
888
+ continue;
889
+ }
890
+
891
+ if (get_class($node) !== $nodeType) {
892
+ $node = $node->nextSibling;
893
+
894
+ continue;
895
+ }
896
+
897
+ if ($selector === null) {
898
+ $result[] = $element;
899
+
900
+ $node = $node->nextSibling;
901
+
902
+ continue;
903
+ }
904
+
905
+ if ($element->matches($selector)) {
906
+ $result[] = $element;
907
+ }
908
+
909
+ $node = $node->nextSibling;
910
+ }
911
+
912
+ return $result;
913
+ }
914
+
915
+ /**
916
+ * @param int $index
917
+ *
918
+ * @return Element|null
919
+ */
920
+ public function child($index)
921
+ {
922
+ $child = $this->node->childNodes->item($index);
923
+
924
+ return $child === null ? null : new Element($child);
925
+ }
926
+
927
+ /**
928
+ * @return Element|null
929
+ */
930
+ public function firstChild()
931
+ {
932
+ if ($this->node->firstChild === null) {
933
+ return null;
934
+ }
935
+
936
+ return new Element($this->node->firstChild);
937
+ }
938
+
939
+ /**
940
+ * @return Element|null
941
+ */
942
+ public function lastChild()
943
+ {
944
+ if ($this->node->lastChild === null) {
945
+ return null;
946
+ }
947
+
948
+ return new Element($this->node->lastChild);
949
+ }
950
+
951
+ /**
952
+ * @return bool
953
+ */
954
+ public function hasChildren()
955
+ {
956
+ return $this->node->hasChildNodes();
957
+ }
958
+
959
+ /**
960
+ * @return Element[]
961
+ */
962
+ public function children()
963
+ {
964
+ $children = [];
965
+
966
+ foreach ($this->node->childNodes as $node) {
967
+ $children[] = new Element($node);
968
+ }
969
+
970
+ return $children;
971
+ }
972
+
973
+ /**
974
+ * Removes child from list of children.
975
+ *
976
+ * @param Node|DOMNode $childNode
977
+ *
978
+ * @return Element the node that has been removed
979
+ */
980
+ public function removeChild($childNode)
981
+ {
982
+ if ($childNode instanceof Node) {
983
+ $childNode = $childNode->getNode();
984
+ }
985
+
986
+ if ( ! $childNode instanceof DOMNode) {
987
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($childNode) ? get_class($childNode) : gettype($childNode))));
988
+ }
989
+
990
+ $removedNode = $this->node->removeChild($childNode);
991
+
992
+ return new Element($removedNode);
993
+ }
994
+
995
+ /**
996
+ * Removes all child nodes.
997
+ *
998
+ * @return Element[] the nodes that has been removed
999
+ */
1000
+ public function removeChildren()
1001
+ {
1002
+ // we need to collect child nodes to array
1003
+ // because removing nodes from the DOMNodeList on iterating is not working
1004
+ $childNodes = [];
1005
+
1006
+ foreach ($this->node->childNodes as $childNode) {
1007
+ $childNodes[] = $childNode;
1008
+ }
1009
+
1010
+ $removedNodes = [];
1011
+
1012
+ foreach ($childNodes as $childNode) {
1013
+ $removedNode = $this->node->removeChild($childNode);
1014
+
1015
+ $removedNodes[] = new Element($removedNode);
1016
+ }
1017
+
1018
+ return $removedNodes;
1019
+ }
1020
+
1021
+ /**
1022
+ * Removes current node from the parent.
1023
+ *
1024
+ * @return Element the node that has been removed
1025
+ *
1026
+ * @throws LogicException if the current node has no parent node
1027
+ */
1028
+ public function remove()
1029
+ {
1030
+ if ($this->node->parentNode === null) {
1031
+ throw new LogicException('Can not remove an element without the parent node');
1032
+ }
1033
+
1034
+ $removedNode = $this->node->parentNode->removeChild($this->node);
1035
+
1036
+ return new Element($removedNode);
1037
+ }
1038
+
1039
+ /**
1040
+ * Replaces a child.
1041
+ *
1042
+ * @param Node|DOMNode $newNode The new node
1043
+ * @param bool $clone Clone the node if true, otherwise move it
1044
+ *
1045
+ * @return Element The node that has been replaced
1046
+ *
1047
+ * @throws LogicException if the current node has no parent node
1048
+ */
1049
+ public function replace($newNode, $clone = true)
1050
+ {
1051
+ if ($this->node->parentNode === null) {
1052
+ throw new LogicException('Can not replace an element without the parent node');
1053
+ }
1054
+
1055
+ if ($newNode instanceof Node) {
1056
+ $newNode = $newNode->getNode();
1057
+ }
1058
+
1059
+ if ( ! $newNode instanceof DOMNode) {
1060
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($newNode) ? get_class($newNode) : gettype($newNode))));
1061
+ }
1062
+
1063
+ if ($clone) {
1064
+ $newNode = $newNode->cloneNode(true);
1065
+ }
1066
+
1067
+ if ($newNode->ownerDocument === null || ! $this->getDocument()->is($newNode->ownerDocument)) {
1068
+ $newNode = $this->node->ownerDocument->importNode($newNode, true);
1069
+ }
1070
+
1071
+ $node = $this->node->parentNode->replaceChild($newNode, $this->node);
1072
+
1073
+ return new Element($node);
1074
+ }
1075
+
1076
+ /**
1077
+ * Get line number for a node.
1078
+ *
1079
+ * @return int
1080
+ */
1081
+ public function getLineNo()
1082
+ {
1083
+ return $this->node->getLineNo();
1084
+ }
1085
+
1086
+ /**
1087
+ * Clones a node.
1088
+ *
1089
+ * @param bool $deep Indicates whether to copy all descendant nodes
1090
+ *
1091
+ * @return Element The cloned node
1092
+ */
1093
+ public function cloneNode($deep = true)
1094
+ {
1095
+ return new Element($this->node->cloneNode($deep));
1096
+ }
1097
+
1098
+ /**
1099
+ * Sets current node instance.
1100
+ *
1101
+ * @param DOMElement|DOMText|DOMComment|DOMCdataSection|DOMDocumentFragment $node
1102
+ *
1103
+ * @return static
1104
+ */
1105
+ protected function setNode($node)
1106
+ {
1107
+ $allowedClasses = ['DOMElement', 'DOMText', 'DOMComment', 'DOMCdataSection', 'DOMDocumentFragment'];
1108
+
1109
+ if ( ! is_object($node) || ! in_array(get_class($node), $allowedClasses, true)) {
1110
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of DOMElement, DOMText, DOMComment, DOMCdataSection or DOMDocumentFragment, %s given', __METHOD__, (is_object($node) ? get_class($node) : gettype($node))));
1111
+ }
1112
+
1113
+ $this->node = $node;
1114
+
1115
+ return $this;
1116
+ }
1117
+
1118
+ /**
1119
+ * Returns current node instance.
1120
+ *
1121
+ * @return DOMElement|DOMText|DOMComment|DOMCdataSection|DOMDocumentFragment
1122
+ */
1123
+ public function getNode()
1124
+ {
1125
+ return $this->node;
1126
+ }
1127
+
1128
+ /**
1129
+ * Returns the document associated with this node.
1130
+ *
1131
+ * @return Document|null
1132
+ */
1133
+ public function getDocument()
1134
+ {
1135
+ if ($this->node->ownerDocument === null) {
1136
+ return null;
1137
+ }
1138
+
1139
+ return new Document($this->node->ownerDocument);
1140
+ }
1141
+
1142
+ /**
1143
+ * Get the DOM document with the current element.
1144
+ *
1145
+ * @param string $encoding The document encoding
1146
+ *
1147
+ * @return Document
1148
+ */
1149
+ public function toDocument($encoding = 'UTF-8')
1150
+ {
1151
+ $document = new Document(null, false, $encoding);
1152
+
1153
+ $document->appendChild($this->node);
1154
+
1155
+ return $document;
1156
+ }
1157
+
1158
+ /**
1159
+ * Convert the element to its string representation.
1160
+ *
1161
+ * @return string
1162
+ */
1163
+ public function __toString()
1164
+ {
1165
+ return $this->html();
1166
+ }
1167
+
1168
+ /**
1169
+ * Searches for a node in the DOM tree for a given XPath expression or CSS selector.
1170
+ *
1171
+ * @param string $expression XPath expression or CSS selector
1172
+ * @param string $type The type of the expression
1173
+ * @param bool $wrapNode Returns array of Element if true, otherwise array of DOMElement
1174
+ *
1175
+ * @return Element[]|DOMElement[]
1176
+ *
1177
+ * @throws InvalidSelectorException
1178
+ *
1179
+ * @deprecated Not longer recommended, use Element::find() instead.
1180
+ */
1181
+ public function __invoke($expression, $type = Query::TYPE_CSS, $wrapNode = true)
1182
+ {
1183
+ return $this->find($expression, $type, $wrapNode);
1184
+ }
1185
+ }
vendor/imangazaliev/didom/src/DiDom/Query.php ADDED
@@ -0,0 +1,567 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use DiDom\Exceptions\InvalidSelectorException;
6
+ use InvalidArgumentException;
7
+ use RuntimeException;
8
+
9
+ class Query
10
+ {
11
+ /**
12
+ * Types of expression.
13
+ *
14
+ * @const string
15
+ */
16
+ const TYPE_XPATH = 'XPATH';
17
+ const TYPE_CSS = 'CSS';
18
+
19
+ /**
20
+ * @var array
21
+ */
22
+ protected static $compiled = [];
23
+
24
+ /**
25
+ * Converts a CSS selector into an XPath expression.
26
+ *
27
+ * @param string $expression XPath expression or CSS selector
28
+ * @param string $type The type of the expression
29
+ *
30
+ * @return string XPath expression
31
+ *
32
+ * @throws InvalidSelectorException if the expression is empty
33
+ */
34
+ public static function compile($expression, $type = self::TYPE_CSS)
35
+ {
36
+ if ( ! is_string($expression)) {
37
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, gettype($expression)));
38
+ }
39
+
40
+ if ( ! is_string($type)) {
41
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, gettype($type)));
42
+ }
43
+
44
+ if (strcasecmp($type, self::TYPE_XPATH) !== 0 && strcasecmp($type, self::TYPE_CSS) !== 0) {
45
+ throw new RuntimeException(sprintf('Unknown expression type "%s"', $type));
46
+ }
47
+
48
+ $expression = trim($expression);
49
+
50
+ if ($expression === '') {
51
+ throw new InvalidSelectorException('The expression must not be empty');
52
+ }
53
+
54
+ if (strcasecmp($type, self::TYPE_XPATH) === 0) {
55
+ return $expression;
56
+ }
57
+
58
+ if ( ! array_key_exists($expression, static::$compiled)) {
59
+ static::$compiled[$expression] = static::cssToXpath($expression);
60
+ }
61
+
62
+ return static::$compiled[$expression];
63
+ }
64
+
65
+ /**
66
+ * Converts a CSS selector into an XPath expression.
67
+ *
68
+ * @param string $selector A CSS selector
69
+ * @param string $prefix Specifies the nesting of nodes
70
+ *
71
+ * @return string XPath expression
72
+ *
73
+ * @throws InvalidSelectorException
74
+ */
75
+ public static function cssToXpath($selector, $prefix = '//')
76
+ {
77
+ $paths = [];
78
+
79
+ while ($selector !== '') {
80
+ list($xpath, $selector) = static::parseAndConvertSelector($selector, $prefix);
81
+
82
+ if (substr($selector, 0, 1) === ',') {
83
+ $selector = trim($selector, ', ');
84
+ }
85
+
86
+ $paths[] = $xpath;
87
+ }
88
+
89
+ return implode('|', $paths);
90
+ }
91
+
92
+ /**
93
+ * @param string $selector
94
+ * @param string $prefix
95
+ *
96
+ * @return array
97
+ *
98
+ * @throws InvalidSelectorException
99
+ */
100
+ protected static function parseAndConvertSelector($selector, $prefix = '//')
101
+ {
102
+ if (substr($selector, 0, 1) === '>') {
103
+ $prefix = '/';
104
+
105
+ $selector = ltrim($selector, '> ');
106
+ }
107
+
108
+ $segments = self::getSegments($selector);
109
+ $xpath = '';
110
+
111
+ while (count($segments) > 0) {
112
+ $xpath .= self::buildXpath($segments, $prefix);
113
+
114
+ $selector = trim(substr($selector, strlen($segments['selector'])));
115
+ $prefix = isset($segments['rel']) ? '/' : '//';
116
+
117
+ if ($selector === '' || substr($selector, 0, 2) === '::' || substr($selector, 0, 1) === ',') {
118
+ break;
119
+ }
120
+
121
+ $segments = self::getSegments($selector);
122
+ }
123
+
124
+ // if selector has property
125
+ if (substr($selector, 0, 2) === '::') {
126
+ $property = self::parseProperty($selector);
127
+ $propertyXpath = self::convertProperty($property['name'], $property['args']);
128
+
129
+ $selector = substr($selector, strlen($property['property']));
130
+ $selector = trim($selector);
131
+
132
+ $xpath .= '/' . $propertyXpath;
133
+ }
134
+
135
+ return [$xpath, $selector];
136
+ }
137
+
138
+ /**
139
+ * @param string $selector
140
+ *
141
+ * @return array
142
+ *
143
+ * @throws InvalidSelectorException
144
+ */
145
+ protected static function parseProperty($selector)
146
+ {
147
+ $name = '(?P<name>[\w\-]+)';
148
+ $args = '(?:\((?P<args>[^\)]+)?\))?';
149
+
150
+ $regexp = '/^::' . $name . $args . '/is';
151
+
152
+ if (preg_match($regexp, $selector, $matches) !== 1) {
153
+ throw new InvalidSelectorException(sprintf('Invalid property "%s"', $selector));
154
+ }
155
+
156
+ $result = [];
157
+
158
+ $result['property'] = $matches[0];
159
+ $result['name'] = $matches['name'];
160
+ $result['args'] = isset($matches['args']) ? explode(',', $matches['args']) : [];
161
+
162
+ $result['args'] = array_map('trim', $result['args']);
163
+
164
+ return $result;
165
+ }
166
+
167
+ /**
168
+ * @param string $name
169
+ * @param array $parameters
170
+ *
171
+ * @return string
172
+ *
173
+ * @throws InvalidSelectorException if the specified property is unknown
174
+ */
175
+ protected static function convertProperty($name, array $parameters = [])
176
+ {
177
+ if ($name === 'text') {
178
+ return 'text()';
179
+ }
180
+
181
+ if ($name === 'attr') {
182
+ if (count($parameters) === 0) {
183
+ return '@*';
184
+ }
185
+
186
+ $attributes = [];
187
+
188
+ foreach ($parameters as $attribute) {
189
+ $attributes[] = sprintf('name() = "%s"', $attribute);
190
+ }
191
+
192
+ return sprintf('@*[%s]', implode(' or ', $attributes));
193
+ }
194
+
195
+ throw new InvalidSelectorException(sprintf('Unknown property "%s"', $name));
196
+ }
197
+
198
+ /**
199
+ * Converts a CSS pseudo-class into an XPath expression.
200
+ *
201
+ * @param string $pseudo Pseudo-class
202
+ * @param string $tagName
203
+ * @param array $parameters
204
+ *
205
+ * @return string
206
+ *
207
+ * @throws InvalidSelectorException if the specified pseudo-class is unknown
208
+ */
209
+ protected static function convertPseudo($pseudo, &$tagName, array $parameters = [])
210
+ {
211
+ switch ($pseudo) {
212
+ case 'first-child':
213
+ return 'position() = 1';
214
+ case 'last-child':
215
+ return 'position() = last()';
216
+ case 'nth-child':
217
+ $xpath = sprintf('(name()="%s") and (%s)', $tagName, self::convertNthExpression($parameters[0]));
218
+ $tagName = '*';
219
+
220
+ return $xpath;
221
+ case 'contains':
222
+ $string = trim($parameters[0], '\'"');
223
+
224
+ if (count($parameters) === 1) {
225
+ return self::convertContains($string);
226
+ }
227
+
228
+ if ($parameters[1] !== 'true' && $parameters[1] !== 'false') {
229
+ throw new InvalidSelectorException(sprintf('Parameter 2 of "contains" pseudo-class must be equal true or false, "%s" given', $parameters[1]));
230
+ }
231
+
232
+ $caseSensitive = $parameters[1] === 'true';
233
+
234
+ if (count($parameters) === 2) {
235
+ return self::convertContains($string, $caseSensitive);
236
+ }
237
+
238
+ if ($parameters[2] !== 'true' && $parameters[2] !== 'false') {
239
+ throw new InvalidSelectorException(sprintf('Parameter 3 of "contains" pseudo-class must be equal true or false, "%s" given', $parameters[2]));
240
+ }
241
+
242
+ $fullMatch = $parameters[2] === 'true';
243
+
244
+ return self::convertContains($string, $caseSensitive, $fullMatch);
245
+ case 'has':
246
+ return self::cssToXpath($parameters[0], './/');
247
+ case 'not':
248
+ return sprintf('not(self::%s)', self::cssToXpath($parameters[0], ''));
249
+
250
+ case 'nth-of-type':
251
+ return self::convertNthExpression($parameters[0]);
252
+ case 'empty':
253
+ return 'count(descendant::*) = 0';
254
+ case 'not-empty':
255
+ return 'count(descendant::*) > 0';
256
+ }
257
+
258
+ throw new InvalidSelectorException(sprintf('Unknown pseudo-class "%s"', $pseudo));
259
+ }
260
+
261
+ /**
262
+ * @param array $segments
263
+ * @param string $prefix Specifies the nesting of nodes
264
+ *
265
+ * @return string XPath expression
266
+ *
267
+ * @throws InvalidArgumentException if you neither specify tag name nor attributes
268
+ */
269
+ public static function buildXpath(array $segments, $prefix = '//')
270
+ {
271
+ $tagName = isset($segments['tag']) ? $segments['tag'] : '*';
272
+
273
+ $attributes = [];
274
+
275
+ // if the id attribute specified
276
+ if (isset($segments['id'])) {
277
+ $attributes[] = sprintf('@id="%s"', $segments['id']);
278
+ }
279
+
280
+ // if the class attribute specified
281
+ if (isset($segments['classes'])) {
282
+ foreach ($segments['classes'] as $class) {
283
+ $attributes[] = sprintf('contains(concat(" ", normalize-space(@class), " "), " %s ")', $class);
284
+ }
285
+ }
286
+
287
+ // if the attributes specified
288
+ if (isset($segments['attributes'])) {
289
+ foreach ($segments['attributes'] as $name => $value) {
290
+ $attributes[] = self::convertAttribute($name, $value);
291
+ }
292
+ }
293
+
294
+ // if the pseudo class specified
295
+ if (array_key_exists('pseudo', $segments)) {
296
+ foreach ($segments['pseudo'] as $pseudo) {
297
+ $expression = $pseudo['expression'] !== null ? $pseudo['expression'] : '';
298
+
299
+ $parameters = explode(',', $expression);
300
+ $parameters = array_map('trim', $parameters);
301
+
302
+ $attributes[] = self::convertPseudo($pseudo['type'], $tagName, $parameters);
303
+ }
304
+ }
305
+
306
+ if (count($attributes) === 0 && ! isset($segments['tag'])) {
307
+ throw new InvalidArgumentException('The array of segments must contain the name of the tag or at least one attribute');
308
+ }
309
+
310
+ $xpath = $prefix . $tagName;
311
+
312
+ if ($count = count($attributes)) {
313
+ $xpath .= ($count > 1) ? sprintf('[(%s)]', implode(') and (', $attributes)) : sprintf('[%s]', $attributes[0]);
314
+ }
315
+
316
+ return $xpath;
317
+ }
318
+
319
+ /**
320
+ * @param string $name The name of an attribute
321
+ * @param string $value The value of an attribute
322
+ *
323
+ * @return string
324
+ */
325
+ protected static function convertAttribute($name, $value)
326
+ {
327
+ $isSimpleSelector = ! in_array(substr($name, 0, 1), ['^', '!'], true);
328
+ $isSimpleSelector = $isSimpleSelector && ( ! in_array(substr($name, -1), ['^', '$', '*', '!', '~'], true));
329
+
330
+ if ($isSimpleSelector) {
331
+ // if specified only the attribute name
332
+ $xpath = $value === null ? '@' . $name : sprintf('@%s="%s"', $name, $value);
333
+
334
+ return $xpath;
335
+ }
336
+
337
+ // if the attribute name starts with ^
338
+ // example: *[^data-]
339
+ if (substr($name, 0, 1) === '^') {
340
+ $xpath = sprintf('@*[starts-with(name(), "%s")]', substr($name, 1));
341
+
342
+ return $value === null ? $xpath : sprintf('%s="%s"', $xpath, $value);
343
+ }
344
+
345
+ // if the attribute name starts with !
346
+ // example: input[!disabled]
347
+ if (substr($name, 0, 1) === '!') {
348
+ $xpath = sprintf('not(@%s)', substr($name, 1));
349
+
350
+ return $xpath;
351
+ }
352
+
353
+ $symbol = substr($name, -1);
354
+ $name = substr($name, 0, -1);
355
+
356
+ switch ($symbol) {
357
+ case '^':
358
+ $xpath = sprintf('starts-with(@%s, "%s")', $name, $value);
359
+
360
+ break;
361
+ case '$':
362
+ $xpath = sprintf('substring(@%s, string-length(@%s) - string-length("%s") + 1) = "%s"', $name, $name, $value, $value);
363
+
364
+ break;
365
+ case '*':
366
+ $xpath = sprintf('contains(@%s, "%s")', $name, $value);
367
+
368
+ break;
369
+ case '!':
370
+ $xpath = sprintf('not(@%s="%s")', $name, $value);
371
+
372
+ break;
373
+ case '~':
374
+ $xpath = sprintf('contains(concat(" ", normalize-space(@%s), " "), " %s ")', $name, $value);
375
+
376
+ break;
377
+ }
378
+
379
+ return $xpath;
380
+ }
381
+
382
+ /**
383
+ * Converts nth-expression into an XPath expression.
384
+ *
385
+ * @param string $expression nth-expression
386
+ *
387
+ * @return string
388
+ *
389
+ * @throws InvalidSelectorException if the given nth-child expression is empty or invalid
390
+ */
391
+ protected static function convertNthExpression($expression)
392
+ {
393
+ if ($expression === '') {
394
+ throw new InvalidSelectorException('nth-child (or nth-last-child) expression must not be empty');
395
+ }
396
+
397
+ if ($expression === 'odd') {
398
+ return 'position() mod 2 = 1 and position() >= 1';
399
+ }
400
+
401
+ if ($expression === 'even') {
402
+ return 'position() mod 2 = 0 and position() >= 0';
403
+ }
404
+
405
+ if (is_numeric($expression)) {
406
+ return sprintf('position() = %d', $expression);
407
+ }
408
+
409
+ if (preg_match("/^(?P<mul>[0-9]?n)(?:(?P<sign>\+|\-)(?P<pos>[0-9]+))?$/is", $expression, $segments)) {
410
+ if (isset($segments['mul'])) {
411
+ $multiplier = $segments['mul'] === 'n' ? 1 : trim($segments['mul'], 'n');
412
+ $sign = (isset($segments['sign']) && $segments['sign'] === '+') ? '-' : '+';
413
+ $position = isset($segments['pos']) ? $segments['pos'] : 0;
414
+
415
+ return sprintf('(position() %s %d) mod %d = 0 and position() >= %d', $sign, $position, $multiplier, $position);
416
+ }
417
+ }
418
+
419
+ throw new InvalidSelectorException(sprintf('Invalid nth-child expression "%s"', $expression));
420
+ }
421
+
422
+ /**
423
+ * @param string $string
424
+ * @param bool $caseSensitive
425
+ * @param bool $fullMatch
426
+ *
427
+ * @return string
428
+ */
429
+ protected static function convertContains($string, $caseSensitive = true, $fullMatch = false)
430
+ {
431
+ if ($caseSensitive && $fullMatch) {
432
+ return sprintf('text() = "%s"', $string);
433
+ }
434
+
435
+ if ($caseSensitive && ! $fullMatch) {
436
+ return sprintf('contains(text(), "%s")', $string);
437
+ }
438
+
439
+ $strToLowerFunction = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
440
+
441
+ if ( ! $caseSensitive && $fullMatch) {
442
+ return sprintf("php:functionString(\"{$strToLowerFunction}\", .) = php:functionString(\"{$strToLowerFunction}\", \"%s\")", $string);
443
+ }
444
+
445
+ // if ! $caseSensitive and ! $fullMatch
446
+ return sprintf("contains(php:functionString(\"{$strToLowerFunction}\", .), php:functionString(\"{$strToLowerFunction}\", \"%s\"))", $string);
447
+ }
448
+
449
+ /**
450
+ * Splits the CSS selector into parts (tag name, ID, classes, attributes, pseudo-class).
451
+ *
452
+ * @param string $selector CSS selector
453
+ *
454
+ * @return array
455
+ *
456
+ * @throws InvalidSelectorException if the selector is empty or not valid
457
+ */
458
+ public static function getSegments($selector)
459
+ {
460
+ $selector = trim($selector);
461
+
462
+ if ($selector === '') {
463
+ throw new InvalidSelectorException('The selector must not be empty.');
464
+ }
465
+
466
+ $pregMatchResult = preg_match(self::getSelectorRegex(), $selector, $segments);
467
+
468
+ if ($pregMatchResult === false || $pregMatchResult === 0 || $segments[0] === '') {
469
+ throw new InvalidSelectorException(sprintf('Invalid selector "%s".', $selector));
470
+ }
471
+
472
+ $result = ['selector' => $segments[0]];
473
+
474
+ if (isset($segments['tag']) && $segments['tag'] !== '') {
475
+ $result['tag'] = $segments['tag'];
476
+ }
477
+
478
+ // if the id attribute specified
479
+ if (isset($segments['id']) && $segments['id'] !== '') {
480
+ $result['id'] = $segments['id'];
481
+ }
482
+
483
+ // if the attributes specified
484
+ if (isset($segments['attrs'])) {
485
+ $attributes = trim($segments['attrs'], '[]');
486
+ $attributes = explode('][', $attributes);
487
+
488
+ foreach ($attributes as $attribute) {
489
+ if ($attribute !== '') {
490
+ list($name, $value) = array_pad(explode('=', $attribute, 2), 2, null);
491
+
492
+ if ($name === '') {
493
+ throw new InvalidSelectorException(sprintf('Invalid selector "%s": attribute name must not be empty', $selector));
494
+ }
495
+
496
+ // equal null if specified only the attribute name
497
+ $result['attributes'][$name] = is_string($value) ? trim($value, '\'"') : null;
498
+ }
499
+ }
500
+ }
501
+
502
+ // if the class attribute specified
503
+ if (isset($segments['classes'])) {
504
+ $classes = trim($segments['classes'], '.');
505
+ $classes = explode('.', $classes);
506
+
507
+ foreach ($classes as $class) {
508
+ if ($class !== '') {
509
+ $result['classes'][] = $class;
510
+ }
511
+ }
512
+ }
513
+
514
+ // if the pseudo class specified
515
+ if (isset($segments['pseudo']) && $segments['pseudo'] !== '') {
516
+ preg_match_all('/:(?P<type>[\w\-]+)(?:\((?P<expr>[^\)]+)\))?/', $segments['pseudo'], $pseudoClasses);
517
+
518
+ $result['pseudo'] = [];
519
+
520
+ foreach ($pseudoClasses['type'] as $index => $pseudoType) {
521
+ $result['pseudo'][] = [
522
+ 'type' => $pseudoType,
523
+ 'expression' => $pseudoClasses['expr'][$index] !== '' ? $pseudoClasses['expr'][$index] : null,
524
+ ];
525
+ }
526
+ }
527
+
528
+ // if it is a direct descendant
529
+ if (isset($segments['rel'])) {
530
+ $result['rel'] = $segments['rel'];
531
+ }
532
+
533
+ return $result;
534
+ }
535
+
536
+ private static function getSelectorRegex()
537
+ {
538
+ $tag = '(?P<tag>[\*|\w|\-]+)?';
539
+ $id = '(?:#(?P<id>[\w|\-]+))?';
540
+ $classes = '(?P<classes>\.[\w|\-|\.]+)*';
541
+ $attrs = '(?P<attrs>(?:\[.+?\])*)?';
542
+ $pseudoType = '[\w\-]+';
543
+ $pseudoExpr = '(?:\([^\)]+\))?';
544
+ $pseudo = '(?P<pseudo>(?::' . $pseudoType . $pseudoExpr . ')+)?';
545
+ $rel = '\s*(?P<rel>>)?';
546
+
547
+ return '/' . $tag . $id . $classes . $attrs . $pseudo . $rel . '/is';
548
+ }
549
+
550
+ /**
551
+ * @return array
552
+ */
553
+ public static function getCompiled()
554
+ {
555
+ return static::$compiled;
556
+ }
557
+
558
+ /**
559
+ * @param array $compiled
560
+ *
561
+ * @throws InvalidArgumentException if the attributes is not an array
562
+ */
563
+ public static function setCompiled(array $compiled)
564
+ {
565
+ static::$compiled = $compiled;
566
+ }
567
+ }
vendor/imangazaliev/didom/src/DiDom/StyleAttribute.php ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use InvalidArgumentException;
6
+
7
+ class StyleAttribute
8
+ {
9
+ /**
10
+ * The DOM element instance.
11
+ *
12
+ * @var Element
13
+ */
14
+ protected $element;
15
+
16
+ /**
17
+ * @var string
18
+ */
19
+ protected $styleString = '';
20
+
21
+ /**
22
+ * @var array
23
+ */
24
+ protected $properties = [];
25
+
26
+ /**
27
+ * @param Element $element
28
+ *
29
+ * @throws InvalidArgumentException if parameter 1 is not an element node
30
+ */
31
+ public function __construct(Element $element)
32
+ {
33
+ if ( ! $element->isElementNode()) {
34
+ throw new InvalidArgumentException(sprintf('The element must contain DOMElement node'));
35
+ }
36
+
37
+ $this->element = $element;
38
+
39
+ $this->parseStyleAttribute();
40
+ }
41
+
42
+ /**
43
+ * Parses style attribute of the element.
44
+ */
45
+ protected function parseStyleAttribute()
46
+ {
47
+ if ( ! $this->element->hasAttribute('style')) {
48
+ // possible if style attribute has been removed
49
+ if ($this->styleString !== '') {
50
+ $this->styleString = '';
51
+ $this->properties = [];
52
+ }
53
+
54
+ return;
55
+ }
56
+
57
+ // if style attribute is not changed
58
+ if ($this->element->getAttribute('style') === $this->styleString) {
59
+ return;
60
+ }
61
+
62
+ // save style attribute as is (without trimming)
63
+ $this->styleString = $this->element->getAttribute('style');
64
+
65
+ $styleString = trim($this->styleString, ' ;');
66
+
67
+ if ($styleString === '') {
68
+ $this->properties = [];
69
+
70
+ return;
71
+ }
72
+
73
+ $properties = explode(';', $styleString);
74
+
75
+ foreach ($properties as $property) {
76
+ list($name, $value) = explode(':', $property, 2);
77
+
78
+ $name = trim($name);
79
+ $value = trim($value);
80
+
81
+ $this->properties[$name] = $value;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Updates style attribute of the element.
87
+ */
88
+ protected function updateStyleAttribute()
89
+ {
90
+ $this->styleString = $this->buildStyleString();
91
+
92
+ $this->element->setAttribute('style', $this->styleString);
93
+ }
94
+
95
+ /**
96
+ * @return string
97
+ */
98
+ protected function buildStyleString()
99
+ {
100
+ $properties = [];
101
+
102
+ foreach ($this->properties as $propertyName => $value) {
103
+ $properties[] = $propertyName . ': ' . $value;
104
+ }
105
+
106
+ return implode('; ', $properties);
107
+ }
108
+
109
+ /**
110
+ * @param string $name
111
+ * @param string $value
112
+ *
113
+ * @return StyleAttribute
114
+ *
115
+ * @throws InvalidArgumentException if property name is not a string
116
+ * @throws InvalidArgumentException if property value is not a string
117
+ */
118
+ public function setProperty($name, $value)
119
+ {
120
+ if ( ! is_string($name)) {
121
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($name) ? get_class($name) : gettype($name))));
122
+ }
123
+
124
+ if ( ! is_string($value)) {
125
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, (is_object($value) ? get_class($value) : gettype($value))));
126
+ }
127
+
128
+ $this->parseStyleAttribute();
129
+
130
+ $this->properties[$name] = $value;
131
+
132
+ $this->updateStyleAttribute();
133
+
134
+ return $this;
135
+ }
136
+
137
+ /**
138
+ * @param array $properties
139
+ *
140
+ * @return StyleAttribute
141
+ *
142
+ * @throws InvalidArgumentException if property name is not a string
143
+ * @throws InvalidArgumentException if property value is not a string
144
+ */
145
+ public function setMultipleProperties(array $properties)
146
+ {
147
+ $this->parseStyleAttribute();
148
+
149
+ foreach ($properties as $propertyName => $value) {
150
+ if ( ! is_string($propertyName)) {
151
+ throw new InvalidArgumentException(sprintf('Property name must be a string, %s given', (is_object($propertyName) ? get_class($propertyName) : gettype($propertyName))));
152
+ }
153
+
154
+ if ( ! is_string($value)) {
155
+ throw new InvalidArgumentException(sprintf('Property value must be a string, %s given', (is_object($value) ? get_class($value) : gettype($value))));
156
+ }
157
+
158
+ $this->properties[$propertyName] = $value;
159
+ }
160
+
161
+ $this->updateStyleAttribute();
162
+
163
+ return $this;
164
+ }
165
+
166
+ /**
167
+ * @param string $name
168
+ * @param mixed $default
169
+ *
170
+ * @return mixed
171
+ */
172
+ public function getProperty($name, $default = null)
173
+ {
174
+ if ( ! is_string($name)) {
175
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($name) ? get_class($name) : gettype($name))));
176
+ }
177
+
178
+ $this->parseStyleAttribute();
179
+
180
+ if ( ! array_key_exists($name, $this->properties)) {
181
+ return $default;
182
+ }
183
+
184
+ return $this->properties[$name];
185
+ }
186
+
187
+ /**
188
+ * @param array $propertyNames
189
+ *
190
+ * @return mixed
191
+ *
192
+ * @throws InvalidArgumentException if property name is not a string
193
+ */
194
+ public function getMultipleProperties(array $propertyNames)
195
+ {
196
+ $this->parseStyleAttribute();
197
+
198
+ $result = [];
199
+
200
+ foreach ($propertyNames as $propertyName) {
201
+ if ( ! is_string($propertyName)) {
202
+ throw new InvalidArgumentException(sprintf('Property name must be a string, %s given', (is_object($propertyName) ? get_class($propertyName) : gettype($propertyName))));
203
+ }
204
+
205
+ if (array_key_exists($propertyName, $this->properties)) {
206
+ $result[$propertyName] = $this->properties[$propertyName];
207
+ }
208
+ }
209
+
210
+ return $result;
211
+ }
212
+
213
+ /**
214
+ * @return array
215
+ */
216
+ public function getAllProperties()
217
+ {
218
+ $this->parseStyleAttribute();
219
+
220
+ return $this->properties;
221
+ }
222
+
223
+ /**
224
+ * @param string $name
225
+ *
226
+ * @return bool
227
+ */
228
+ public function hasProperty($name)
229
+ {
230
+ if ( ! is_string($name)) {
231
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($name) ? get_class($name) : gettype($name))));
232
+ }
233
+
234
+ $this->parseStyleAttribute();
235
+
236
+ return array_key_exists($name, $this->properties);
237
+ }
238
+
239
+ /**
240
+ * @param string $name
241
+ *
242
+ * @return StyleAttribute
243
+ *
244
+ * @throws InvalidArgumentException if property name is not a string
245
+ */
246
+ public function removeProperty($name)
247
+ {
248
+ if ( ! is_string($name)) {
249
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($name) ? get_class($name) : gettype($name))));
250
+ }
251
+
252
+ $this->parseStyleAttribute();
253
+
254
+ unset($this->properties[$name]);
255
+
256
+ $this->updateStyleAttribute();
257
+
258
+ return $this;
259
+ }
260
+
261
+ /**
262
+ * @param array $propertyNames
263
+ *
264
+ * @return StyleAttribute
265
+ *
266
+ * @throws InvalidArgumentException if property name is not a string
267
+ */
268
+ public function removeMultipleProperties(array $propertyNames)
269
+ {
270
+ $this->parseStyleAttribute();
271
+
272
+ foreach ($propertyNames as $propertyName) {
273
+ if ( ! is_string($propertyName)) {
274
+ throw new InvalidArgumentException(sprintf('Property name must be a string, %s given', (is_object($propertyName) ? get_class($propertyName) : gettype($propertyName))));
275
+ }
276
+
277
+ unset($this->properties[$propertyName]);
278
+ }
279
+
280
+ $this->updateStyleAttribute();
281
+
282
+ return $this;
283
+ }
284
+
285
+ /**
286
+ * @param string[] $exclusions
287
+ *
288
+ * @return StyleAttribute
289
+ */
290
+ public function removeAllProperties(array $exclusions = [])
291
+ {
292
+ $this->parseStyleAttribute();
293
+
294
+ $preservedProperties = [];
295
+
296
+ foreach ($exclusions as $propertyName) {
297
+ if ( ! is_string($propertyName)) {
298
+ throw new InvalidArgumentException(sprintf('Property name must be a string, %s given', (is_object($propertyName) ? get_class($propertyName) : gettype($propertyName))));
299
+ }
300
+
301
+ if ( ! array_key_exists($propertyName, $this->properties)) {
302
+ continue;
303
+ }
304
+
305
+ $preservedProperties[$propertyName] = $this->properties[$propertyName];
306
+ }
307
+
308
+ $this->properties = $preservedProperties;
309
+
310
+ $this->updateStyleAttribute();
311
+
312
+ return $this;
313
+ }
314
+
315
+ /**
316
+ * @return Element
317
+ */
318
+ public function getElement()
319
+ {
320
+ return $this->element;
321
+ }
322
+ }