From b26947526caadf2adfe8e15f2c1a5134bcee4f6f Mon Sep 17 00:00:00 2001 From: Hasnain Shahid <69379555+hasnain37@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:53:33 +0500 Subject: [PATCH] Dev (#122) * Apply fixes from StyleCI (#119) * update freemius sdk, fix plugin deletion error, edit profile form with existing email and select2 library version upgrade --------- Co-authored-by: Muhammad Usama Masood Co-authored-by: Hamza Bin Ilyas --- CHANGELOG.md | 7 + admin/class-wpfep-admin-help.php | 1 - admin/class-wpfep-shortcodes-button.php | 1 - assets/js/select2/select2.min.js | 5 +- freemius/.editorconfig | 9 + freemius/assets/css/admin/account.css | 2 +- freemius/assets/css/admin/add-ons.css | 3 +- freemius/assets/css/admin/affiliation.css | 2 +- .../assets/css/admin/clone-resolution.css | 1 + freemius/assets/css/admin/common.css | 3 +- freemius/assets/css/admin/connect.css | 2 +- freemius/assets/css/admin/debug.css | 2 +- freemius/assets/css/admin/dialog-boxes.css | 3 +- .../assets/css/admin/gdpr-optin-notice.css | 2 +- freemius/assets/css/admin/optout.css | 1 + freemius/assets/css/customizer.css | 2 +- freemius/config.php | 22 +- freemius/includes/class-freemius-abstract.php | 69 +- freemius/includes/class-freemius.php | 4459 ++++++++++------- freemius/includes/class-fs-admin-notices.php | 82 +- freemius/includes/class-fs-api.php | 295 +- freemius/includes/class-fs-lock.php | 117 + freemius/includes/class-fs-logger.php | 1360 +++-- freemius/includes/class-fs-plugin-updater.php | 129 +- freemius/includes/class-fs-storage.php | 187 +- freemius/includes/class-fs-user-lock.php | 58 +- .../class-fs-customizer-support-section.php | 141 +- .../class-fs-customizer-upsell-control.php | 243 +- .../debug/class-fs-debug-bar-panel.php | 119 +- .../entities/class-fs-affiliate-terms.php | 5 +- .../includes/entities/class-fs-affiliate.php | 1 - .../includes/entities/class-fs-billing.php | 1 - .../includes/entities/class-fs-payment.php | 1 - .../entities/class-fs-plugin-license.php | 1 - .../entities/class-fs-plugin-plan.php | 1 - .../includes/entities/class-fs-plugin-tag.php | 4 + .../includes/entities/class-fs-plugin.php | 5 + .../includes/entities/class-fs-pricing.php | 1 - freemius/includes/entities/class-fs-site.php | 49 +- .../entities/class-fs-subscription.php | 1 - freemius/includes/entities/class-fs-user.php | 13 +- freemius/includes/fs-core-functions.php | 796 ++- freemius/includes/fs-essential-functions.php | 106 +- .../includes/fs-html-escaping-functions.php | 128 + freemius/includes/fs-plugin-info-dialog.php | 925 ++-- .../managers/class-fs-admin-menu-manager.php | 9 +- .../class-fs-admin-notice-manager.php | 97 +- .../managers/class-fs-clone-manager.php | 1709 +++++++ .../managers/class-fs-gdpr-manager.php | 13 - .../managers/class-fs-key-value-storage.php | 10 + .../managers/class-fs-option-manager.php | 116 +- .../managers/class-fs-permission-manager.php | 737 +++ .../managers/class-fs-plugin-manager.php | 21 +- .../includes/sdk/Exceptions/Exception.php | 124 +- freemius/includes/sdk/FreemiusBase.php | 308 +- freemius/includes/sdk/FreemiusWordPress.php | 1140 +++-- .../supplements/fs-migration-2.5.1.php | 32 + freemius/languages/freemius-cs_CZ.mo | Bin 60931 -> 36563 bytes freemius/languages/freemius-da_DK.mo | Bin 59840 -> 69088 bytes freemius/languages/freemius-de_DE.mo | Bin 0 -> 73551 bytes freemius/languages/freemius-en.mo | Bin 59161 -> 68519 bytes freemius/languages/freemius-es_ES.mo | Bin 62945 -> 72032 bytes freemius/languages/freemius-fr_FR.mo | Bin 63519 -> 49369 bytes freemius/languages/freemius-he_IL.mo | Bin 62213 -> 40497 bytes freemius/languages/freemius-hu_HU.mo | Bin 60199 -> 26759 bytes freemius/languages/freemius-it_IT.mo | Bin 61645 -> 59634 bytes freemius/languages/freemius-ja.mo | Bin 67335 -> 48367 bytes freemius/languages/freemius-nl_NL.mo | Bin 61628 -> 47831 bytes freemius/languages/freemius-ru_RU.mo | Bin 75599 -> 49143 bytes freemius/languages/freemius-ta.mo | Bin 93241 -> 77216 bytes freemius/languages/freemius-zh_CN.mo | Bin 56773 -> 55459 bytes freemius/languages/freemius.pot | 1577 +++--- freemius/phpcompat.xml | 19 + freemius/phpstan.neon | 14 + freemius/require.php | 5 +- freemius/start.php | 8 +- freemius/templates/account.php | 909 ++-- freemius/templates/account/billing.php | 603 ++- .../partials/activate-license-button.php | 35 +- freemius/templates/account/partials/addon.php | 308 +- .../partials/deactivate-license-button.php | 12 +- .../account/partials/disconnect-button.php | 104 + freemius/templates/account/partials/site.php | 279 +- freemius/templates/account/payments.php | 49 +- freemius/templates/add-ons.php | 387 +- freemius/templates/add-trial-to-pricing.php | 32 +- freemius/templates/admin-notice.php | 154 +- freemius/templates/ajax-loader.php | 4 +- .../templates/api-connectivity-message-js.php | 32 + freemius/templates/auto-installation.php | 156 +- freemius/templates/checkout.php | 399 +- freemius/templates/clone-resolution-js.php | 79 + freemius/templates/connect.php | 827 +-- freemius/templates/connect/index.php | 3 + freemius/templates/connect/permission.php | 43 + .../templates/connect/permissions-group.php | 72 + freemius/templates/contact.php | 168 +- freemius/templates/debug.php | 486 +- freemius/templates/debug/api-calls.php | 205 +- freemius/templates/debug/logger.php | 76 +- .../templates/debug/plugins-themes-sync.php | 92 +- freemius/templates/debug/scheduled-crons.php | 206 +- freemius/templates/email.php | 49 +- freemius/templates/forms/affiliation.php | 210 +- freemius/templates/forms/data-debug-mode.php | 82 +- .../templates/forms/deactivation/form.php | 381 +- .../templates/forms/email-address-update.php | 347 ++ .../templates/forms/license-activation.php | 215 +- freemius/templates/forms/optout.php | 495 +- .../premium-versions-upgrade-handler.php | 42 +- .../premium-versions-upgrade-metadata.php | 18 +- freemius/templates/forms/resend-key.php | 89 +- .../forms/subscription-cancellation.php | 98 +- freemius/templates/forms/trial-start.php | 94 +- freemius/templates/forms/user-change.php | 40 +- freemius/templates/gdpr-optin-js.php | 31 +- .../templates/js/jquery.content-change.php | 18 +- .../templates/js/open-license-activation.php | 2 +- freemius/templates/js/permissions.php | 546 ++ freemius/templates/js/style-premium-theme.php | 44 +- .../templates/partials/network-activation.php | 70 +- freemius/templates/plugin-icon.php | 30 +- .../templates/plugin-info/description.php | 85 +- freemius/templates/plugin-info/features.php | 154 +- .../templates/plugin-info/screenshots.php | 41 +- freemius/templates/powered-by.php | 72 +- freemius/templates/pricing.php | 260 +- freemius/templates/secure-https-header.php | 62 +- freemius/templates/sticky-admin-notice-js.php | 29 +- freemius/templates/tabs-capture-js.php | 43 +- freemius/templates/tabs.php | 34 +- functions/save-fields.php | 161 +- inc/class-wp-frontend-profile.php | 1 - inc/class-wpfep-captcha-recaptcha.php | 1 - inc/class-wpfep-login.php | 6 - inc/class-wpfep-registration.php | 2 - inc/class-wpfep-user.php | 1 - readme.txt | 4 +- tests/test-sample.php | 1 - wp-frontend-profile.php | 4 +- 140 files changed, 15141 insertions(+), 9745 deletions(-) create mode 100644 freemius/.editorconfig create mode 100644 freemius/assets/css/admin/clone-resolution.css create mode 100644 freemius/assets/css/admin/optout.css create mode 100644 freemius/includes/class-fs-lock.php create mode 100644 freemius/includes/fs-html-escaping-functions.php create mode 100644 freemius/includes/managers/class-fs-clone-manager.php create mode 100644 freemius/includes/managers/class-fs-permission-manager.php create mode 100644 freemius/includes/supplements/fs-migration-2.5.1.php create mode 100644 freemius/languages/freemius-de_DE.mo create mode 100644 freemius/phpcompat.xml create mode 100644 freemius/phpstan.neon create mode 100644 freemius/templates/account/partials/disconnect-button.php create mode 100644 freemius/templates/api-connectivity-message-js.php create mode 100644 freemius/templates/clone-resolution-js.php create mode 100644 freemius/templates/connect/index.php create mode 100644 freemius/templates/connect/permission.php create mode 100644 freemius/templates/connect/permissions-group.php create mode 100644 freemius/templates/forms/email-address-update.php create mode 100644 freemius/templates/js/permissions.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 3539f9d..c8f1187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file, per [the Ke ## [Unreleased] +## [1.3.1] - 2023-10-14 +- Tested with WordPress version 6.3.2 #121 +- Update Freemius SDK version #121 +- Update select2 library #121 +- Fix error on plugin deletion #121 +- Fix error on profile form when updating email with existing email #121 + ## [1.3.0] - 2023-03-08 ### Added diff --git a/admin/class-wpfep-admin-help.php b/admin/class-wpfep-admin-help.php index c218548..4d7d4b0 100644 --- a/admin/class-wpfep-admin-help.php +++ b/admin/class-wpfep-admin-help.php @@ -12,7 +12,6 @@ */ class WPFEP_Admin_Help { - /** * Hook in tabs. */ diff --git a/admin/class-wpfep-shortcodes-button.php b/admin/class-wpfep-shortcodes-button.php index 3d9e06a..a6c9bf2 100644 --- a/admin/class-wpfep-shortcodes-button.php +++ b/admin/class-wpfep-shortcodes-button.php @@ -14,7 +14,6 @@ */ class WPFEP_Shortcodes_Button { - /** * Constructor for shortcode class. */ diff --git a/assets/js/select2/select2.min.js b/assets/js/select2/select2.min.js index 43f0a65..e421426 100644 --- a/assets/js/select2/select2.min.js +++ b/assets/js/select2/select2.min.js @@ -1,3 +1,2 @@ -/*! Select2 4.0.3 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return u.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n=b&&b.split("/"),o=s.map,p=o&&o["*"]||{};if(a&&"."===a.charAt(0))if(b){for(a=a.split("/"),g=a.length-1,s.nodeIdCompat&&w.test(a[g])&&(a[g]=a[g].replace(w,"")),a=n.slice(0,n.length-1).concat(a),k=0;k0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}else 0===a.indexOf("./")&&(a=a.substring(2));if((n||p)&&o){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),n)for(l=n.length;l>0;l-=1)if(e=o[n.slice(0,l).join("/")],e&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&p&&p[d]&&(i=p[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){var d=v.call(arguments,0);return"string"!=typeof d[0]&&1===d.length&&d.push(null),n.apply(b,d.concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){q[a]=b}}function j(a){if(e(r,a)){var c=r[a];delete r[a],t[a]=!0,m.apply(b,c)}if(!e(q,a)&&!e(t,a))throw new Error("No "+a);return q[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return function(){return s&&s.config&&s.config[a]||{}}}var m,n,o,p,q={},r={},s={},t={},u=Object.prototype.hasOwnProperty,v=[].slice,w=/\.js$/;o=function(a,b){var c,d=k(a),e=d[0];return a=d[1],e&&(e=f(e,b),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(b)):f(a,b):(a=f(a,b),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},p={require:function(a){return g(a)},exports:function(a){var b=q[a];return"undefined"!=typeof b?b:q[a]={}},module:function(a){return{id:a,uri:"",exports:q[a],config:l(a)}}},m=function(a,c,d,f){var h,k,l,m,n,s,u=[],v=typeof d;if(f=f||a,"undefined"===v||"function"===v){for(c=!c.length&&d.length?["require","exports","module"]:c,n=0;n0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;hc;c++)a[c].apply(this,b)},c.Observable=d,c.generateChars=function(a){for(var b="",c=0;a>c;c++){var d=Math.floor(36*Math.random());b+=d.toString(36)}return b},c.bind=function(a,b){return function(){a.apply(b,arguments)}},c._convertData=function(a){for(var b in a){var c=b.split("-"),d=a;if(1!==c.length){for(var e=0;e":">",'"':""","'":"'","/":"/"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c}),b.define("select2/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('
    ');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a('
  • '),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),d[0].className+=" select2-results__message",this.$results.append(d)},c.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c0?b.first().trigger("mouseenter"):a.first().trigger("mouseenter"),this.ensureHighlightVisible()},c.prototype.setClasses=function(){var b=this;this.data.current(function(c){var d=a.map(c,function(a){return a.id.toString()}),e=b.$results.find(".select2-results__option[aria-selected]");e.each(function(){var b=a(this),c=a.data(this,"data"),e=""+c.id;null!=c.element&&c.element.selected||null==c.element&&a.inArray(e,d)>-1?b.attr("aria-selected","true"):b.attr("aria-selected","false")})})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(b){var c=document.createElement("li");c.className="select2-results__option";var d={role:"treeitem","aria-selected":"false"};b.disabled&&(delete d["aria-selected"],d["aria-disabled"]="true"),null==b.id&&delete d["aria-selected"],null!=b._resultId&&(c.id=b._resultId),b.title&&(c.title=b.title),b.children&&(d.role="group",d["aria-label"]=b.text,delete d["aria-selected"]);for(var e in d){var f=d[e];c.setAttribute(e,f)}if(b.children){var g=a(c),h=document.createElement("strong");h.className="select2-results__group";a(h);this.template(b,h);for(var i=[],j=0;j",{"class":"select2-results__options select2-results__options--nested"});m.append(i),g.append(h),g.append(m)}else this.template(b,c);return a.data(c,"data",b),c},c.prototype.bind=function(b,c){var d=this,e=b.id+"-results";this.$results.attr("id",e),b.on("results:all",function(a){d.clear(),d.append(a.data),b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("results:append",function(a){d.append(a.data),b.isOpen()&&d.setClasses()}),b.on("query",function(a){d.hideMessages(),d.showLoading(a)}),b.on("select",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("unselect",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("open",function(){d.$results.attr("aria-expanded","true"),d.$results.attr("aria-hidden","false"),d.setClasses(),d.ensureHighlightVisible()}),b.on("close",function(){d.$results.attr("aria-expanded","false"),d.$results.attr("aria-hidden","true"),d.$results.removeAttr("aria-activedescendant")}),b.on("results:toggle",function(){var a=d.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),b.on("results:select",function(){var a=d.getHighlightedResults();if(0!==a.length){var b=a.data("data");"true"==a.attr("aria-selected")?d.trigger("close",{}):d.trigger("select",{data:b})}}),b.on("results:previous",function(){var a=d.getHighlightedResults(),b=d.$results.find("[aria-selected]"),c=b.index(a);if(0!==c){var e=c-1;0===a.length&&(e=0);var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top,h=f.offset().top,i=d.$results.scrollTop()+(h-g);0===e?d.$results.scrollTop(0):0>h-g&&d.$results.scrollTop(i)}}),b.on("results:next",function(){var a=d.getHighlightedResults(),b=d.$results.find("[aria-selected]"),c=b.index(a),e=c+1;if(!(e>=b.length)){var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top+d.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=d.$results.scrollTop()+h-g;0===e?d.$results.scrollTop(0):h>g&&d.$results.scrollTop(i)}}),b.on("results:focus",function(a){a.element.addClass("select2-results__option--highlighted")}),b.on("results:message",function(a){d.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=d.$results.scrollTop(),c=d.$results.get(0).scrollHeight-b+a.deltaY,e=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&c<=d.$results.height();e?(d.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(d.$results.scrollTop(d.$results.get(0).scrollHeight-d.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2-results__option[aria-selected]",function(b){var c=a(this),e=c.data("data");return"true"===c.attr("aria-selected")?void(d.options.get("multiple")?d.trigger("unselect",{originalEvent:b,data:e}):d.trigger("close",{})):void d.trigger("select",{originalEvent:b,data:e})}),this.$results.on("mouseenter",".select2-results__option[aria-selected]",function(b){var c=a(this).data("data");d.getHighlightedResults().removeClass("select2-results__option--highlighted"),d.trigger("results:focus",{data:c,element:a(this)})})},c.prototype.getHighlightedResults=function(){var a=this.$results.find(".select2-results__option--highlighted");return a},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[aria-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),2>=c?this.$results.scrollTop(0):(g>this.$results.outerHeight()||0>g)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b,c);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2/keys",[],function(){var a={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46};return a}),b.define("select2/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var b=a('');return this._tabindex=0,null!=this.$element.data("old-tabindex")?this._tabindex=this.$element.data("old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),b.attr("title",this.$element.attr("title")),b.attr("tabindex",this._tabindex),this.$selection=b,b},d.prototype.bind=function(a,b){var d=this,e=(a.id+"-container",a.id+"-results");this.container=a,this.$selection.on("focus",function(a){d.trigger("focus",a)}),this.$selection.on("blur",function(a){d._handleBlur(a)}),this.$selection.on("keydown",function(a){d.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){d.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){d.update(a.data)}),a.on("open",function(){d.$selection.attr("aria-expanded","true"),d.$selection.attr("aria-owns",e),d._attachCloseHandler(a)}),a.on("close",function(){d.$selection.attr("aria-expanded","false"),d.$selection.removeAttr("aria-activedescendant"),d.$selection.removeAttr("aria-owns"),d.$selection.focus(),d._detachCloseHandler(a)}),a.on("enable",function(){d.$selection.attr("tabindex",d._tabindex)}),a.on("disable",function(){d.$selection.attr("tabindex","-1")})},d.prototype._handleBlur=function(b){var c=this;window.setTimeout(function(){document.activeElement==c.$selection[0]||a.contains(c.$selection[0],document.activeElement)||c.trigger("blur",b)},1)},d.prototype._attachCloseHandler=function(b){a(document.body).on("mousedown.select2."+b.id,function(b){var c=a(b.target),d=c.closest(".select2"),e=a(".select2.select2-container--open");e.each(function(){var b=a(this);if(this!=d[0]){var c=b.data("element");c.select2("close")}})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2."+b.id)},d.prototype.position=function(a,b){var c=b.find(".selection");c.append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(a){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c,d){function e(){e.__super__.constructor.apply(this,arguments)}return c.Extend(e,b),e.prototype.render=function(){var a=e.__super__.render.call(this);return a.addClass("select2-selection--single"),a.html(''),a},e.prototype.bind=function(a,b){var c=this;e.__super__.bind.apply(this,arguments);var d=a.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",d),this.$selection.attr("aria-labelledby",d),this.$selection.on("mousedown",function(a){1===a.which&&c.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(a){}),this.$selection.on("blur",function(a){}),a.on("focus",function(b){a.isOpen()||c.$selection.focus()}),a.on("selection:update",function(a){c.update(a.data)})},e.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},e.prototype.display=function(a,b){var c=this.options.get("templateSelection"),d=this.options.get("escapeMarkup");return d(c(a,b))},e.prototype.selectionContainer=function(){return a("")},e.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.$selection.find(".select2-selection__rendered"),d=this.display(b,c);c.empty().append(d),c.prop("title",b.title||b.text)},e}),b.define("select2/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(a,b){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--multiple"),a.html('
      '),a},d.prototype.bind=function(b,c){var e=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){e.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2-selection__choice__remove",function(b){if(!e.options.get("disabled")){var c=a(this),d=c.parent(),f=d.data("data");e.trigger("unselect",{originalEvent:b,data:f})}})},d.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},d.prototype.display=function(a,b){var c=this.options.get("templateSelection"),d=this.options.get("escapeMarkup");return d(c(a,b))},d.prototype.selectionContainer=function(){var b=a('
    • ×
    • ');return b},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d1;if(d||c)return a.call(this,b);this.clear();var e=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(e)},b}),b.define("select2/selection/allowClear",["jquery","../keys"],function(a,b){function c(){}return c.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},c.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var c=this.$selection.find(".select2-selection__clear");if(0!==c.length){b.stopPropagation();for(var d=c.data("data"),e=0;e0||0===c.length)){var d=a('×');d.data("data",c),this.$selection.find(".select2-selection__rendered").prepend(d)}},c}),b.define("select2/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,b,d){var e=this;a.call(this,b,d),b.on("open",function(){e.$search.trigger("focus")}),b.on("close",function(){e.$search.val(""),e.$search.removeAttr("aria-activedescendant"),e.$search.trigger("focus")}),b.on("enable",function(){e.$search.prop("disabled",!1),e._transferTabIndex()}),b.on("disable",function(){e.$search.prop("disabled",!0)}),b.on("focus",function(a){e.$search.trigger("focus")}),b.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.id)}),this.$selection.on("focusin",".select2-search--inline",function(a){e.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){e._handleBlur(a)}),this.$selection.on("keydown",".select2-search--inline",function(a){a.stopPropagation(),e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented();var b=a.which;if(b===c.BACKSPACE&&""===e.$search.val()){var d=e.$searchContainer.prev(".select2-selection__choice");if(d.length>0){var f=d.data("data");e.searchRemoveChoice(f),a.preventDefault()}}});var f=document.documentMode,g=f&&11>=f;this.$selection.on("input.searchcheck",".select2-search--inline",function(a){return g?void e.$selection.off("input.search input.searchcheck"):void e.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(a){if(g&&"input"===a.type)return void e.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&e.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c&&this.$search.focus()},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{var b=this.$search.val().length+1;a=.75*b+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting"],g=["opening","closing","selecting","unselecting"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){var a={"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"};return a}),b.define("select2/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d=b.id+"-result-";return d+=a.generateChars(4),d+=null!=c.id?"-"+c.id.toString():"-"+a.generateChars(4)},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change"); -if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){d.status&&"0"===d.status||e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&null!=a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h0&&b.term.length>this.maximumInputLength?void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}}):void a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;return d.maximumSelectionLength>0&&f>=d.maximumSelectionLength?void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}}):void a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.val("")}),c.on("focus",function(){c.isOpen()&&e.$search.focus()}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){var b=e.showSearch(a);b?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){var c=e.$results.offset().top+e.$results.outerHeight(!1),d=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1);c+50>=d&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('
    • '),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a(""),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2."+d.id,g="resize.select2."+d.id,h="orientationchange.select2."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){a(this).data("select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(b){var c=a(this).data("select2-scroll-position");a(this).scrollTop(c.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2."+d.id,f="resize.select2."+d.id,g="orientationchange.select2."+d.id,h=this.$container.parents().filter(b.hasScroll);h.off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.topf.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-n.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.position="relative",a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),(null!=l.tokenSeparators||null!=l.tokenizer)&&(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){var h=e.children[g],i=c(d,h);null==i&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var j=b(e.text).toUpperCase(),k=b(d.term).toUpperCase();return j.indexOf(k)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(this.defaults,f)};var E=new D;return E}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),a.data("select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),a.data("data",a.data("select2Tags")),a.data("tags",!0)),a.data("ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",a.data("ajaxUrl")),a.data("ajax--url",a.data("ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,a.data()):a.data();var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,c){null!=a.data("select2")&&a.data("select2").destroy(),this.$element=a,this.id=this._generateId(a),c=c||{},this.options=new b(c,a),e.__super__.constructor.call(this);var d=a.attr("tabindex")||0;a.data("old-tabindex",d),a.attr("tabindex","-1");var f=this.options.get("dataAdapter");this.dataAdapter=new f(a,this.options);var g=this.render();this._placeContainer(g);var h=this.options.get("selectionAdapter");this.selection=new h(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,g);var i=this.options.get("dropdownAdapter");this.dropdown=new i(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,g);var j=this.options.get("resultsAdapter");this.results=new j(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var k=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){k.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return 0>=e?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;i>h;h+=1){var j=g[h].replace(/\s/g,""),k=j.match(c);if(null!==k&&k.length>=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this.$element.on("focus.select2",function(a){b.trigger("focus",a)}),this._syncA=c.bind(this._syncAttributes,this),this._syncS=c.bind(this._syncSubtree,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._syncA);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._syncA),a.each(c,b._syncS)}),this._observer.observe(this.$element[0],{attributes:!0,childList:!0,subtree:!1})):this.$element[0].addEventListener&&(this.$element[0].addEventListener("DOMAttrModified",b._syncA,!1),this.$element[0].addEventListener("DOMNodeInserted",b._syncS,!1),this.$element[0].addEventListener("DOMNodeRemoved",b._syncS,!1))},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype._syncSubtree=function(a,b){var c=!1,d=this;if(!a||!a.target||"OPTION"===a.target.nodeName||"OPTGROUP"===a.target.nodeName){if(b)if(b.addedNodes&&b.addedNodes.length>0)for(var e=0;e0&&(c=!0);else c=!0;c&&this.dataAdapter.current(function(a){d.trigger("selection:update",{data:a})})}},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),(null==a||0===a.length)&&(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._syncA),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&(this.$element[0].removeEventListener("DOMAttrModified",this._syncA,!1),this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,!1),this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,!1)),this._syncA=null,this._syncS=null,this.$element.off(".select2"),this.$element.attr("tabindex",this.$element.data("old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null; -},e.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),b.data("element",this.$element),b},e}),b.define("jquery-mousewheel",["jquery"],function(a){return a}),b.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults"],function(a,b,c,d){if(null==a.fn.select2){var e=["open","close","destroy"];a.fn.select2=function(b){if(b=b||{},"object"==typeof b)return this.each(function(){var d=a.extend(!0,{},b);new c(a(this),d)}),this;if("string"==typeof b){var d,f=Array.prototype.slice.call(arguments,1);return this.each(function(){var c=a(this).data("select2");null==c&&window.console&&console.error&&console.error("The select2('"+b+"') method was called on an element that is not using Select2."),d=c[b].apply(c,f)}),a.inArray(b,e)>-1?this:d}throw new Error("Invalid arguments for Select2: "+b)}}return null==a.fn.select2.defaults&&(a.fn.select2.defaults=d),c}),{define:b.define,require:b.require}}(),c=b.require("jquery.select2");return a.fn.select2.amd=b,c}); \ No newline at end of file +/*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ +!function(n){"function"==typeof define&&define.amd?define(["jquery"],n):"object"==typeof module&&module.exports?module.exports=function(e,t){return void 0===t&&(t="undefined"!=typeof window?require("jquery"):require("jquery")(e)),n(t),t}:n(jQuery)}(function(u){var e=function(){if(u&&u.fn&&u.fn.select2&&u.fn.select2.amd)var e=u.fn.select2.amd;var t,n,r,h,o,s,f,g,m,v,y,_,i,a,b;function w(e,t){return i.call(e,t)}function l(e,t){var n,r,i,o,s,a,l,c,u,d,p,h=t&&t.split("/"),f=y.map,g=f&&f["*"]||{};if(e){for(s=(e=e.split("/")).length-1,y.nodeIdCompat&&b.test(e[s])&&(e[s]=e[s].replace(b,"")),"."===e[0].charAt(0)&&h&&(e=h.slice(0,h.length-1).concat(e)),u=0;u":">",'"':""","'":"'","/":"/"};return"string"!=typeof e?e:String(e).replace(/[&<>"'\/\\]/g,function(e){return t[e]})},i.appendMany=function(e,t){if("1.7"===o.fn.jquery.substr(0,3)){var n=o();o.map(t,function(e){n=n.add(e)}),t=n}e.append(t)},i.__cache={};var n=0;return i.GetUniqueElementId=function(e){var t=e.getAttribute("data-select2-id");return null==t&&(e.id?(t=e.id,e.setAttribute("data-select2-id",t)):(e.setAttribute("data-select2-id",++n),t=n.toString())),t},i.StoreData=function(e,t,n){var r=i.GetUniqueElementId(e);i.__cache[r]||(i.__cache[r]={}),i.__cache[r][t]=n},i.GetData=function(e,t){var n=i.GetUniqueElementId(e);return t?i.__cache[n]&&null!=i.__cache[n][t]?i.__cache[n][t]:o(e).data(t):i.__cache[n]},i.RemoveData=function(e){var t=i.GetUniqueElementId(e);null!=i.__cache[t]&&delete i.__cache[t],e.removeAttribute("data-select2-id")},i}),e.define("select2/results",["jquery","./utils"],function(h,f){function r(e,t,n){this.$element=e,this.data=n,this.options=t,r.__super__.constructor.call(this)}return f.Extend(r,f.Observable),r.prototype.render=function(){var e=h('
        ');return this.options.get("multiple")&&e.attr("aria-multiselectable","true"),this.$results=e},r.prototype.clear=function(){this.$results.empty()},r.prototype.displayMessage=function(e){var t=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var n=h(''),r=this.options.get("translations").get(e.message);n.append(t(r(e.args))),n[0].className+=" select2-results__message",this.$results.append(n)},r.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},r.prototype.append=function(e){this.hideLoading();var t=[];if(null!=e.results&&0!==e.results.length){e.results=this.sort(e.results);for(var n=0;n",{class:"select2-results__options select2-results__options--nested"});p.append(l),s.append(a),s.append(p)}else this.template(e,t);return f.StoreData(t,"data",e),t},r.prototype.bind=function(t,e){var l=this,n=t.id+"-results";this.$results.attr("id",n),t.on("results:all",function(e){l.clear(),l.append(e.data),t.isOpen()&&(l.setClasses(),l.highlightFirstItem())}),t.on("results:append",function(e){l.append(e.data),t.isOpen()&&l.setClasses()}),t.on("query",function(e){l.hideMessages(),l.showLoading(e)}),t.on("select",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("unselect",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("open",function(){l.$results.attr("aria-expanded","true"),l.$results.attr("aria-hidden","false"),l.setClasses(),l.ensureHighlightVisible()}),t.on("close",function(){l.$results.attr("aria-expanded","false"),l.$results.attr("aria-hidden","true"),l.$results.removeAttr("aria-activedescendant")}),t.on("results:toggle",function(){var e=l.getHighlightedResults();0!==e.length&&e.trigger("mouseup")}),t.on("results:select",function(){var e=l.getHighlightedResults();if(0!==e.length){var t=f.GetData(e[0],"data");"true"==e.attr("aria-selected")?l.trigger("close",{}):l.trigger("select",{data:t})}}),t.on("results:previous",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e);if(!(n<=0)){var r=n-1;0===e.length&&(r=0);var i=t.eq(r);i.trigger("mouseenter");var o=l.$results.offset().top,s=i.offset().top,a=l.$results.scrollTop()+(s-o);0===r?l.$results.scrollTop(0):s-o<0&&l.$results.scrollTop(a)}}),t.on("results:next",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e)+1;if(!(n>=t.length)){var r=t.eq(n);r.trigger("mouseenter");var i=l.$results.offset().top+l.$results.outerHeight(!1),o=r.offset().top+r.outerHeight(!1),s=l.$results.scrollTop()+o-i;0===n?l.$results.scrollTop(0):ithis.$results.outerHeight()||o<0)&&this.$results.scrollTop(i)}},r.prototype.template=function(e,t){var n=this.options.get("templateResult"),r=this.options.get("escapeMarkup"),i=n(e,t);null==i?t.style.display="none":"string"==typeof i?t.innerHTML=r(i):h(t).append(i)},r}),e.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),e.define("select2/selection/base",["jquery","../utils","../keys"],function(n,r,i){function o(e,t){this.$element=e,this.options=t,o.__super__.constructor.call(this)}return r.Extend(o,r.Observable),o.prototype.render=function(){var e=n('');return this._tabindex=0,null!=r.GetData(this.$element[0],"old-tabindex")?this._tabindex=r.GetData(this.$element[0],"old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),e.attr("title",this.$element.attr("title")),e.attr("tabindex",this._tabindex),e.attr("aria-disabled","false"),this.$selection=e},o.prototype.bind=function(e,t){var n=this,r=e.id+"-results";this.container=e,this.$selection.on("focus",function(e){n.trigger("focus",e)}),this.$selection.on("blur",function(e){n._handleBlur(e)}),this.$selection.on("keydown",function(e){n.trigger("keypress",e),e.which===i.SPACE&&e.preventDefault()}),e.on("results:focus",function(e){n.$selection.attr("aria-activedescendant",e.data._resultId)}),e.on("selection:update",function(e){n.update(e.data)}),e.on("open",function(){n.$selection.attr("aria-expanded","true"),n.$selection.attr("aria-owns",r),n._attachCloseHandler(e)}),e.on("close",function(){n.$selection.attr("aria-expanded","false"),n.$selection.removeAttr("aria-activedescendant"),n.$selection.removeAttr("aria-owns"),n.$selection.trigger("focus"),n._detachCloseHandler(e)}),e.on("enable",function(){n.$selection.attr("tabindex",n._tabindex),n.$selection.attr("aria-disabled","false")}),e.on("disable",function(){n.$selection.attr("tabindex","-1"),n.$selection.attr("aria-disabled","true")})},o.prototype._handleBlur=function(e){var t=this;window.setTimeout(function(){document.activeElement==t.$selection[0]||n.contains(t.$selection[0],document.activeElement)||t.trigger("blur",e)},1)},o.prototype._attachCloseHandler=function(e){n(document.body).on("mousedown.select2."+e.id,function(e){var t=n(e.target).closest(".select2");n(".select2.select2-container--open").each(function(){this!=t[0]&&r.GetData(this,"element").select2("close")})})},o.prototype._detachCloseHandler=function(e){n(document.body).off("mousedown.select2."+e.id)},o.prototype.position=function(e,t){t.find(".selection").append(e)},o.prototype.destroy=function(){this._detachCloseHandler(this.container)},o.prototype.update=function(e){throw new Error("The `update` method must be defined in child classes.")},o.prototype.isEnabled=function(){return!this.isDisabled()},o.prototype.isDisabled=function(){return this.options.get("disabled")},o}),e.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(e,t,n,r){function i(){i.__super__.constructor.apply(this,arguments)}return n.Extend(i,t),i.prototype.render=function(){var e=i.__super__.render.call(this);return e.addClass("select2-selection--single"),e.html(''),e},i.prototype.bind=function(t,e){var n=this;i.__super__.bind.apply(this,arguments);var r=t.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",r).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",r),this.$selection.on("mousedown",function(e){1===e.which&&n.trigger("toggle",{originalEvent:e})}),this.$selection.on("focus",function(e){}),this.$selection.on("blur",function(e){}),t.on("focus",function(e){t.isOpen()||n.$selection.trigger("focus")})},i.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},i.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},i.prototype.selectionContainer=function(){return e("")},i.prototype.update=function(e){if(0!==e.length){var t=e[0],n=this.$selection.find(".select2-selection__rendered"),r=this.display(t,n);n.empty().append(r);var i=t.title||t.text;i?n.attr("title",i):n.removeAttr("title")}else this.clear()},i}),e.define("select2/selection/multiple",["jquery","./base","../utils"],function(i,e,l){function n(e,t){n.__super__.constructor.apply(this,arguments)}return l.Extend(n,e),n.prototype.render=function(){var e=n.__super__.render.call(this);return e.addClass("select2-selection--multiple"),e.html('
          '),e},n.prototype.bind=function(e,t){var r=this;n.__super__.bind.apply(this,arguments),this.$selection.on("click",function(e){r.trigger("toggle",{originalEvent:e})}),this.$selection.on("click",".select2-selection__choice__remove",function(e){if(!r.isDisabled()){var t=i(this).parent(),n=l.GetData(t[0],"data");r.trigger("unselect",{originalEvent:e,data:n})}})},n.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},n.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},n.prototype.selectionContainer=function(){return i('
        • ×
        • ')},n.prototype.update=function(e){if(this.clear(),0!==e.length){for(var t=[],n=0;n×');a.StoreData(r[0],"data",t),this.$selection.find(".select2-selection__rendered").prepend(r)}},e}),e.define("select2/selection/search",["jquery","../utils","../keys"],function(r,a,l){function e(e,t,n){e.call(this,t,n)}return e.prototype.render=function(e){var t=r('');this.$searchContainer=t,this.$search=t.find("input");var n=e.call(this);return this._transferTabIndex(),n},e.prototype.bind=function(e,t,n){var r=this,i=t.id+"-results";e.call(this,t,n),t.on("open",function(){r.$search.attr("aria-controls",i),r.$search.trigger("focus")}),t.on("close",function(){r.$search.val(""),r.$search.removeAttr("aria-controls"),r.$search.removeAttr("aria-activedescendant"),r.$search.trigger("focus")}),t.on("enable",function(){r.$search.prop("disabled",!1),r._transferTabIndex()}),t.on("disable",function(){r.$search.prop("disabled",!0)}),t.on("focus",function(e){r.$search.trigger("focus")}),t.on("results:focus",function(e){e.data._resultId?r.$search.attr("aria-activedescendant",e.data._resultId):r.$search.removeAttr("aria-activedescendant")}),this.$selection.on("focusin",".select2-search--inline",function(e){r.trigger("focus",e)}),this.$selection.on("focusout",".select2-search--inline",function(e){r._handleBlur(e)}),this.$selection.on("keydown",".select2-search--inline",function(e){if(e.stopPropagation(),r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented(),e.which===l.BACKSPACE&&""===r.$search.val()){var t=r.$searchContainer.prev(".select2-selection__choice");if(0this.maximumInputLength?this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:t.term,params:t}}):e.call(this,t,n)},e}),e.define("select2/data/maximumSelectionLength",[],function(){function e(e,t,n){this.maximumSelectionLength=n.get("maximumSelectionLength"),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("select",function(){r._checkIfMaximumSelected()})},e.prototype.query=function(e,t,n){var r=this;this._checkIfMaximumSelected(function(){e.call(r,t,n)})},e.prototype._checkIfMaximumSelected=function(e,n){var r=this;this.current(function(e){var t=null!=e?e.length:0;0=r.maximumSelectionLength?r.trigger("results:message",{message:"maximumSelected",args:{maximum:r.maximumSelectionLength}}):n&&n()})},e}),e.define("select2/dropdown",["jquery","./utils"],function(t,e){function n(e,t){this.$element=e,this.options=t,n.__super__.constructor.call(this)}return e.Extend(n,e.Observable),n.prototype.render=function(){var e=t('');return e.attr("dir",this.options.get("dir")),this.$dropdown=e},n.prototype.bind=function(){},n.prototype.position=function(e,t){},n.prototype.destroy=function(){this.$dropdown.remove()},n}),e.define("select2/dropdown/search",["jquery","../utils"],function(o,e){function t(){}return t.prototype.render=function(e){var t=e.call(this),n=o('');return this.$searchContainer=n,this.$search=n.find("input"),t.prepend(n),t},t.prototype.bind=function(e,t,n){var r=this,i=t.id+"-results";e.call(this,t,n),this.$search.on("keydown",function(e){r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented()}),this.$search.on("input",function(e){o(this).off("keyup")}),this.$search.on("keyup input",function(e){r.handleSearch(e)}),t.on("open",function(){r.$search.attr("tabindex",0),r.$search.attr("aria-controls",i),r.$search.trigger("focus"),window.setTimeout(function(){r.$search.trigger("focus")},0)}),t.on("close",function(){r.$search.attr("tabindex",-1),r.$search.removeAttr("aria-controls"),r.$search.removeAttr("aria-activedescendant"),r.$search.val(""),r.$search.trigger("blur")}),t.on("focus",function(){t.isOpen()||r.$search.trigger("focus")}),t.on("results:all",function(e){null!=e.query.term&&""!==e.query.term||(r.showSearch(e)?r.$searchContainer.removeClass("select2-search--hide"):r.$searchContainer.addClass("select2-search--hide"))}),t.on("results:focus",function(e){e.data._resultId?r.$search.attr("aria-activedescendant",e.data._resultId):r.$search.removeAttr("aria-activedescendant")})},t.prototype.handleSearch=function(e){if(!this._keyUpPrevented){var t=this.$search.val();this.trigger("query",{term:t})}this._keyUpPrevented=!1},t.prototype.showSearch=function(e,t){return!0},t}),e.define("select2/dropdown/hidePlaceholder",[],function(){function e(e,t,n,r){this.placeholder=this.normalizePlaceholder(n.get("placeholder")),e.call(this,t,n,r)}return e.prototype.append=function(e,t){t.results=this.removePlaceholder(t.results),e.call(this,t)},e.prototype.normalizePlaceholder=function(e,t){return"string"==typeof t&&(t={id:"",text:t}),t},e.prototype.removePlaceholder=function(e,t){for(var n=t.slice(0),r=t.length-1;0<=r;r--){var i=t[r];this.placeholder.id===i.id&&n.splice(r,1)}return n},e}),e.define("select2/dropdown/infiniteScroll",["jquery"],function(n){function e(e,t,n,r){this.lastParams={},e.call(this,t,n,r),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return e.prototype.append=function(e,t){this.$loadingMore.remove(),this.loading=!1,e.call(this,t),this.showLoadingMore(t)&&(this.$results.append(this.$loadingMore),this.loadMoreIfNeeded())},e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("query",function(e){r.lastParams=e,r.loading=!0}),t.on("query:append",function(e){r.lastParams=e,r.loading=!0}),this.$results.on("scroll",this.loadMoreIfNeeded.bind(this))},e.prototype.loadMoreIfNeeded=function(){var e=n.contains(document.documentElement,this.$loadingMore[0]);if(!this.loading&&e){var t=this.$results.offset().top+this.$results.outerHeight(!1);this.$loadingMore.offset().top+this.$loadingMore.outerHeight(!1)<=t+50&&this.loadMore()}},e.prototype.loadMore=function(){this.loading=!0;var e=n.extend({},{page:1},this.lastParams);e.page++,this.trigger("query:append",e)},e.prototype.showLoadingMore=function(e,t){return t.pagination&&t.pagination.more},e.prototype.createLoadingMore=function(){var e=n('
        • '),t=this.options.get("translations").get("loadingMore");return e.html(t(this.lastParams)),e},e}),e.define("select2/dropdown/attachBody",["jquery","../utils"],function(f,a){function e(e,t,n){this.$dropdownParent=f(n.get("dropdownParent")||document.body),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("open",function(){r._showDropdown(),r._attachPositioningHandler(t),r._bindContainerResultHandlers(t)}),t.on("close",function(){r._hideDropdown(),r._detachPositioningHandler(t)}),this.$dropdownContainer.on("mousedown",function(e){e.stopPropagation()})},e.prototype.destroy=function(e){e.call(this),this.$dropdownContainer.remove()},e.prototype.position=function(e,t,n){t.attr("class",n.attr("class")),t.removeClass("select2"),t.addClass("select2-container--open"),t.css({position:"absolute",top:-999999}),this.$container=n},e.prototype.render=function(e){var t=f(""),n=e.call(this);return t.append(n),this.$dropdownContainer=t},e.prototype._hideDropdown=function(e){this.$dropdownContainer.detach()},e.prototype._bindContainerResultHandlers=function(e,t){if(!this._containerResultsHandlersBound){var n=this;t.on("results:all",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:append",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:message",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("select",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("unselect",function(){n._positionDropdown(),n._resizeDropdown()}),this._containerResultsHandlersBound=!0}},e.prototype._attachPositioningHandler=function(e,t){var n=this,r="scroll.select2."+t.id,i="resize.select2."+t.id,o="orientationchange.select2."+t.id,s=this.$container.parents().filter(a.hasScroll);s.each(function(){a.StoreData(this,"select2-scroll-position",{x:f(this).scrollLeft(),y:f(this).scrollTop()})}),s.on(r,function(e){var t=a.GetData(this,"select2-scroll-position");f(this).scrollTop(t.y)}),f(window).on(r+" "+i+" "+o,function(e){n._positionDropdown(),n._resizeDropdown()})},e.prototype._detachPositioningHandler=function(e,t){var n="scroll.select2."+t.id,r="resize.select2."+t.id,i="orientationchange.select2."+t.id;this.$container.parents().filter(a.hasScroll).off(n),f(window).off(n+" "+r+" "+i)},e.prototype._positionDropdown=function(){var e=f(window),t=this.$dropdown.hasClass("select2-dropdown--above"),n=this.$dropdown.hasClass("select2-dropdown--below"),r=null,i=this.$container.offset();i.bottom=i.top+this.$container.outerHeight(!1);var o={height:this.$container.outerHeight(!1)};o.top=i.top,o.bottom=i.top+o.height;var s=this.$dropdown.outerHeight(!1),a=e.scrollTop(),l=e.scrollTop()+e.height(),c=ai.bottom+s,d={left:i.left,top:o.bottom},p=this.$dropdownParent;"static"===p.css("position")&&(p=p.offsetParent());var h={top:0,left:0};(f.contains(document.body,p[0])||p[0].isConnected)&&(h=p.offset()),d.top-=h.top,d.left-=h.left,t||n||(r="below"),u||!c||t?!c&&u&&t&&(r="below"):r="above",("above"==r||t&&"below"!==r)&&(d.top=o.top-h.top-s),null!=r&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+r),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+r)),this.$dropdownContainer.css(d)},e.prototype._resizeDropdown=function(){var e={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(e.minWidth=e.width,e.position="relative",e.width="auto"),this.$dropdown.css(e)},e.prototype._showDropdown=function(e){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},e}),e.define("select2/dropdown/minimumResultsForSearch",[],function(){function e(e,t,n,r){this.minimumResultsForSearch=n.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),e.call(this,t,n,r)}return e.prototype.showSearch=function(e,t){return!(function e(t){for(var n=0,r=0;r');return e.attr("dir",this.options.get("dir")),this.$container=e,this.$container.addClass("select2-container--"+this.options.get("theme")),u.StoreData(e[0],"element",this.$element),e},d}),e.define("jquery-mousewheel",["jquery"],function(e){return e}),e.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(i,e,o,t,s){if(null==i.fn.select2){var a=["open","close","destroy"];i.fn.select2=function(t){if("object"==typeof(t=t||{}))return this.each(function(){var e=i.extend(!0,{},t);new o(i(this),e)}),this;if("string"!=typeof t)throw new Error("Invalid arguments for Select2: "+t);var n,r=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=s.GetData(this,"select2");null==e&&window.console&&console.error&&console.error("The select2('"+t+"') method was called on an element that is not using Select2."),n=e[t].apply(e,r)}),-1table.widefat{border:none !important}#fs_sites .fs-scrollable-table .fs-main-column{width:100%}#fs_sites .fs-scrollable-table .fs-site-details td:first-of-type{text-align:right;color:grey;width:1px}#fs_sites .fs-scrollable-table .fs-site-details td:last-of-type{text-align:right}#fs_sites .fs-scrollable-table .fs-install-details table tr td{width:1px;white-space:nowrap}#fs_sites .fs-scrollable-table .fs-install-details table tr td:last-of-type{width:auto}#fs_addons h3{border:none;margin-bottom:0;padding:4px 5px}#fs_addons td{vertical-align:middle}#fs_addons thead{white-space:nowrap}#fs_addons td:first-child,#fs_addons th:first-child{text-align:left;font-weight:bold}#fs_addons td:last-child,#fs_addons th:last-child{text-align:right}#fs_addons th{font-weight:bold}#fs_billing_address{width:100%}#fs_billing_address tr td{width:50%;padding:5px}#fs_billing_address tr:first-of-type td{padding-top:0}#fs_billing_address span{font-weight:bold}#fs_billing_address input,#fs_billing_address select{display:block;width:100%;margin-top:5px}#fs_billing_address input::-moz-placeholder,#fs_billing_address select::-moz-placeholder{color:transparent;opacity:1}#fs_billing_address input:-ms-input-placeholder,#fs_billing_address select:-ms-input-placeholder{color:transparent}#fs_billing_address input::-webkit-input-placeholder,#fs_billing_address select::-webkit-input-placeholder{color:transparent}#fs_billing_address input.fs-read-mode,#fs_billing_address select.fs-read-mode{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode td span{display:none}#fs_billing_address.fs-read-mode input,#fs_billing_address.fs-read-mode select{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode input::-moz-placeholder,#fs_billing_address.fs-read-mode select::-moz-placeholder{color:#ccc;opacity:1}#fs_billing_address.fs-read-mode input:-ms-input-placeholder,#fs_billing_address.fs-read-mode select:-ms-input-placeholder{color:#ccc}#fs_billing_address.fs-read-mode input::-webkit-input-placeholder,#fs_billing_address.fs-read-mode select::-webkit-input-placeholder{color:#ccc}#fs_billing_address button{display:block;width:100%} +label.fs-tag,span.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}.fs-notice[data-id=license_not_whitelabeled].success,.fs-notice[data-id=license_whitelabeled].success{color:inherit;border-left-color:#00a0d2}.fs-notice[data-id=license_not_whitelabeled].success label.fs-plugin-title,.fs-notice[data-id=license_whitelabeled].success label.fs-plugin-title{display:none}#fs_account .postbox,#fs_account .widefat{max-width:800px}#fs_account h3{font-size:1.3em;padding:12px 15px;margin:0 0 12px 0;line-height:1.4;border-bottom:1px solid #f1f1f1}#fs_account h3 .dashicons{width:26px;height:26px;font-size:1.3em}#fs_account i.dashicons{font-size:1.2em;height:1.2em;width:1.2em}#fs_account .dashicons{vertical-align:middle}#fs_account .fs-header-actions{position:absolute;top:17px;right:15px;font-size:.9em}#fs_account .fs-header-actions ul{margin:0}#fs_account .fs-header-actions li{float:left}#fs_account .fs-header-actions li form{display:inline-block}#fs_account .fs-header-actions li a{text-decoration:none}#fs_account_details .button-group{float:right}.rtl #fs_account .fs-header-actions{left:15px;right:auto}.fs-key-value-table{width:100%}.fs-key-value-table form{display:inline-block}.fs-key-value-table tr td:first-child{text-align:right}.fs-key-value-table tr td:first-child nobr{font-weight:bold}.fs-key-value-table tr td:first-child form{display:block}.fs-key-value-table tr td.fs-right{text-align:right}.fs-key-value-table tr.fs-odd{background:#ebebeb}.fs-key-value-table td,.fs-key-value-table th{padding:10px}.fs-key-value-table code{line-height:28px}.fs-key-value-table var,.fs-key-value-table code,.fs-key-value-table input[type=text]{color:#0073aa;font-size:16px;background:none}.fs-key-value-table input[type=text]{width:100%;font-weight:bold}.fs-field-beta_program label{margin-left:7px}label.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error{background:#dc3232}#fs_sites .fs-scrollable-table .fs-table-body{max-height:200px;overflow:auto;border:1px solid #e5e5e5}#fs_sites .fs-scrollable-table .fs-table-body>table.widefat{border:none !important}#fs_sites .fs-scrollable-table .fs-main-column{width:100%}#fs_sites .fs-scrollable-table .fs-site-details td:first-of-type{text-align:right;color:gray;width:1px}#fs_sites .fs-scrollable-table .fs-site-details td:last-of-type{text-align:right}#fs_sites .fs-scrollable-table .fs-install-details table tr td{width:1px;white-space:nowrap}#fs_sites .fs-scrollable-table .fs-install-details table tr td:last-of-type{width:auto}#fs_addons h3{border:none;margin-bottom:0;padding:4px 5px}#fs_addons td{vertical-align:middle}#fs_addons thead{white-space:nowrap}#fs_addons td:first-child,#fs_addons th:first-child{text-align:left;font-weight:bold}#fs_addons td:last-child,#fs_addons th:last-child{text-align:right}#fs_addons th{font-weight:bold}#fs_billing_address{width:100%}#fs_billing_address tr td{width:50%;padding:5px}#fs_billing_address tr:first-of-type td{padding-top:0}#fs_billing_address span{font-weight:bold}#fs_billing_address input,#fs_billing_address select{display:block;width:100%;margin-top:5px}#fs_billing_address input::-moz-placeholder,#fs_billing_address select::-moz-placeholder{color:transparent;opacity:1}#fs_billing_address input:-ms-input-placeholder,#fs_billing_address select:-ms-input-placeholder{color:transparent}#fs_billing_address input::-webkit-input-placeholder,#fs_billing_address select::-webkit-input-placeholder{color:transparent}#fs_billing_address input.fs-read-mode,#fs_billing_address select.fs-read-mode{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode td span{display:none}#fs_billing_address.fs-read-mode input,#fs_billing_address.fs-read-mode select{border-color:transparent;color:#777;border-bottom:1px dashed #ccc;padding-left:0;background:none}#fs_billing_address.fs-read-mode input::-moz-placeholder,#fs_billing_address.fs-read-mode select::-moz-placeholder{color:#ccc;opacity:1}#fs_billing_address.fs-read-mode input:-ms-input-placeholder,#fs_billing_address.fs-read-mode select:-ms-input-placeholder{color:#ccc}#fs_billing_address.fs-read-mode input::-webkit-input-placeholder,#fs_billing_address.fs-read-mode select::-webkit-input-placeholder{color:#ccc}#fs_billing_address button{display:block;width:100%}@media screen and (max-width: 639px){#fs_account .fs-header-actions{position:static;padding:0 15px 12px 15px;margin:0 0 12px 0}#fs_account .fs-header-actions li{float:none;display:inline-block}#fs_account #fs_account_details{display:block}#fs_account #fs_account_details tbody,#fs_account #fs_account_details tr,#fs_account #fs_account_details td,#fs_account #fs_account_details th{display:block}#fs_account #fs_account_details tr td:first-child{text-align:left}#fs_account #fs_account_details tr td:nth-child(2){padding:0 12px}#fs_account #fs_account_details tr td:nth-child(2) code{margin:0;padding:0}#fs_account #fs_account_details tr td:nth-child(2) label{margin-left:0}#fs_account #fs_account_details tr td:nth-child(3){text-align:left}#fs_account #fs_account_details tr.fs-field-plan td:nth-child(2) .button-group{float:none;margin:12px 0}} diff --git a/freemius/assets/css/admin/add-ons.css b/freemius/assets/css/admin/add-ons.css index d2391c6..dd71813 100644 --- a/freemius/assets/css/admin/add-ons.css +++ b/freemius/assets/css/admin/add-ons.css @@ -1,2 +1 @@ -.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:white;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);box-shadow:0 2px 1px -1px rgba(0,0,0,0.3)}#fs_addons .fs-cards-list{list-style:none}#fs_addons .fs-cards-list .fs-card{float:left;height:152px;width:310px;padding:0;margin:0 0 30px 30px;font-size:14px;list-style:none;border:1px solid #ddd;cursor:pointer;position:relative}#fs_addons .fs-cards-list .fs-card .fs-overlay{position:absolute;left:0;right:0;bottom:0;top:0;z-index:9}#fs_addons .fs-cards-list .fs-card .fs-inner{background-color:#fff;overflow:hidden;height:100%;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner>ul{-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s;left:0;right:0;top:0;position:absolute}#fs_addons .fs-cards-list .fs-card .fs-inner>ul>li{list-style:none;line-height:18px;padding:0 15px;width:100%;display:block;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner{padding:0;margin:0;line-height:0;display:block;height:100px;background-repeat:repeat-x;background-size:100% 100%;-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner .fs-badge.fs-installed-addon-badge{font-size:1.02em;line-height:1.3em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-title{margin:10px 0 0 0;height:18px;overflow:hidden;color:#000;white-space:nowrap;text-overflow:ellipsis;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-offer{font-size:0.9em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-description{background-color:#f9f9f9;padding:10px 15px 100px 15px;border-top:1px solid #eee;margin:0 0 10px 0;color:#777}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-tag{position:absolute;top:10px;right:0px;background:greenyellow;display:block;padding:2px 10px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.3);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.3);box-shadow:1px 1px 1px rgba(0,0,0,0.3);text-transform:uppercase;font-size:0.9em;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button,#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button-group{position:absolute;top:112px;right:10px}@media screen and (min-width: 960px){#fs_addons .fs-cards-list .fs-card:hover .fs-overlay{border:2px solid #29abe1;margin-left:-1px;margin-top:-1px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner ul{top:-100px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-title,#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-offer{color:#29abe1}} -#TB_window,#TB_window iframe{width:821px !important}#plugin-information .fyi{width:266px !important}#plugin-information #section-holder{margin-right:299px}#plugin-information #section-description h2,#plugin-information #section-description h3,#plugin-information #section-description p,#plugin-information #section-description b,#plugin-information #section-description i,#plugin-information #section-description blockquote,#plugin-information #section-description li,#plugin-information #section-description ul,#plugin-information #section-description ol{clear:none}#plugin-information #section-description iframe{max-width:100%}#plugin-information #section-description .fs-selling-points{padding-bottom:10px;border-bottom:1px solid #ddd}#plugin-information #section-description .fs-selling-points ul{margin:0}#plugin-information #section-description .fs-selling-points ul li{padding:0;list-style:none outside none}#plugin-information #section-description .fs-selling-points ul li i.dashicons{color:#71ae00;font-size:3em;vertical-align:middle;line-height:30px;float:left;margin:0 0 0 -15px}#plugin-information #section-description .fs-selling-points ul li h3{margin:1em 30px !important}#plugin-information #section-description .fs-screenshots:after{content:"";display:table;clear:both}#plugin-information #section-description .fs-screenshots ul{list-style:none;margin:0}#plugin-information #section-description .fs-screenshots ul li{width:225px;height:225px;float:left;margin-bottom:20px;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}#plugin-information #section-description .fs-screenshots ul li a{display:block;width:100%;height:100%;border:1px solid;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);background-size:cover}#plugin-information #section-description .fs-screenshots ul li.odd{margin-right:20px}#plugin-information .plugin-information-pricing{margin:-16px;border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan h3{margin-top:0;padding:20px;font-size:16px}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper{border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab{cursor:pointer;position:relative;padding:0 10px;font-size:0.9em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab label{text-transform:uppercase;color:green;background:greenyellow;position:absolute;left:-1px;right:-1px;bottom:100%;border:1px solid darkgreen;padding:2px;text-align:center;font-size:0.9em;line-height:1em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab.nav-tab-active{cursor:default;background:#fffeec;border-bottom-color:#fffeec}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle h3{background:#fffeec;margin:0;padding-bottom:0;color:#0073aa}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .nav-tab-wrapper,#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .fs-billing-frequency{display:none}#plugin-information .plugin-information-pricing .fs-plan .fs-pricing-body{background:#fffeec;padding:20px}#plugin-information .plugin-information-pricing .fs-plan .button{width:100%;text-align:center;font-weight:bold;text-transform:uppercase;font-size:1.1em}#plugin-information .plugin-information-pricing .fs-plan label{white-space:nowrap}#plugin-information .plugin-information-pricing .fs-plan var{font-style:normal}#plugin-information .plugin-information-pricing .fs-plan .fs-billing-frequency,#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-align:center;display:block;font-weight:bold;margin-bottom:10px;text-transform:uppercase;background:#F3F3F3;padding:2px;border:1px solid #ccc}#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-transform:none;color:green;background:greenyellow}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms{font-size:0.9em}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms i{float:left;margin:0 0 0 -15px}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms li{margin:10px 0 0 0}#plugin-information #section-features .fs-features{margin:-20px -26px}#plugin-information #section-features table{width:100%;border-spacing:0;border-collapse:separate}#plugin-information #section-features table thead th{padding:10px 0}#plugin-information #section-features table thead .fs-price{color:#71ae00;font-weight:normal;display:block;text-align:center}#plugin-information #section-features table tbody td{border-top:1px solid #ccc;padding:10px 0;text-align:center;width:100px;color:#71ae00}#plugin-information #section-features table tbody td:first-child{text-align:left;width:auto;color:inherit;padding-left:26px}#plugin-information #section-features table tbody tr.fs-odd td{background:#fefefe}#plugin-information #section-features .dashicons-yes{width:30px;height:30px;font-size:30px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button,#plugin-information .fs-dropdown .button-group .button{position:relative;width:auto;top:0;right:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button:focus,#plugin-information .fs-dropdown .button-group .button:focus{z-index:10}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .fs-dropdown-arrow,#plugin-information .fs-dropdown .button-group .fs-dropdown-arrow{border-top:6px solid white;border-right:4px solid transparent;border-left:4px solid transparent;top:12px;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button){border-bottom-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button{border-bottom-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button){border-top-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active.up .fs-dropdown-arrow-button{border-top-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list,#plugin-information .fs-dropdown .fs-dropdown-list{position:absolute;right:-1px;top:100%;margin-left:auto;padding:3px 0;border:1px solid #bfbfbf;background-color:#fff;z-index:1;width:230px;text-align:left;-moz-box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);-webkit-box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);box-shadow:0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li,#plugin-information .fs-dropdown .fs-dropdown-list li{margin:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li a,#plugin-information .fs-dropdown .fs-dropdown-list li a{display:block;padding:5px 10px;text-decoration:none;text-shadow:none}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover,#plugin-information .fs-dropdown .fs-dropdown-list li:hover{background-color:#0074a3;color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover a,#plugin-information .fs-dropdown .fs-dropdown-list li:hover a{color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown:not(.up) .fs-dropdown-list,#plugin-information .fs-dropdown:not(.up) .fs-dropdown-list{-moz-border-radius:3px 0 3px 3px;-webkit-border-radius:3px 0 3px 3px;border-radius:3px 0 3px 3px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.up .fs-dropdown-list,#plugin-information .fs-dropdown.up .fs-dropdown-list{-moz-border-radius:3px 3px 0 3px;-webkit-border-radius:3px 3px 0 3px;border-radius:3px 3px 0 3px}#plugin-information .fs-dropdown .button-group{width:100%}#plugin-information .fs-dropdown .button-group .button{float:none;font-size:14px;font-weight:normal;text-transform:none}#plugin-information .fs-dropdown .fs-dropdown-list{margin-top:1px}#plugin-information .fs-dropdown.up .fs-dropdown-list{top:auto;bottom:100%;margin-bottom:2px}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group{text-align:center;display:table}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button{display:table-cell}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button:not(.fs-dropdown-arrow-button){left:1px;width:100%}#plugin-information-footer>.button,#plugin-information-footer .fs-dropdown{position:relative;top:3px}#plugin-information-footer>.button.left,#plugin-information-footer .fs-dropdown.left{float:left}#plugin-information-footer>.right,#plugin-information-footer .fs-dropdown{float:right}@media screen and (max-width: 961px){#fs_addons .fs-cards-list .fs-card{height:265px}} +.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:#fff;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.3);box-shadow:0 2px 1px -1px rgba(0,0,0,.3)}#fs_addons .fs-cards-list{list-style:none}#fs_addons .fs-cards-list .fs-card{float:left;height:152px;width:310px;padding:0;margin:0 0 30px 30px;font-size:14px;list-style:none;border:1px solid #ddd;cursor:pointer;position:relative}#fs_addons .fs-cards-list .fs-card .fs-overlay{position:absolute;left:0;right:0;bottom:0;top:0;z-index:9}#fs_addons .fs-cards-list .fs-card .fs-inner{background-color:#fff;overflow:hidden;height:100%;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner>ul{-moz-transition:all,.15s;-o-transition:all,.15s;-ms-transition:all,.15s;-webkit-transition:all,.15s;transition:all,.15s;left:0;right:0;top:0;position:absolute}#fs_addons .fs-cards-list .fs-card .fs-inner>ul>li{list-style:none;line-height:18px;padding:0 15px;width:100%;display:block;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner{padding:0;margin:0;line-height:0;display:block;height:100px;background-repeat:repeat-x;background-size:100% 100%;-moz-transition:all,.15s;-o-transition:all,.15s;-ms-transition:all,.15s;-webkit-transition:all,.15s;transition:all,.15s}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner .fs-badge.fs-installed-addon-badge{font-size:1.02em;line-height:1.3em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-title{margin:10px 0 0 0;height:18px;overflow:hidden;color:#000;white-space:nowrap;text-overflow:ellipsis;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-offer{font-size:.9em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-description{background-color:#f9f9f9;padding:10px 15px 100px 15px;border-top:1px solid #eee;margin:0 0 10px 0;color:#777}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-tag{position:absolute;top:10px;right:0px;background:#adff2f;display:block;padding:2px 10px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,.3);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,.3);box-shadow:1px 1px 1px rgba(0,0,0,.3);text-transform:uppercase;font-size:.9em;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button,#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button-group{position:absolute;top:112px;right:10px}@media screen and (min-width: 960px){#fs_addons .fs-cards-list .fs-card:hover .fs-overlay{border:2px solid #29abe1;margin-left:-1px;margin-top:-1px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner ul{top:-100px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-title,#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-offer{color:#29abe1}}#TB_window,#TB_window iframe{width:821px !important}#plugin-information .fyi{width:266px !important}#plugin-information #section-holder{margin-right:299px;clear:none}#plugin-information #section-description h2,#plugin-information #section-description h3,#plugin-information #section-description p,#plugin-information #section-description b,#plugin-information #section-description i,#plugin-information #section-description blockquote,#plugin-information #section-description li,#plugin-information #section-description ul,#plugin-information #section-description ol{clear:none}#plugin-information #section-description iframe{max-width:100%}#plugin-information #section-description .fs-selling-points{padding-bottom:10px;border-bottom:1px solid #ddd}#plugin-information #section-description .fs-selling-points ul{margin:0}#plugin-information #section-description .fs-selling-points ul li{padding:0;list-style:none outside none}#plugin-information #section-description .fs-selling-points ul li i.dashicons{color:#71ae00;font-size:3em;vertical-align:middle;line-height:30px;float:left;margin:0 0 0 -15px}#plugin-information #section-description .fs-selling-points ul li h3{margin:1em 30px !important}#plugin-information #section-description .fs-screenshots:after{content:"";display:table;clear:both}#plugin-information #section-description .fs-screenshots ul{list-style:none;margin:0}#plugin-information #section-description .fs-screenshots ul li{width:225px;height:225px;float:left;margin-bottom:20px;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}#plugin-information #section-description .fs-screenshots ul li a{display:block;width:100%;height:100%;border:1px solid;-moz-box-shadow:1px 1px 1px rgba(0,0,0,.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,.2);box-shadow:1px 1px 1px rgba(0,0,0,.2);background-size:cover}#plugin-information #section-description .fs-screenshots ul li.odd{margin-right:20px}#plugin-information .plugin-information-pricing{margin:-16px;border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan h3{margin-top:0;padding:20px;font-size:16px}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper{border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab{cursor:pointer;position:relative;padding:0 10px;font-size:.9em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab label{text-transform:uppercase;color:green;background:#adff2f;position:absolute;left:-1px;right:-1px;bottom:100%;border:1px solid #006400;padding:2px;text-align:center;font-size:.9em;line-height:1em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab.nav-tab-active{cursor:default;background:#fffeec;border-bottom-color:#fffeec}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle h3{background:#fffeec;margin:0;padding-bottom:0;color:#0073aa}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .nav-tab-wrapper,#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .fs-billing-frequency{display:none}#plugin-information .plugin-information-pricing .fs-plan .fs-pricing-body{background:#fffeec;padding:20px}#plugin-information .plugin-information-pricing .fs-plan .button{width:100%;text-align:center;font-weight:bold;text-transform:uppercase;font-size:1.1em}#plugin-information .plugin-information-pricing .fs-plan label{white-space:nowrap}#plugin-information .plugin-information-pricing .fs-plan var{font-style:normal}#plugin-information .plugin-information-pricing .fs-plan .fs-billing-frequency,#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-align:center;display:block;font-weight:bold;margin-bottom:10px;text-transform:uppercase;background:#f3f3f3;padding:2px;border:1px solid #ccc}#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-transform:none;color:green;background:#adff2f}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms{font-size:.9em}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms i{float:left;margin:0 0 0 -15px}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms li{margin:10px 0 0 0}#plugin-information #section-features .fs-features{margin:-20px -26px}#plugin-information #section-features table{width:100%;border-spacing:0;border-collapse:separate}#plugin-information #section-features table thead th{padding:10px 0}#plugin-information #section-features table thead .fs-price{color:#71ae00;font-weight:normal;display:block;text-align:center}#plugin-information #section-features table tbody td{border-top:1px solid #ccc;padding:10px 0;text-align:center;width:100px;color:#71ae00}#plugin-information #section-features table tbody td:first-child{text-align:left;width:auto;color:inherit;padding-left:26px}#plugin-information #section-features table tbody tr.fs-odd td{background:#fefefe}#plugin-information #section-features .dashicons-yes{width:30px;height:30px;font-size:30px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button,#plugin-information .fs-dropdown .button-group .button{position:relative;width:auto;top:0;right:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .button:focus,#plugin-information .fs-dropdown .button-group .button:focus{z-index:10}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .button-group .fs-dropdown-arrow,#plugin-information .fs-dropdown .button-group .fs-dropdown-arrow{border-top:6px solid #fff;border-right:4px solid transparent;border-left:4px solid transparent;top:12px;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active:not(.up) .button:not(.fs-dropdown-arrow-button){border-bottom-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active:not(.up) .fs-dropdown-arrow-button{border-bottom-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button),#plugin-information .fs-dropdown.active.up .button:not(.fs-dropdown-arrow-button){border-top-left-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.active.up .fs-dropdown-arrow-button,#plugin-information .fs-dropdown.active.up .fs-dropdown-arrow-button{border-top-right-radius:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list,#plugin-information .fs-dropdown .fs-dropdown-list{position:absolute;right:-1px;top:100%;margin-left:auto;padding:3px 0;border:1px solid #bfbfbf;background-color:#fff;z-index:1;width:230px;text-align:left;-moz-box-shadow:0px 2px 4px -1px rgba(0,0,0,.2),0px 4px 5px 0px rgba(0,0,0,.14),0px 1px 10px 0px rgba(0,0,0,.12);-webkit-box-shadow:0px 2px 4px -1px rgba(0,0,0,.2),0px 4px 5px 0px rgba(0,0,0,.14),0px 1px 10px 0px rgba(0,0,0,.12);box-shadow:0px 2px 4px -1px rgba(0,0,0,.2),0px 4px 5px 0px rgba(0,0,0,.14),0px 1px 10px 0px rgba(0,0,0,.12)}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li,#plugin-information .fs-dropdown .fs-dropdown-list li{margin:0}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li a,#plugin-information .fs-dropdown .fs-dropdown-list li a{display:block;padding:5px 10px;text-decoration:none;text-shadow:none}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover,#plugin-information .fs-dropdown .fs-dropdown-list li:hover{background-color:#0074a3;color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown .fs-dropdown-list li:hover a,#plugin-information .fs-dropdown .fs-dropdown-list li:hover a{color:#fff}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown:not(.up) .fs-dropdown-list,#plugin-information .fs-dropdown:not(.up) .fs-dropdown-list{-moz-border-radius:3px 0 3px 3px;-webkit-border-radius:3px 0 3px 3px;border-radius:3px 0 3px 3px}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-dropdown.up .fs-dropdown-list,#plugin-information .fs-dropdown.up .fs-dropdown-list{-moz-border-radius:3px 3px 0 3px;-webkit-border-radius:3px 3px 0 3px;border-radius:3px 3px 0 3px}#plugin-information .fs-dropdown .button-group{width:100%}#plugin-information .fs-dropdown .button-group .button{float:none;font-size:14px;font-weight:normal;text-transform:none}#plugin-information .fs-dropdown .fs-dropdown-list{margin-top:1px}#plugin-information .fs-dropdown.up .fs-dropdown-list{top:auto;bottom:100%;margin-bottom:2px}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group{text-align:center;display:table}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button{display:table-cell}#plugin-information.wp-core-ui .fs-pricing-body .fs-dropdown .button-group .button:not(.fs-dropdown-arrow-button){left:1px;width:100%}#plugin-information-footer>.button,#plugin-information-footer .fs-dropdown{position:relative;top:3px}#plugin-information-footer>.button.left,#plugin-information-footer .fs-dropdown.left{float:left}#plugin-information-footer>.right,#plugin-information-footer .fs-dropdown{float:right}@media screen and (max-width: 961px){#fs_addons .fs-cards-list .fs-card{height:265px}} diff --git a/freemius/assets/css/admin/affiliation.css b/freemius/assets/css/admin/affiliation.css index 003ca37..aae31ed 100644 --- a/freemius/assets/css/admin/affiliation.css +++ b/freemius/assets/css/admin/affiliation.css @@ -1 +1 @@ -@charset "UTF-8";#fs_affiliation_content_wrapper #messages{margin-top:25px}#fs_affiliation_content_wrapper h3{font-size:24px;padding:0;margin-left:0}#fs_affiliation_content_wrapper ul li{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;list-style-type:none}#fs_affiliation_content_wrapper ul li:before{content:'✓';margin-right:10px;font-weight:bold}#fs_affiliation_content_wrapper p:not(.description),#fs_affiliation_content_wrapper li,#fs_affiliation_content_wrapper label{font-size:16px !important;line-height:26px !important}#fs_affiliation_content_wrapper .button{margin-top:20px;margin-bottom:7px;line-height:35px;height:40px;font-size:16px}#fs_affiliation_content_wrapper .button#cancel_button{margin-right:5px}#fs_affiliation_content_wrapper form .input-container{margin-bottom:15px}#fs_affiliation_content_wrapper form .input-container .input-label{font-weight:bold;display:block;width:100%}#fs_affiliation_content_wrapper form .input-container.input-container-text label,#fs_affiliation_content_wrapper form .input-container.input-container-text input,#fs_affiliation_content_wrapper form .input-container.input-container-text textarea{display:block}#fs_affiliation_content_wrapper form .input-container #add_domain,#fs_affiliation_content_wrapper form .input-container .remove-domain{text-decoration:none;display:inline-block;margin-top:3px}#fs_affiliation_content_wrapper form .input-container #add_domain:focus,#fs_affiliation_content_wrapper form .input-container .remove-domain:focus{box-shadow:none}#fs_affiliation_content_wrapper form .input-container #add_domain.disabled,#fs_affiliation_content_wrapper form .input-container .remove-domain.disabled{color:#aaa;cursor:default}#fs_affiliation_content_wrapper form #extra_domains_container .description{margin-top:0;position:relative;top:-4px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container{margin-bottom:15px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container .domain{display:inline-block;margin-right:5px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container .domain:last-of-type{margin-bottom:0} +#fs_affiliation_content_wrapper #messages{margin-top:25px}#fs_affiliation_content_wrapper h3{font-size:24px;padding:0;margin-left:0}#fs_affiliation_content_wrapper ul li{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;list-style-type:none}#fs_affiliation_content_wrapper ul li:before{content:"✓";margin-right:10px;font-weight:bold}#fs_affiliation_content_wrapper p:not(.description),#fs_affiliation_content_wrapper li,#fs_affiliation_content_wrapper label{font-size:16px !important;line-height:26px !important}#fs_affiliation_content_wrapper .button{margin-top:20px;margin-bottom:7px;line-height:35px;height:40px;font-size:16px}#fs_affiliation_content_wrapper .button#cancel_button{margin-right:5px}#fs_affiliation_content_wrapper form .input-container{margin-bottom:15px}#fs_affiliation_content_wrapper form .input-container .input-label{font-weight:bold;display:block;width:100%}#fs_affiliation_content_wrapper form .input-container.input-container-text label,#fs_affiliation_content_wrapper form .input-container.input-container-text input,#fs_affiliation_content_wrapper form .input-container.input-container-text textarea{display:block}#fs_affiliation_content_wrapper form .input-container #add_domain,#fs_affiliation_content_wrapper form .input-container .remove-domain{text-decoration:none;display:inline-block;margin-top:3px}#fs_affiliation_content_wrapper form .input-container #add_domain:focus,#fs_affiliation_content_wrapper form .input-container .remove-domain:focus{box-shadow:none}#fs_affiliation_content_wrapper form .input-container #add_domain.disabled,#fs_affiliation_content_wrapper form .input-container .remove-domain.disabled{color:#aaa;cursor:default}#fs_affiliation_content_wrapper form #extra_domains_container .description{margin-top:0;position:relative;top:-4px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container{margin-bottom:15px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container .domain{display:inline-block;margin-right:5px}#fs_affiliation_content_wrapper form #extra_domains_container .extra-domain-input-container .domain:last-of-type{margin-bottom:0} diff --git a/freemius/assets/css/admin/clone-resolution.css b/freemius/assets/css/admin/clone-resolution.css new file mode 100644 index 0000000..8f48326 --- /dev/null +++ b/freemius/assets/css/admin/clone-resolution.css @@ -0,0 +1 @@ +.fs-notice[data-id^=clone_resolution_options_notice]{padding:0;color:inherit !important}.fs-notice[data-id^=clone_resolution_options_notice] .fs-notice-body{padding:0;margin-bottom:0}.fs-notice[data-id^=clone_resolution_options_notice] .fs-notice-header{padding:5px 10px}.fs-notice[data-id^=clone_resolution_options_notice] ol{margin-top:0;margin-bottom:0}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-options-container{display:flex;flex-direction:row;padding:0 10px 10px}@media(max-width: 750px){.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-options-container{flex-direction:column}}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-option{border:1px solid #ccc;padding:10px 10px 15px 10px;flex:auto;margin:5px}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-option:first-child{margin-left:0}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-option:last-child{margin-right:0}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-resolution-option strong{font-size:1.2em;padding:2px;line-height:1.5em}.fs-notice[data-id^=clone_resolution_options_notice] a{text-decoration:none}.fs-notice[data-id^=clone_resolution_options_notice] .button{margin-right:10px}.rtl .fs-notice[data-id^=clone_resolution_options_notice] .button{margin-right:0;margin-left:10px}.fs-notice[data-id^=clone_resolution_options_notice] .fs-clone-documentation-container{padding:0 10px 15px}.fs-notice[data-id=temporary_duplicate_notice] #fs_clone_resolution_error_message{border:1px solid #d3135a;background:#fee;color:#d3135a;padding:10px}.fs-notice[data-id=temporary_duplicate_notice] ol{margin-top:0} diff --git a/freemius/assets/css/admin/common.css b/freemius/assets/css/admin/common.css index d96aa2f..900103a 100644 --- a/freemius/assets/css/admin/common.css +++ b/freemius/assets/css/admin/common.css @@ -1,2 +1 @@ -.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:white;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,0.3);box-shadow:0 2px 1px -1px rgba(0,0,0,0.3)}.theme-browser .theme .fs-premium-theme-badge-container{position:absolute;right:0;top:0}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge{position:relative;top:0;margin-top:10px;text-align:center}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-premium-theme-badge{font-size:1.1em}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-beta-theme-badge{background:#00a0d2}.fs-switch{position:relative;display:inline-block;color:#ccc;text-shadow:0 1px 1px rgba(255,255,255,0.8);height:18px;padding:6px 6px 5px 6px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);background:#ececec;box-shadow:0 0 4px rgba(0,0,0,0.1),inset 0 1px 3px 0 rgba(0,0,0,0.1);cursor:pointer}.fs-switch span{display:inline-block;width:35px;text-transform:uppercase}.fs-switch .fs-toggle{position:absolute;top:1px;width:37px;height:25px;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.3);border-radius:4px;background:#fff;background-color:#fff;background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ececec), color-stop(1, #fff));background-image:-webkit-linear-gradient(top, #ececec, #fff);background-image:-moz-linear-gradient(top, #ececec, #fff);background-image:-ms-linear-gradient(top, #ececec, #fff);background-image:-o-linear-gradient(top, #ececec, #fff);background-image:linear-gradient(top, bottom, #ececec, #fff);box-shadow:inset 0 1px 0 0 rgba(255,255,255,0.5);z-index:999;-moz-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-o-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-ms-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);-webkit-transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1);transition:0.4s cubic-bezier(0.54, 1.6, 0.5, 1)}.fs-switch.fs-off .fs-toggle{left:2%}.fs-switch.fs-on .fs-toggle{left:54%}.fs-switch.fs-round{top:8px;padding:4px 25px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round .fs-toggle{top:0;width:24px;height:24px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round.fs-off .fs-toggle{left:-1px}.fs-switch.fs-round.fs-on{background:#0085ba}.fs-switch.fs-round.fs-on .fs-toggle{left:25px}.fs-switch.fs-small.fs-round{padding:1px 19px}.fs-switch.fs-small.fs-round .fs-toggle{top:0;width:18px;height:18px;-moz-border-radius:18px;-webkit-border-radius:18px;border-radius:18px}.fs-switch.fs-small.fs-round.fs-on .fs-toggle{left:19px}.fs-switch-feedback{margin-left:10px}.fs-switch-feedback.success{color:#71ae00}.rtl .fs-switch-feedback{margin-left:0;margin-right:10px}#fs_frame{line-height:0;font-size:0}.fs-full-size-wrapper{margin:40px 0 -65px -20px}@media (max-width: 600px){.fs-full-size-wrapper{margin:0 0 -65px -10px}} -.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,0.3);color:#fff;padding:2px 10px;position:absolute;top:100%;bottom:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}div.fs-notice.updated,div.fs-notice.success,div.fs-notice.promotion{display:block !important}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;-moz-box-shadow:0 2px 2px rgba(6,113,6,0.3);-webkit-box-shadow:0 2px 2px rgba(6,113,6,0.3);box-shadow:0 2px 2px rgba(6,113,6,0.3);opacity:0.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}.fs-secure-notice a.fs-security-proof{color:green;text-decoration:none}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 600px){.fs-secure-notice{display:none}}@media screen and (max-width: 1250px){#fs_promo_tab{display:none}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:'\21B3';padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:'\21B2'}.fs-submenu-item.pricing.upgrade-mode{color:greenyellow}.fs-submenu-item.pricing.trial-mode{color:#83e2ff}#adminmenu .update-plugins.fs-trial{background-color:#00b9eb}.fs-ajax-spinner{border:0;width:20px;height:20px;margin-right:5px;vertical-align:sub;display:inline-block;background:url("/wp-admin/images/wpspin_light-2x.gif");background-size:contain;margin-bottom:-2px}.wrap.fs-section h2{text-align:left}.plugins p.fs-upgrade-notice{border:0;background-color:#d54e21;padding:10px;color:#f9f9f9;margin-top:10px} +.fs-badge{position:absolute;top:10px;right:0;background:#71ae00;color:#fff;text-transform:uppercase;padding:5px 10px;-moz-border-radius:3px 0 0 3px;-webkit-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;font-weight:bold;border-right:0;-moz-box-shadow:0 2px 1px -1px rgba(0,0,0,.3);-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.3);box-shadow:0 2px 1px -1px rgba(0,0,0,.3)}.theme-browser .theme .fs-premium-theme-badge-container{position:absolute;right:0;top:0}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge{position:relative;top:0;margin-top:10px;text-align:center}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-premium-theme-badge{font-size:1.1em}.theme-browser .theme .fs-premium-theme-badge-container .fs-badge.fs-beta-theme-badge{background:#00a0d2}.fs-switch{position:relative;display:inline-block;color:#ccc;text-shadow:0 1px 1px rgba(255,255,255,.8);height:18px;padding:6px 6px 5px 6px;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);background:#ececec;box-shadow:0 0 4px rgba(0,0,0,.1),inset 0 1px 3px 0 rgba(0,0,0,.1);cursor:pointer}.fs-switch span{display:inline-block;width:35px;text-transform:uppercase}.fs-switch .fs-toggle{position:absolute;top:1px;width:37px;height:25px;border:1px solid #ccc;border:1px solid rgba(0,0,0,.3);border-radius:4px;background:#fff;background-color:#fff;background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ececec), color-stop(1, #fff));background-image:-webkit-linear-gradient(top, #ececec, #fff);background-image:-moz-linear-gradient(top, #ececec, #fff);background-image:-ms-linear-gradient(top, #ececec, #fff);background-image:-o-linear-gradient(top, #ececec, #fff);background-image:linear-gradient(top, bottom, #ececec, #fff);box-shadow:inset 0 1px 0 0 rgba(255,255,255,.5);z-index:999;-moz-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);-o-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);-ms-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);-webkit-transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1);transition:.4s cubic-bezier(0.54, 1.6, 0.5, 1)}.fs-switch.fs-off .fs-toggle{left:2%}.fs-switch.fs-on .fs-toggle{left:54%}.fs-switch.fs-round{top:8px;padding:4px 25px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round .fs-toggle{top:0;width:24px;height:24px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px}.fs-switch.fs-round.fs-off .fs-toggle{left:-1px}.fs-switch.fs-round.fs-on{background:#0085ba}.fs-switch.fs-round.fs-on .fs-toggle{left:25px}.fs-switch.fs-small.fs-round{padding:1px 19px}.fs-switch.fs-small.fs-round .fs-toggle{top:0;width:18px;height:18px;-moz-border-radius:18px;-webkit-border-radius:18px;border-radius:18px}.fs-switch.fs-small.fs-round.fs-on .fs-toggle{left:19px}body.fs-loading,body.fs-loading *{cursor:wait !important}#fs_frame{line-height:0;font-size:0}.fs-full-size-wrapper{margin:40px 0 -65px -20px}@media(max-width: 600px){.fs-full-size-wrapper{margin:0 0 -65px -10px}}.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,.3);color:#fff;padding:2px 10px;position:absolute;top:100%;bottom:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}div.fs-notice.updated,div.fs-notice.success,div.fs-notice.promotion{display:block !important}#fs_connect .fs-error ol,#fs_connect .fs-error .fs-api-request-error-show-details-link,#fs_connect .fs-error .fs-api-request-error-details,.fs-modal .notice-error ol,.fs-modal .notice-error .fs-api-request-error-show-details-link,.fs-modal .notice-error .fs-api-request-error-details,.fs-notice.error ol,.fs-notice.error .fs-api-request-error-show-details-link,.fs-notice.error .fs-api-request-error-details{text-align:left}#fs_connect .fs-error ol,.fs-modal .notice-error ol,.fs-notice.error ol{list-style-type:disc}#fs_connect .fs-error .fs-api-request-error-show-details-link,.fs-modal .notice-error .fs-api-request-error-show-details-link,.fs-notice.error .fs-api-request-error-show-details-link{text-decoration:none;color:#2271b1;box-shadow:none}#fs_connect .fs-error .fs-api-request-error-details,.fs-modal .notice-error .fs-api-request-error-details,.fs-notice.error .fs-api-request-error-details{border:1px solid #ccc;padding:5px;overflow:auto;max-height:150px}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;-moz-box-shadow:0 2px 2px rgba(6,113,6,.3);-webkit-box-shadow:0 2px 2px rgba(6,113,6,.3);box-shadow:0 2px 2px rgba(6,113,6,.3);opacity:.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}.fs-secure-notice a.fs-security-proof{color:green;text-decoration:none}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 600px){.fs-secure-notice{display:none}}@media screen and (max-width: 1250px){#fs_promo_tab{display:none}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:"↳";padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:"↲"}.fs-submenu-item.pricing.upgrade-mode{color:#adff2f}.fs-submenu-item.pricing.trial-mode{color:#83e2ff}#adminmenu .update-plugins.fs-trial{background-color:#00b9eb}.fs-ajax-spinner{border:0;width:20px;height:20px;margin-right:5px;vertical-align:sub;display:inline-block;background:url("/wp-admin/images/wpspin_light-2x.gif");background-size:contain;margin-bottom:-2px}.wrap.fs-section h2{text-align:left}.plugins p.fs-upgrade-notice{border:0;background-color:#d54e21;padding:10px;color:#f9f9f9;margin-top:10px} diff --git a/freemius/assets/css/admin/connect.css b/freemius/assets/css/admin/connect.css index dff7c49..47a75d8 100644 --- a/freemius/assets/css/admin/connect.css +++ b/freemius/assets/css/admin/connect.css @@ -1 +1 @@ -#fs_connect{width:480px;-moz-box-shadow:0px 1px 2px rgba(0,0,0,0.3);-webkit-box-shadow:0px 1px 2px rgba(0,0,0,0.3);box-shadow:0px 1px 2px rgba(0,0,0,0.3);margin:20px 0}@media screen and (max-width: 479px){#fs_connect{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;width:auto;margin:0 0 0 -10px}}#fs_connect .fs-content{background:#fff;padding:15px 20px}#fs_connect .fs-content .fs-error{background:snow;color:#d3135a;border:1px solid #d3135a;-moz-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);-webkit-box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 1px 0 rgba(0,0,0,0.1);text-align:center;padding:5px;margin-bottom:10px}#fs_connect .fs-content p{margin:0;padding:0;font-size:1.2em}#fs_connect .fs-license-key-container{position:relative;width:280px;margin:10px auto 0 auto}#fs_connect .fs-license-key-container input{width:100%}#fs_connect .fs-license-key-container .dashicons{position:absolute;top:5px;right:5px}#fs_connect.require-license-key .fs-sites-list-container td{cursor:pointer}#fs_connect #delegate_to_site_admins{margin-right:15px;float:right;height:26px;vertical-align:middle;line-height:37px;font-weight:bold;border-bottom:1px dashed;text-decoration:none}#fs_connect #delegate_to_site_admins.rtl{margin-left:15px;margin-right:0}#fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}#fs_connect .fs-actions .button{padding:0 10px 1px;line-height:35px;height:37px;font-size:16px;margin-bottom:0}#fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}#fs_connect .fs-actions .button.button-primary{padding-right:15px;padding-left:15px}#fs_connect .fs-actions .button.button-primary:after{content:' \279C'}#fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}#fs_connect .fs-actions .button.button-secondary{float:right}#fs_connect.fs-anonymous-disabled .fs-actions .button.button-primary{width:100%}#fs_connect .fs-permissions{padding:10px 20px;background:#FEFEFE;-moz-transition:background 0.5s ease;-o-transition:background 0.5s ease;-ms-transition:background 0.5s ease;-webkit-transition:background 0.5s ease;transition:background 0.5s ease}#fs_connect .fs-permissions .fs-license-sync-disclaimer{text-align:center;margin-top:0}#fs_connect .fs-permissions>.fs-trigger{font-size:0.9em;text-decoration:none;text-align:center;display:block}#fs_connect .fs-permissions ul{height:0;overflow:hidden;margin:0}#fs_connect .fs-permissions ul li{margin-bottom:12px}#fs_connect .fs-permissions ul li:last-child{margin-bottom:0}#fs_connect .fs-permissions ul li>i.dashicons{float:left;font-size:40px;width:40px;height:40px}#fs_connect .fs-permissions ul li .fs-switch{float:right}#fs_connect .fs-permissions ul li .fs-permission-description{margin-left:55px}#fs_connect .fs-permissions ul li .fs-permission-description span{font-weight:bold;text-transform:uppercase;color:#23282d}#fs_connect .fs-permissions ul li .fs-permission-description p{margin:2px 0 0 0}#fs_connect .fs-permissions.fs-open{background:#fff}#fs_connect .fs-permissions.fs-open ul{overflow:initial;height:auto;margin:20px 20px 10px 20px}@media screen and (max-width: 479px){#fs_connect .fs-permissions{background:#fff}#fs_connect .fs-permissions .fs-trigger{display:none}#fs_connect .fs-permissions ul{height:auto;margin:20px}}#fs_connect .fs-freemium-licensing{padding:8px;background:#777;color:#fff}#fs_connect .fs-freemium-licensing p{text-align:center;display:block;margin:0;padding:0}#fs_connect .fs-freemium-licensing a{color:#C2EEFF;text-decoration:underline}#fs_connect .fs-visual{padding:12px;line-height:0;background:#fafafa;height:80px;position:relative}#fs_connect .fs-visual .fs-site-icon{position:absolute;left:20px;top:10px}#fs_connect .fs-visual .fs-connect-logo{position:absolute;right:20px;top:10px}#fs_connect .fs-visual .fs-plugin-icon{position:absolute;top:10px;left:50%;margin-left:-40px}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-site-icon,#fs_connect .fs-visual img,#fs_connect .fs-visual object{width:80px;height:80px}#fs_connect .fs-visual .dashicons-wordpress{font-size:64px;background:#01749a;color:#fff;width:64px;height:64px;padding:8px}#fs_connect .fs-visual .dashicons-plus{position:absolute;top:50%;font-size:30px;margin-top:-10px;color:#bbb}#fs_connect .fs-visual .dashicons-plus.fs-first{left:28%}#fs_connect .fs-visual .dashicons-plus.fs-second{left:65%}#fs_connect .fs-visual .fs-plugin-icon,#fs_connect .fs-visual .fs-connect-logo,#fs_connect .fs-visual .fs-site-icon{border:1px solid #ccc;padding:1px;background:#fff}#fs_connect .fs-terms{text-align:center;font-size:0.85em;padding:5px;background:rgba(0,0,0,0.05)}#fs_connect .fs-terms,#fs_connect .fs-terms a{color:#999}#fs_connect .fs-terms a{text-decoration:none}.fs-multisite-options-container{margin-top:10px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .fs-tooltip{opacity:0;visibility:hidden;-moz-transition:opacity 0.3s ease-in-out;-o-transition:opacity 0.3s ease-in-out;-ms-transition:opacity 0.3s ease-in-out;-webkit-transition:opacity 0.3s ease-in-out;transition:opacity 0.3s ease-in-out;position:absolute;background:rgba(0,0,0,0.8);color:#fff !important;font-family:'arial', serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:-17px;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);line-height:1.3em;font-weight:bold;text-align:left;text-transform:none !important}.rtl .fs-tooltip-trigger .fs-tooltip{text-align:right;left:auto;right:-17px}.fs-tooltip-trigger .fs-tooltip::after{content:' ';display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:rgba(0,0,0,0.8) transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl .fs-tooltip-trigger .fs-tooltip::after{right:21px;left:auto}.fs-tooltip-trigger:hover .fs-tooltip{visibility:visible;opacity:1}#fs_marketing_optin{display:none;margin-top:10px;border:1px solid #ccc;padding:10px;line-height:1.5em}#fs_marketing_optin .fs-message{display:block;margin-bottom:5px;font-size:1.05em;font-weight:600}#fs_marketing_optin.error{border:1px solid #d3135a;background:#fee}#fs_marketing_optin.error .fs-message{color:#d3135a}#fs_marketing_optin .fs-input-container{margin-top:5px}#fs_marketing_optin .fs-input-container label{margin-top:5px;display:block}#fs_marketing_optin .fs-input-container label input{float:left;margin:1px 0 0 0}#fs_marketing_optin .fs-input-container label:first-child{display:block;margin-bottom:2px}#fs_marketing_optin .fs-input-label{display:block;margin-left:20px}#fs_marketing_optin .fs-input-label .underlined{text-decoration:underline}.rtl #fs_marketing_optin .fs-input-container label input{float:right}.rtl #fs_marketing_optin .fs-input-label{margin-left:0;margin-right:20px}.rtl #fs_connect .fs-actions{padding:10px 20px;background:#C0C7CA}.rtl #fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}.rtl #fs_connect .fs-actions .button.button-primary:after{content:' \000bb'}.rtl #fs_connect .fs-actions .button.button-primary.fs-loading:after{content:''}.rtl #fs_connect .fs-actions .button.button-secondary{float:left}.rtl #fs_connect .fs-permissions ul li .fs-permission-description{margin-right:55px;margin-left:0}.rtl #fs_connect .fs-permissions ul li .fs-switch{float:left}.rtl #fs_connect .fs-permissions ul li i.dashicons{float:right}.rtl #fs_connect .fs-visual .fs-site-icon{right:20px;left:auto}.rtl #fs_connect .fs-visual .fs-connect-logo{right:auto;left:20px}#fs_theme_connect_wrapper{position:fixed;top:0;height:100%;width:100%;z-index:99990;background:rgba(0,0,0,0.75);text-align:center;overflow-y:auto}#fs_theme_connect_wrapper:before{content:"";display:inline-block;vertical-align:middle;height:100%}#fs_theme_connect_wrapper>button.close{color:white;cursor:pointer;height:40px;width:40px;position:absolute;right:0;border:0;background-color:transparent;top:32px}#fs_theme_connect_wrapper #fs_connect{top:0;text-align:left;display:inline-block;vertical-align:middle;margin-top:52px;margin-bottom:20px}#fs_theme_connect_wrapper #fs_connect .fs-terms{background:rgba(140,140,140,0.64)}#fs_theme_connect_wrapper #fs_connect .fs-terms,#fs_theme_connect_wrapper #fs_connect .fs-terms a{color:#c5c5c5}.wp-pointer-content #fs_connect{margin:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.fs-opt-in-pointer .wp-pointer-content{padding:0}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow{border-bottom-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow-inner{border-bottom-color:#fafafa}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow{border-top-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow-inner{border-top-color:#fafafa}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow{border-right-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow-inner{border-right-color:#fafafa}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow{border-left-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow-inner{border-left-color:#fafafa}#license_issues_link{display:block;text-align:center;font-size:0.9em;margin-top:10px} +.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .dashicons{float:none !important}.fs-tooltip-trigger .fs-tooltip{opacity:0;visibility:hidden;-moz-transition:opacity .3s ease-in-out;-o-transition:opacity .3s ease-in-out;-ms-transition:opacity .3s ease-in-out;-webkit-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out;position:absolute;background:rgba(0,0,0,.8);color:#fff !important;font-family:"arial",serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:-17px;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,.2);box-shadow:1px 1px 1px rgba(0,0,0,.2);line-height:1.3em;font-weight:bold;text-align:left;text-transform:none !important}.rtl .fs-tooltip-trigger .fs-tooltip{text-align:right;left:auto;right:-17px}.fs-tooltip-trigger .fs-tooltip::after{content:" ";display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:rgba(0,0,0,.8) transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl .fs-tooltip-trigger .fs-tooltip::after{right:21px;left:auto}.fs-tooltip-trigger:hover .fs-tooltip{visibility:visible;opacity:1}#fs_connect{width:484px;margin:60px auto 20px auto}#fs_connect a{color:inherit}#fs_connect a:not(.button){text-decoration:underline}#fs_connect .fs-box-container{box-shadow:0 1px 2px rgba(0,0,0,.3);border-radius:3px;overflow:hidden;padding-top:40px;background:#f0f0f1}@media screen and (max-width: 483px){#fs_connect{width:auto;margin:30px 0 0 -10px}#fs_connect .fs-box-container{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}}#fs_connect .fs-content{background:#fff;padding:30px 20px}#fs_connect .fs-content .fs-error{background:snow;color:#d3135a;border:1px solid #d3135a;-moz-box-shadow:0 1px 1px 0 rgba(0,0,0,.1);-webkit-box-shadow:0 1px 1px 0 rgba(0,0,0,.1);box-shadow:0 1px 1px 0 rgba(0,0,0,.1);text-align:center;padding:5px;margin-bottom:10px}#fs_connect .fs-content h2{line-height:1.5em}#fs_connect .fs-content p{margin:0;padding:0;font-size:1.2em}#fs_connect .fs-license-key-container{position:relative;width:280px;margin:10px auto 0 auto}#fs_connect .fs-license-key-container input{width:100%}#fs_connect .fs-license-key-container .dashicons{position:absolute;top:5px;right:5px}#fs_connect.require-license-key .fs-content{padding-bottom:10px}#fs_connect.require-license-key .fs-actions{border-top:none}#fs_connect.require-license-key .fs-sites-list-container td{cursor:pointer}#fs_connect #delegate_to_site_admins{margin-right:15px;float:right;height:26px;vertical-align:middle;line-height:37px;font-weight:bold;border-bottom:1px dashed;text-decoration:none}#fs_connect #delegate_to_site_admins.rtl{margin-left:15px;margin-right:0}#fs_connect .fs-actions{padding:10px 20px;background:#fff;border-width:1px 0;border-style:solid;border-color:#f1f1f1}#fs_connect .fs-actions .button{padding:0 10px 1px;line-height:35px;height:37px;font-size:16px;margin-bottom:0}#fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}#fs_connect .fs-actions .button.button-primary{padding-right:15px;padding-left:15px}#fs_connect .fs-actions .button.button-primary:after{content:" ➜"}#fs_connect .fs-actions .button.button-primary.fs-loading:after{content:""}#fs_connect .fs-actions .button.button-secondary{float:right}#fs_connect.fs-anonymous-disabled .fs-actions .button.button-primary{width:100%}#fs_connect .fs-permissions{padding:10px 20px;background:#fff;-moz-transition:background .5s ease;-o-transition:background .5s ease;-ms-transition:background .5s ease;-webkit-transition:background .5s ease;transition:background .5s ease}#fs_connect .fs-permissions .fs-license-sync-disclaimer{text-align:center;margin-top:0}#fs_connect .fs-permissions>.fs-trigger{font-size:.9em;text-decoration:none;text-align:center;display:block}#fs_connect .fs-permissions>.fs-trigger .fs-arrow::after{content:"→";width:20px;display:inline-block}#fs_connect .fs-permissions.fs-open>.fs-trigger .fs-arrow::after{content:"↓" !important}#fs_connect .fs-permissions ul li{padding-left:0;padding-right:0}@media screen and (max-width: 483px){#fs_connect .fs-permissions ul{height:auto;margin:20px}}#fs_connect .fs-freemium-licensing{padding:8px;background:#777;color:#fff}#fs_connect .fs-freemium-licensing p{text-align:center;display:block;margin:0;padding:0}#fs_connect .fs-freemium-licensing a{color:inherit;text-decoration:underline}#fs_connect .fs-header{padding:0;line-height:0;height:0;position:relative}#fs_connect .fs-header .fs-site-icon,#fs_connect .fs-header .fs-connect-logo{position:absolute;top:-8px;border-radius:50%}#fs_connect .fs-header .fs-site-icon{left:152px}#fs_connect .fs-header .fs-connect-logo{right:152px}#fs_connect .fs-header .fs-site-icon,#fs_connect .fs-header img,#fs_connect .fs-header object{width:50px;height:50px;border-radius:50%}#fs_connect .fs-header .fs-plugin-icon{position:absolute;overflow:hidden;top:-23px;left:50%;margin-left:-44px;border-radius:50%;z-index:1}#fs_connect .fs-header .fs-plugin-icon,#fs_connect .fs-header .fs-plugin-icon img{width:80px;height:80px}#fs_connect .fs-header .dashicons-wordpress-alt{font-size:40px;background:#01749a;color:#fff;width:40px;height:40px;padding:5px;border-radius:50%}#fs_connect .fs-header .dashicons-plus{position:absolute;top:50%;font-size:30px;margin-top:-10px;color:#bbb}#fs_connect .fs-header .dashicons-plus.fs-first{left:28%}#fs_connect .fs-header .dashicons-plus.fs-second{left:65%}#fs_connect .fs-header .fs-plugin-icon,#fs_connect .fs-header .fs-connect-logo,#fs_connect .fs-header .fs-site-icon{border:1px solid #efefef;padding:3px;background:#fff}#fs_connect .fs-terms{text-align:center;font-size:.85em;padding:10px 5px}#fs_connect .fs-terms,#fs_connect .fs-terms a{color:#999}#fs_connect .fs-terms a{text-decoration:none}.fs-multisite-options-container{margin-top:20px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}#fs_marketing_optin{display:none;margin-top:10px;border:1px solid #ccc;padding:10px;line-height:1.5em}#fs_marketing_optin .fs-message{display:block;margin-bottom:5px;font-size:1.05em;font-weight:600}#fs_marketing_optin.error{border:1px solid #d3135a;background:#fee}#fs_marketing_optin.error .fs-message{color:#d3135a}#fs_marketing_optin .fs-input-container{margin-top:5px}#fs_marketing_optin .fs-input-container label{margin-top:5px;display:block}#fs_marketing_optin .fs-input-container label input{float:left;margin:1px 0 0 0}#fs_marketing_optin .fs-input-container label:first-child{display:block;margin-bottom:2px}#fs_marketing_optin .fs-input-label{display:block;margin-left:20px}#fs_marketing_optin .fs-input-label .underlined{text-decoration:underline}.rtl #fs_marketing_optin .fs-input-container label input{float:right}.rtl #fs_marketing_optin .fs-input-label{margin-left:0;margin-right:20px}.rtl #fs_connect{border-radius:3px}.rtl #fs_connect .fs-actions{padding:10px 20px;background:#c0c7ca}.rtl #fs_connect .fs-actions .button .dashicons{font-size:37px;margin-left:-8px;margin-right:12px}.rtl #fs_connect .fs-actions .button.button-primary:after{content:" »"}.rtl #fs_connect .fs-actions .button.button-primary.fs-loading:after{content:""}.rtl #fs_connect .fs-actions .button.button-secondary{float:left}.rtl #fs_connect .fs-header .fs-site-icon{right:20px;left:auto}.rtl #fs_connect .fs-header .fs-connect-logo{right:auto;left:20px}.rtl #fs_connect .fs-permissions>.fs-trigger .fs-arrow::after{content:"←"}#fs_theme_connect_wrapper{position:fixed;top:0;height:100%;width:100%;z-index:99990;background:rgba(0,0,0,.75);text-align:center;overflow-y:auto}#fs_theme_connect_wrapper:before{content:"";display:inline-block;vertical-align:middle;height:100%}#fs_theme_connect_wrapper>button.close{color:#fff;cursor:pointer;height:40px;width:40px;position:absolute;right:0;border:0;background-color:transparent;top:32px}#fs_theme_connect_wrapper #fs_connect{top:0;text-align:left;display:inline-block;vertical-align:middle;margin-top:0;margin-bottom:20px}#fs_theme_connect_wrapper #fs_connect .fs-terms,#fs_theme_connect_wrapper #fs_connect .fs-terms a{color:#c5c5c5}.wp-pointer-content #fs_connect{margin:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.fs-opt-in-pointer .wp-pointer-content{padding:0}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow{border-bottom-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-top .wp-pointer-arrow-inner{border-bottom-color:#fafafa}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow{border-top-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-bottom .wp-pointer-arrow-inner{border-top-color:#fafafa}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow{border-right-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-left .wp-pointer-arrow-inner{border-right-color:#fafafa}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow{border-left-color:#dfdfdf}.fs-opt-in-pointer.wp-pointer-right .wp-pointer-arrow-inner{border-left-color:#fafafa}#license_issues_link{display:block;text-align:center;font-size:.9em;margin-top:10px}.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .dashicons{float:none !important}.fs-tooltip-trigger .fs-tooltip{opacity:0;visibility:hidden;-moz-transition:opacity .3s ease-in-out;-o-transition:opacity .3s ease-in-out;-ms-transition:opacity .3s ease-in-out;-webkit-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out;position:absolute;background:rgba(0,0,0,.8);color:#fff !important;font-family:"arial",serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:-17px;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,.2);box-shadow:1px 1px 1px rgba(0,0,0,.2);line-height:1.3em;font-weight:bold;text-align:left;text-transform:none !important}.rtl .fs-tooltip-trigger .fs-tooltip{text-align:right;left:auto;right:-17px}.fs-tooltip-trigger .fs-tooltip::after{content:" ";display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:rgba(0,0,0,.8) transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl .fs-tooltip-trigger .fs-tooltip::after{right:21px;left:auto}.fs-tooltip-trigger:hover .fs-tooltip{visibility:visible;opacity:1}.fs-permissions .fs-permission.fs-disabled{color:#aaa}.fs-permissions .fs-permission.fs-disabled .fs-permission-description span{color:#aaa}.fs-permissions .fs-permission .fs-switch-feedback{position:absolute;right:15px;top:52px}.fs-permissions ul{height:0;overflow:hidden;margin:0}.fs-permissions ul li{padding:17px 15px;margin:0;position:relative}.fs-permissions ul li>i.dashicons{float:left;font-size:30px;width:30px;height:30px;padding:5px}.fs-permissions ul li .fs-switch{float:right}.fs-permissions ul li .fs-permission-description{margin-left:55px}.fs-permissions ul li .fs-permission-description span{font-size:14px;font-weight:500;color:#23282d}.fs-permissions ul li .fs-permission-description .fs-tooltip{font-size:13px;font-weight:bold}.fs-permissions ul li .fs-permission-description .fs-tooltip-trigger .dashicons{margin:-1px 2px 0 2px}.fs-permissions ul li .fs-permission-description p{margin:2px 0 0 0}.fs-permissions.fs-open{background:#fff}.fs-permissions.fs-open ul{overflow:initial;height:auto;margin:20px 0 10px 0}.fs-permissions .fs-switch-feedback .fs-ajax-spinner{margin-right:10px}.fs-permissions .fs-switch-feedback.success{color:#71ae00}.rtl .fs-permissions .fs-switch-feedback{right:auto;left:15px}.rtl .fs-permissions .fs-switch-feedback .fs-ajax-spinner{margin-left:10px;margin-right:0}.rtl .fs-permissions ul li .fs-permission-description{margin-right:55px;margin-left:0}.rtl .fs-permissions ul li .fs-switch{float:left}.rtl .fs-permissions ul li i.dashicons{float:right} diff --git a/freemius/assets/css/admin/debug.css b/freemius/assets/css/admin/debug.css index 4b30d84..3a2aadf 100644 --- a/freemius/assets/css/admin/debug.css +++ b/freemius/assets/css/admin/debug.css @@ -1 +1 @@ -.fs-switch-label{font-size:20px;line-height:31px;margin:0 5px}#fs_log_book table{font-family:Consolas,Monaco,monospace;font-size:12px}#fs_log_book table th{color:#ccc}#fs_log_book table tr{background:#232525}#fs_log_book table tr.alternate{background:#2b2b2b}#fs_log_book table tr td.fs-col--logger{color:#5a7435}#fs_log_book table tr td.fs-col--type{color:#ffc861}#fs_log_book table tr td.fs-col--function{color:#a7b7b1;font-weight:bold}#fs_log_book table tr td.fs-col--message,#fs_log_book table tr td.fs-col--message a{color:#9a73ac !important}#fs_log_book table tr td.fs-col--file{color:#d07922}#fs_log_book table tr td.fs-col--timestamp{color:#6596be} +label.fs-tag,span.fs-tag{background:#ffba00;color:#fff;display:inline-block;border-radius:3px;padding:5px;font-size:11px;line-height:11px;vertical-align:baseline}label.fs-tag.fs-warn,span.fs-tag.fs-warn{background:#ffba00}label.fs-tag.fs-info,span.fs-tag.fs-info{background:#00a0d2}label.fs-tag.fs-success,span.fs-tag.fs-success{background:#46b450}label.fs-tag.fs-error,span.fs-tag.fs-error{background:#dc3232}.fs-switch-label{font-size:20px;line-height:31px;margin:0 5px}#fs_log_book table{font-family:Consolas,Monaco,monospace;font-size:12px}#fs_log_book table th{color:#ccc}#fs_log_book table tr{background:#232525}#fs_log_book table tr.alternate{background:#2b2b2b}#fs_log_book table tr td.fs-col--logger{color:#5a7435}#fs_log_book table tr td.fs-col--type{color:#ffc861}#fs_log_book table tr td.fs-col--function{color:#a7b7b1;font-weight:bold}#fs_log_book table tr td.fs-col--message,#fs_log_book table tr td.fs-col--message a{color:#9a73ac !important}#fs_log_book table tr td.fs-col--file{color:#d07922}#fs_log_book table tr td.fs-col--timestamp{color:#6596be} diff --git a/freemius/assets/css/admin/dialog-boxes.css b/freemius/assets/css/admin/dialog-boxes.css index 434f346..de7f829 100644 --- a/freemius/assets/css/admin/dialog-boxes.css +++ b/freemius/assets/css/admin/dialog-boxes.css @@ -1,2 +1 @@ -.fs-modal{position:fixed;overflow:auto;height:100%;width:100%;top:0;z-index:100000;display:none;background:rgba(0,0,0,0.6)}.fs-modal .dashicons{vertical-align:middle}.fs-modal .fs-modal-dialog{background:transparent;position:absolute;left:50%;margin-left:-298px;padding-bottom:30px;top:-100%;z-index:100001;width:596px}@media (max-width: 650px){.fs-modal .fs-modal-dialog{margin-left:-50%;box-sizing:border-box;padding-left:10px;padding-right:10px;width:100%}.fs-modal .fs-modal-dialog .fs-modal-panel>h3>strong{font-size:1.3em}}.fs-modal.active{display:block}.fs-modal.active:before{display:block}.fs-modal.active .fs-modal-dialog{top:10%}.fs-modal.fs-success .fs-modal-header{border-bottom-color:#46b450}.fs-modal.fs-success .fs-modal-body{background-color:#f7fff7}.fs-modal.fs-warn .fs-modal-header{border-bottom-color:#ffb900}.fs-modal.fs-warn .fs-modal-body{background-color:#fff8e5}.fs-modal.fs-error .fs-modal-header{border-bottom-color:#dc3232}.fs-modal.fs-error .fs-modal-body{background-color:#ffeaea}.fs-modal .fs-modal-body,.fs-modal .fs-modal-footer{border:0;background:#fefefe;padding:20px}.fs-modal .fs-modal-header{border-bottom:#eeeeee solid 1px;background:#fbfbfb;padding:15px 20px;position:relative;margin-bottom:-10px}.fs-modal .fs-modal-header h4{margin:0;padding:0;text-transform:uppercase;font-size:1.2em;font-weight:bold;color:#cacaca;text-shadow:1px 1px 1px #fff;letter-spacing:0.6px;-webkit-font-smoothing:antialiased}.fs-modal .fs-modal-header .fs-close{position:absolute;right:10px;top:12px;cursor:pointer;color:#bbb;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;padding:3px;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;-ms-transition:all 0.2s ease-in-out;-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.fs-modal .fs-modal-header .fs-close:hover{color:#fff;background:#aaa}.fs-modal .fs-modal-header .fs-close .dashicons,.fs-modal .fs-modal-header .fs-close:hover .dashicons{text-decoration:none}.fs-modal .fs-modal-body{border-bottom:0}.fs-modal .fs-modal-body p{font-size:14px}.fs-modal .fs-modal-body h2{font-size:20px;line-height:1.5em}.fs-modal .fs-modal-body>div{margin-top:10px}.fs-modal .fs-modal-body>div h2{font-weight:bold;font-size:20px;margin-top:0}.fs-modal .fs-modal-footer{border-top:#eeeeee solid 1px;text-align:right}.fs-modal .fs-modal-footer>.button{margin:0 7px}.fs-modal .fs-modal-footer>.button:first-child{margin:0}.fs-modal .fs-modal-panel>.notice.inline{margin:0;display:none}.fs-modal .fs-modal-panel:not(.active){display:none}.rtl .fs-modal .fs-modal-header .fs-close{right:auto;left:20px}body.has-fs-modal{overflow:hidden}.fs-modal.fs-modal-deactivation-feedback .reason-input,.fs-modal.fs-modal-deactivation-feedback .internal-message{margin:3px 0 3px 22px}.fs-modal.fs-modal-deactivation-feedback .reason-input input,.fs-modal.fs-modal-deactivation-feedback .reason-input textarea,.fs-modal.fs-modal-deactivation-feedback .internal-message input,.fs-modal.fs-modal-deactivation-feedback .internal-message textarea{width:100%}.fs-modal.fs-modal-deactivation-feedback li.reason.has-internal-message .internal-message{border:1px solid #ccc;padding:7px;display:none}@media (max-width: 650px){.fs-modal.fs-modal-deactivation-feedback li.reason li.reason{margin-bottom:10px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .reason-input,.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .internal-message{margin-left:29px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label{display:table}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label>span{display:table-cell;font-size:1.3em}}.fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label{float:left}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel{margin-top:0 !important}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel h3{margin-top:0;line-height:1.5em}#the-list .deactivate>.fs-slug{display:none}.fs-modal.fs-modal-subscription-cancellation .fs-price-increase-warning{color:red;font-weight:bold;padding:0 25px;margin-bottom:0}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:left;top:5px;position:relative}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:right}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{display:block;margin-left:24px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{margin-left:0;margin-right:24px}.fs-modal.fs-modal-license-activation .fs-modal-body input.fs-license-key{width:100%}.fs-license-options-container table,.fs-license-options-container table select,.fs-license-options-container table .fs-available-license-key{width:100%}.fs-license-options-container table td:first-child{width:1%}.fs-license-options-container table .fs-other-license-key-container label{position:relative;top:6px;float:left;margin-right:5px}.fs-license-options-container table .fs-other-license-key-container div{overflow:hidden;width:auto;height:30px;display:block;top:2px;position:relative}.fs-license-options-container table .fs-other-license-key-container div input{margin:0}.fs-sites-list-container td{cursor:pointer}.fs-modal.fs-modal-user-change .fs-modal-body input#fs_other_email_address{width:100%}.fs-user-change-options-container table{width:100%;border-collapse:collapse}.fs-user-change-options-container table tr{display:block;margin-bottom:2px}.fs-user-change-options-container table .fs-email-address-container td{display:inline-block}.fs-user-change-options-container table .fs-email-address-container input[type="radio"]{margin-bottom:0;margin-top:0}.fs-user-change-options-container table .fs-other-email-address-container{width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div{display:table;width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div label,.fs-user-change-options-container table .fs-other-email-address-container>div>div{display:table-cell}.fs-user-change-options-container table .fs-other-email-address-container>div label{width:1%;padding-left:3px;padding-right:3px}.fs-user-change-options-container table .fs-other-email-address-container>div>div{width:auto}.fs-user-change-options-container table .fs-other-email-address-container>div>div input{width:100%}.fs-modal.fs-modal-developer-license-debug-mode .fs-modal-body input.fs-license-or-user-key{width:100%}.fs-multisite-options-container{margin-top:10px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-modal.fs-modal-license-key-resend .email-address-container{overflow:hidden;padding-right:2px}.fs-modal.fs-modal-license-key-resend.fs-freemium input.email-address{width:300px}.fs-modal.fs-modal-license-key-resend.fs-freemium label{display:block;margin-bottom:10px}.fs-modal.fs-modal-license-key-resend.fs-premium input.email-address{width:100%}.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{float:right;margin-left:7px}@media (max-width: 650px){.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{margin-top:2px}} -.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .input-container>.email-address-container{padding-left:2px;padding-right:0}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .button-container{float:left;margin-right:7px;margin-left:0}a.show-license-resend-modal{margin-top:4px;display:inline-block}.fs-ajax-loader{position:relative;width:170px;height:20px;margin:auto}.fs-ajax-loader .fs-ajax-loader-bar{position:absolute;top:0;background-color:#0074a3;width:20px;height:20px;-webkit-animation-name:bounce_ajaxLoader;-moz-animation-name:bounce_ajaxLoader;-ms-animation-name:bounce_ajaxLoader;-o-animation-name:bounce_ajaxLoader;animation-name:bounce_ajaxLoader;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s;animation-duration:1.5s;animation-iteration-count:infinite;-o-animation-iteration-count:infinite;-ms-animation-iteration-count:infinite;-webkit-animation-iteration-count:infinite;-moz-animation-iteration-count:infinite;-webkit-animation-direction:normal;-moz-animation-direction:normal;-ms-animation-direction:normal;-o-animation-direction:normal;animation-direction:normal;-moz-transform:0.3;-o-transform:0.3;-ms-transform:0.3;-webkit-transform:0.3;transform:0.3}.fs-ajax-loader .fs-ajax-loader-bar-1{left:0px;animation-delay:0.6s;-o-animation-delay:0.6s;-ms-animation-delay:0.6s;-webkit-animation-delay:0.6s;-moz-animation-delay:0.6s}.fs-ajax-loader .fs-ajax-loader-bar-2{left:19px;animation-delay:0.75s;-o-animation-delay:0.75s;-ms-animation-delay:0.75s;-webkit-animation-delay:0.75s;-moz-animation-delay:0.75s}.fs-ajax-loader .fs-ajax-loader-bar-3{left:38px;animation-delay:0.9s;-o-animation-delay:0.9s;-ms-animation-delay:0.9s;-webkit-animation-delay:0.9s;-moz-animation-delay:0.9s}.fs-ajax-loader .fs-ajax-loader-bar-4{left:57px;animation-delay:1.05s;-o-animation-delay:1.05s;-ms-animation-delay:1.05s;-webkit-animation-delay:1.05s;-moz-animation-delay:1.05s}.fs-ajax-loader .fs-ajax-loader-bar-5{left:76px;animation-delay:1.2s;-o-animation-delay:1.2s;-ms-animation-delay:1.2s;-webkit-animation-delay:1.2s;-moz-animation-delay:1.2s}.fs-ajax-loader .fs-ajax-loader-bar-6{left:95px;animation-delay:1.35s;-o-animation-delay:1.35s;-ms-animation-delay:1.35s;-webkit-animation-delay:1.35s;-moz-animation-delay:1.35s}.fs-ajax-loader .fs-ajax-loader-bar-7{left:114px;animation-delay:1.5s;-o-animation-delay:1.5s;-ms-animation-delay:1.5s;-webkit-animation-delay:1.5s;-moz-animation-delay:1.5s}.fs-ajax-loader .fs-ajax-loader-bar-8{left:133px;animation-delay:1.65s;-o-animation-delay:1.65s;-ms-animation-delay:1.65s;-webkit-animation-delay:1.65s;-moz-animation-delay:1.65s}@-moz-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-ms-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-o-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-webkit-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}.fs-modal-auto-install #request-filesystem-credentials-form h2,.fs-modal-auto-install #request-filesystem-credentials-form .request-filesystem-credentials-action-buttons{display:none}.fs-modal-auto-install #request-filesystem-credentials-form input[type=password],.fs-modal-auto-install #request-filesystem-credentials-form input[type=email],.fs-modal-auto-install #request-filesystem-credentials-form input[type=text]{-webkit-appearance:none;padding:10px 10px 5px 10px;width:300px;max-width:100%}.fs-modal-auto-install #request-filesystem-credentials-form>div,.fs-modal-auto-install #request-filesystem-credentials-form label,.fs-modal-auto-install #request-filesystem-credentials-form fieldset{width:300px;max-width:100%;margin:0 auto;display:block}.button-primary.warn{box-shadow:0 1px 0 #d2593c;text-shadow:0 -1px 1px #d2593c,1px 0 1px #d2593c,0 1px 1px #d2593c,-1px 0 1px #d2593c;background:#f56a48;border-color:#ec6544 #d2593c #d2593c}.button-primary.warn:hover{background:#fd6d4a;border-color:#d2593c}.button-primary.warn:focus{box-shadow:0 1px 0 #dd6041,0 0 2px 1px #e4a796}.button-primary.warn:active{background:#dd6041;border-color:#d2593c;box-shadow:inset 0 2px 0 #d2593c}.button-primary.warn.disabled{color:#f5b3a1 !important;background:#e76444 !important;border-color:#d85e40 !important;text-shadow:0 -1px 0 rgba(0,0,0,0.1) !important} +.fs-modal{position:fixed;overflow:auto;height:100%;width:100%;top:0;z-index:100000;display:none;background:rgba(0,0,0,.6)}@media(min-width: 961px){.fs-modal{padding-left:160px}.rtl .fs-modal{padding-left:0;padding-right:160px}}.fs-modal .dashicons{vertical-align:middle}.fs-modal .fs-modal-dialog{background:transparent;position:absolute;left:50%;margin-left:-298px;padding-bottom:30px;top:-100%;z-index:100001;width:596px}@media(max-width: 650px){.fs-modal .fs-modal-dialog{margin-left:-50%;box-sizing:border-box;padding-left:10px;padding-right:10px;width:100%}.fs-modal .fs-modal-dialog .fs-modal-panel>h3>strong{font-size:1.3em}}.fs-modal.active{display:block}.fs-modal.active:before{display:block}.fs-modal.active .fs-modal-dialog{top:10%}.fs-modal.fs-success .fs-modal-header{border-bottom-color:#46b450}.fs-modal.fs-success .fs-modal-body{background-color:#f7fff7}.fs-modal.fs-warn .fs-modal-header{border-bottom-color:#ffb900}.fs-modal.fs-warn .fs-modal-body{background-color:#fff8e5}.fs-modal.fs-error .fs-modal-header{border-bottom-color:#dc3232}.fs-modal.fs-error .fs-modal-body{background-color:#ffeaea}.fs-modal .fs-modal-body,.fs-modal .fs-modal-footer{border:0;background:#fefefe;padding:20px}.fs-modal .fs-modal-header{border-bottom:#eee solid 1px;background:#fbfbfb;padding:15px 20px;position:relative;margin-bottom:-10px}.fs-modal .fs-modal-header h4{margin:0;padding:0;text-transform:uppercase;font-size:1.2em;font-weight:bold;color:#cacaca;text-shadow:1px 1px 1px #fff;letter-spacing:.6px;-webkit-font-smoothing:antialiased}.fs-modal .fs-modal-header .fs-close{position:absolute;right:10px;top:12px;cursor:pointer;color:#bbb;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;padding:3px;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.fs-modal .fs-modal-header .fs-close:hover{color:#fff;background:#aaa}.fs-modal .fs-modal-header .fs-close .dashicons,.fs-modal .fs-modal-header .fs-close:hover .dashicons{text-decoration:none}.fs-modal .fs-modal-body{border-bottom:0}.fs-modal .fs-modal-body p{font-size:14px}.fs-modal .fs-modal-body h2{font-size:20px;line-height:1.5em}.fs-modal .fs-modal-body>div{margin-top:10px}.fs-modal .fs-modal-body>div h2{font-weight:bold;font-size:20px;margin-top:0}.fs-modal .fs-modal-footer{border-top:#eee solid 1px;text-align:right}.fs-modal .fs-modal-footer>.button{margin:0 7px}.fs-modal .fs-modal-footer>.button:last-of-type{margin:0}.fs-modal .fs-modal-panel>.notice.inline{margin:0;display:none}.fs-modal .fs-modal-panel:not(.active){display:none}.rtl .fs-modal .fs-modal-header .fs-close{right:auto;left:20px}.rtl .fs-modal .fs-modal-footer{text-align:left}body.has-fs-modal{overflow:hidden}.fs-modal.fs-modal-deactivation-feedback .reason-input,.fs-modal.fs-modal-deactivation-feedback .internal-message{margin:3px 0 3px 22px}.fs-modal.fs-modal-deactivation-feedback .reason-input input,.fs-modal.fs-modal-deactivation-feedback .reason-input textarea,.fs-modal.fs-modal-deactivation-feedback .internal-message input,.fs-modal.fs-modal-deactivation-feedback .internal-message textarea{width:100%}.fs-modal.fs-modal-deactivation-feedback li.reason.has-internal-message .internal-message{border:1px solid #ccc;padding:7px;display:none}@media(max-width: 650px){.fs-modal.fs-modal-deactivation-feedback li.reason li.reason{margin-bottom:10px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .reason-input,.fs-modal.fs-modal-deactivation-feedback li.reason li.reason .internal-message{margin-left:29px}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label{display:table}.fs-modal.fs-modal-deactivation-feedback li.reason li.reason label>span{display:table-cell;font-size:1.3em}}.fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label,.fs-modal.fs-modal-deactivation-feedback .feedback-from-snooze-label{float:left;line-height:30px}.rtl .fs-modal.fs-modal-deactivation-feedback .anonymous-feedback-label,.rtl .fs-modal.fs-modal-deactivation-feedback .feedback-from-snooze-label{float:right}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel{margin-top:0 !important}.fs-modal.fs-modal-deactivation-feedback .fs-modal-panel h3{margin-top:0;line-height:1.5em}#the-list .deactivate>.fs-slug{display:none}.fs-modal.fs-modal-subscription-cancellation .fs-price-increase-warning{color:red;font-weight:bold;padding:0 25px;margin-bottom:0}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:left;top:5px;position:relative}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label input{float:right}.fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{display:block;margin-left:24px}.rtl .fs-modal.fs-modal-subscription-cancellation ul.subscription-actions label span{margin-left:0;margin-right:24px}.fs-modal.fs-modal-license-activation .fs-modal-body input.fs-license-key{width:100%}.fs-license-options-container table,.fs-license-options-container table select,.fs-license-options-container table .fs-available-license-key{width:100%}.fs-license-options-container table td:first-child{width:1%}.fs-license-options-container table .fs-other-license-key-container label{position:relative;top:6px;float:left;margin-right:5px}.fs-license-options-container table .fs-other-license-key-container div{overflow:hidden;width:auto;height:30px;display:block;top:2px;position:relative}.fs-license-options-container table .fs-other-license-key-container div input{margin:0}.fs-sites-list-container td{cursor:pointer}.fs-modal.fs-modal-user-change .fs-modal-body input#fs_other_email_address{width:100%}.fs-user-change-options-container table{width:100%;border-collapse:collapse}.fs-user-change-options-container table tr{display:block;margin-bottom:2px}.fs-user-change-options-container table .fs-email-address-container td{display:inline-block}.fs-user-change-options-container table .fs-email-address-container input[type=radio]{margin-bottom:0;margin-top:0}.fs-user-change-options-container table .fs-other-email-address-container{width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div{display:table;width:100%}.fs-user-change-options-container table .fs-other-email-address-container>div label,.fs-user-change-options-container table .fs-other-email-address-container>div>div{display:table-cell}.fs-user-change-options-container table .fs-other-email-address-container>div label{width:1%;padding-left:3px;padding-right:3px}.fs-user-change-options-container table .fs-other-email-address-container>div>div{width:auto}.fs-user-change-options-container table .fs-other-email-address-container>div>div input{width:100%}.fs-modal.fs-modal-developer-license-debug-mode .fs-modal-body input.fs-license-or-user-key{width:100%}.fs-multisite-options-container{margin-top:20px;border:1px solid #ccc;padding:5px}.fs-multisite-options-container a{text-decoration:none}.fs-multisite-options-container a:focus{box-shadow:none}.fs-multisite-options-container a.selected{font-weight:bold}.fs-multisite-options-container.fs-apply-on-all-sites{border:0 none;padding:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options{border-spacing:0}.fs-multisite-options-container.fs-apply-on-all-sites .fs-all-sites-options td:not(:first-child){display:none}.fs-multisite-options-container .fs-sites-list-container{display:none;overflow:auto}.fs-multisite-options-container .fs-sites-list-container table td{border-top:1px solid #ccc;padding:4px 2px}.fs-modal.fs-modal-license-key-resend .email-address-container{overflow:hidden;padding-right:2px}.fs-modal.fs-modal-license-key-resend.fs-freemium input.email-address{width:300px}.fs-modal.fs-modal-license-key-resend.fs-freemium label{display:block;margin-bottom:10px}.fs-modal.fs-modal-license-key-resend.fs-premium input.email-address{width:100%}.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{float:right;margin-left:7px}@media(max-width: 650px){.fs-modal.fs-modal-license-key-resend.fs-premium .button-container{margin-top:2px}}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .input-container>.email-address-container{padding-left:2px;padding-right:0}.rtl .fs-modal.fs-modal-license-key-resend .fs-modal-body .button-container{float:left;margin-right:7px;margin-left:0}a.show-license-resend-modal{margin-top:4px;display:inline-block}.fs-modal.fs-modal-email-address-update .fs-modal-body input[type=text]{width:100%}.fs-modal.fs-modal-email-address-update p{margin-bottom:0}.fs-modal.fs-modal-email-address-update ul{margin:1em .5em}.fs-modal.fs-modal-email-address-update ul li label span{float:left;margin-top:0}.fs-modal.fs-modal-email-address-update ul li label span:last-child{display:block;float:none;margin-left:20px}.fs-ajax-loader{position:relative;width:170px;height:20px;margin:auto}.fs-ajax-loader .fs-ajax-loader-bar{position:absolute;top:0;background-color:#0074a3;width:20px;height:20px;-webkit-animation-name:bounce_ajaxLoader;-moz-animation-name:bounce_ajaxLoader;-ms-animation-name:bounce_ajaxLoader;-o-animation-name:bounce_ajaxLoader;animation-name:bounce_ajaxLoader;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s;animation-duration:1.5s;animation-iteration-count:infinite;-o-animation-iteration-count:infinite;-ms-animation-iteration-count:infinite;-webkit-animation-iteration-count:infinite;-moz-animation-iteration-count:infinite;-webkit-animation-direction:normal;-moz-animation-direction:normal;-ms-animation-direction:normal;-o-animation-direction:normal;animation-direction:normal;-moz-transform:.3;-o-transform:.3;-ms-transform:.3;-webkit-transform:.3;transform:.3}.fs-ajax-loader .fs-ajax-loader-bar-1{left:0px;animation-delay:0.6s;-o-animation-delay:0.6s;-ms-animation-delay:0.6s;-webkit-animation-delay:0.6s;-moz-animation-delay:0.6s}.fs-ajax-loader .fs-ajax-loader-bar-2{left:19px;animation-delay:0.75s;-o-animation-delay:0.75s;-ms-animation-delay:0.75s;-webkit-animation-delay:0.75s;-moz-animation-delay:0.75s}.fs-ajax-loader .fs-ajax-loader-bar-3{left:38px;animation-delay:0.9s;-o-animation-delay:0.9s;-ms-animation-delay:0.9s;-webkit-animation-delay:0.9s;-moz-animation-delay:0.9s}.fs-ajax-loader .fs-ajax-loader-bar-4{left:57px;animation-delay:1.05s;-o-animation-delay:1.05s;-ms-animation-delay:1.05s;-webkit-animation-delay:1.05s;-moz-animation-delay:1.05s}.fs-ajax-loader .fs-ajax-loader-bar-5{left:76px;animation-delay:1.2s;-o-animation-delay:1.2s;-ms-animation-delay:1.2s;-webkit-animation-delay:1.2s;-moz-animation-delay:1.2s}.fs-ajax-loader .fs-ajax-loader-bar-6{left:95px;animation-delay:1.35s;-o-animation-delay:1.35s;-ms-animation-delay:1.35s;-webkit-animation-delay:1.35s;-moz-animation-delay:1.35s}.fs-ajax-loader .fs-ajax-loader-bar-7{left:114px;animation-delay:1.5s;-o-animation-delay:1.5s;-ms-animation-delay:1.5s;-webkit-animation-delay:1.5s;-moz-animation-delay:1.5s}.fs-ajax-loader .fs-ajax-loader-bar-8{left:133px;animation-delay:1.65s;-o-animation-delay:1.65s;-ms-animation-delay:1.65s;-webkit-animation-delay:1.65s;-moz-animation-delay:1.65s}@-moz-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-ms-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-o-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@-webkit-keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}@keyframes bounce_ajaxLoader{0%{-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);-webkit-transform:scale(1);transform:scale(1);background-color:#0074a3}100%{-moz-transform:scale(0.3);-o-transform:scale(0.3);-ms-transform:scale(0.3);-webkit-transform:scale(0.3);transform:scale(0.3);background-color:#fff}}.fs-modal-auto-install #request-filesystem-credentials-form h2,.fs-modal-auto-install #request-filesystem-credentials-form .request-filesystem-credentials-action-buttons{display:none}.fs-modal-auto-install #request-filesystem-credentials-form input[type=password],.fs-modal-auto-install #request-filesystem-credentials-form input[type=email],.fs-modal-auto-install #request-filesystem-credentials-form input[type=text]{-webkit-appearance:none;padding:10px 10px 5px 10px;width:300px;max-width:100%}.fs-modal-auto-install #request-filesystem-credentials-form>div,.fs-modal-auto-install #request-filesystem-credentials-form label,.fs-modal-auto-install #request-filesystem-credentials-form fieldset{width:300px;max-width:100%;margin:0 auto;display:block}.button-primary.warn{box-shadow:0 1px 0 #d2593c;text-shadow:0 -1px 1px #d2593c,1px 0 1px #d2593c,0 1px 1px #d2593c,-1px 0 1px #d2593c;background:#f56a48;border-color:#ec6544 #d2593c #d2593c}.button-primary.warn:hover{background:#fd6d4a;border-color:#d2593c}.button-primary.warn:focus{box-shadow:0 1px 0 #dd6041,0 0 2px 1px #e4a796}.button-primary.warn:active{background:#dd6041;border-color:#d2593c;box-shadow:inset 0 2px 0 #d2593c}.button-primary.warn.disabled{color:#f5b3a1 !important;background:#e76444 !important;border-color:#d85e40 !important;text-shadow:0 -1px 0 rgba(0,0,0,.1) !important} diff --git a/freemius/assets/css/admin/gdpr-optin-notice.css b/freemius/assets/css/admin/gdpr-optin-notice.css index 0da5146..fb934ff 100644 --- a/freemius/assets/css/admin/gdpr-optin-notice.css +++ b/freemius/assets/css/admin/gdpr-optin-notice.css @@ -1 +1 @@ -.fs-notice[data-id^="gdpr_optin_actions"] .underlined{text-decoration:underline}.fs-notice[data-id^="gdpr_optin_actions"] ul .button,.fs-notice[data-id^="gdpr_optin_actions"] ul .action-description{vertical-align:middle}.fs-notice[data-id^="gdpr_optin_actions"] ul .action-description{display:inline-block;margin-left:3px} +.fs-notice[data-id^=gdpr_optin_actions] .underlined{text-decoration:underline}.fs-notice[data-id^=gdpr_optin_actions] ul .button,.fs-notice[data-id^=gdpr_optin_actions] ul .action-description{vertical-align:middle}.fs-notice[data-id^=gdpr_optin_actions] ul .action-description{display:inline-block;margin-left:3px} diff --git a/freemius/assets/css/admin/optout.css b/freemius/assets/css/admin/optout.css new file mode 100644 index 0000000..6b0d5e2 --- /dev/null +++ b/freemius/assets/css/admin/optout.css @@ -0,0 +1 @@ +.fs-tooltip-trigger{position:relative}.fs-tooltip-trigger:not(a){cursor:help}.fs-tooltip-trigger .dashicons{float:none !important}.fs-tooltip-trigger .fs-tooltip{opacity:0;visibility:hidden;-moz-transition:opacity .3s ease-in-out;-o-transition:opacity .3s ease-in-out;-ms-transition:opacity .3s ease-in-out;-webkit-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out;position:absolute;background:rgba(0,0,0,.8);color:#fff !important;font-family:"arial",serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:-17px;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,.2);box-shadow:1px 1px 1px rgba(0,0,0,.2);line-height:1.3em;font-weight:bold;text-align:left;text-transform:none !important}.rtl .fs-tooltip-trigger .fs-tooltip{text-align:right;left:auto;right:-17px}.fs-tooltip-trigger .fs-tooltip::after{content:" ";display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:rgba(0,0,0,.8) transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl .fs-tooltip-trigger .fs-tooltip::after{right:21px;left:auto}.fs-tooltip-trigger:hover .fs-tooltip{visibility:visible;opacity:1}.fs-permissions .fs-permission.fs-disabled{color:#aaa}.fs-permissions .fs-permission.fs-disabled .fs-permission-description span{color:#aaa}.fs-permissions .fs-permission .fs-switch-feedback{position:absolute;right:15px;top:52px}.fs-permissions ul{height:0;overflow:hidden;margin:0}.fs-permissions ul li{padding:17px 15px;margin:0;position:relative}.fs-permissions ul li>i.dashicons{float:left;font-size:30px;width:30px;height:30px;padding:5px}.fs-permissions ul li .fs-switch{float:right}.fs-permissions ul li .fs-permission-description{margin-left:55px}.fs-permissions ul li .fs-permission-description span{font-size:14px;font-weight:500;color:#23282d}.fs-permissions ul li .fs-permission-description .fs-tooltip{font-size:13px;font-weight:bold}.fs-permissions ul li .fs-permission-description .fs-tooltip-trigger .dashicons{margin:-1px 2px 0 2px}.fs-permissions ul li .fs-permission-description p{margin:2px 0 0 0}.fs-permissions.fs-open{background:#fff}.fs-permissions.fs-open ul{overflow:initial;height:auto;margin:20px 0 10px 0}.fs-permissions .fs-switch-feedback .fs-ajax-spinner{margin-right:10px}.fs-permissions .fs-switch-feedback.success{color:#71ae00}.rtl .fs-permissions .fs-switch-feedback{right:auto;left:15px}.rtl .fs-permissions .fs-switch-feedback .fs-ajax-spinner{margin-left:10px;margin-right:0}.rtl .fs-permissions ul li .fs-permission-description{margin-right:55px;margin-left:0}.rtl .fs-permissions ul li .fs-switch{float:left}.rtl .fs-permissions ul li i.dashicons{float:right}.fs-modal-opt-out .fs-modal-footer .fs-opt-out-button{line-height:30px;margin-right:10px}.fs-modal-opt-out .fs-permissions{margin-top:0 !important}.fs-modal-opt-out .fs-permissions .fs-permissions-section--header .fs-group-opt-out-button{float:right;line-height:1.1em}.fs-modal-opt-out .fs-permissions .fs-permissions-section--header .fs-switch-feedback{float:right;line-height:1.1em;margin-right:10px}.fs-modal-opt-out .fs-permissions .fs-permissions-section--header .fs-switch-feedback .fs-ajax-spinner{margin:-2px 0 0}.fs-modal-opt-out .fs-permissions .fs-permissions-section--header-title{font-size:1.1em;font-weight:600;text-transform:uppercase;display:block;line-height:1.1em;margin:.5em 0}.fs-modal-opt-out .fs-permissions .fs-permissions-section--desc{margin-top:0}.fs-modal-opt-out .fs-permissions hr{border:0;border-top:#eee solid 1px;margin:25px 0 20px 0}.fs-modal-opt-out .fs-permissions ul{border:1px solid #c3c4c7;border-radius:3px;margin:10px 0 0 0;box-shadow:0 1px 1px rgba(0,0,0,.04)}.fs-modal-opt-out .fs-permissions ul li{border-bottom:1px solid #d7dde1;border-left:4px solid #72aee6}.rtl .fs-modal-opt-out .fs-permissions ul li{border-left:none;border-right:4px solid #72aee6}.fs-modal-opt-out .fs-permissions ul li.fs-disabled{border-left-color:rgba(114,174,230,0)}.fs-modal-opt-out .fs-permissions ul li:last-child{border-bottom:none} diff --git a/freemius/assets/css/customizer.css b/freemius/assets/css/customizer.css index 75fe403..d59b381 100644 --- a/freemius/assets/css/customizer.css +++ b/freemius/assets/css/customizer.css @@ -1 +1 @@ -#fs_customizer_upsell .fs-customizer-plan{padding:10px 20px 20px 20px;border-radius:3px;background:#fff}#fs_customizer_upsell .fs-customizer-plan h2{position:relative;margin:0;line-height:2em;text-transform:uppercase}#fs_customizer_upsell .fs-customizer-plan h2 .button-link{top:-2px}#fs_customizer_upsell .fs-feature{position:relative}#fs_customizer_upsell .dashicons-yes{color:#0085ba;font-size:2em;vertical-align:bottom;margin-left:-7px;margin-right:10px}.rtl #fs_customizer_upsell .dashicons-yes{margin-left:10px;margin-right:-7px}#fs_customizer_upsell .dashicons-editor-help{color:#bbb;cursor:help}#fs_customizer_upsell .dashicons-editor-help .fs-feature-desc{opacity:0;visibility:hidden;-moz-transition:opacity 0.3s ease-in-out;-o-transition:opacity 0.3s ease-in-out;-ms-transition:opacity 0.3s ease-in-out;-webkit-transition:opacity 0.3s ease-in-out;transition:opacity 0.3s ease-in-out;position:absolute;background:#000;color:#fff;font-family:'arial', serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:0;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);line-height:1.3em;font-weight:bold;text-align:left}.rtl #fs_customizer_upsell .dashicons-editor-help .fs-feature-desc{text-align:right}#fs_customizer_upsell .dashicons-editor-help .fs-feature-desc::after{content:' ';display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:#000 transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl #fs_customizer_upsell .dashicons-editor-help .fs-feature-desc::after{right:21px;left:auto}#fs_customizer_upsell .dashicons-editor-help:hover .fs-feature-desc{visibility:visible;opacity:1}#fs_customizer_upsell .button-primary{display:block;text-align:center;margin-top:10px}#fs_customizer_support{display:block !important}#fs_customizer_support .button{float:right}#fs_customizer_support .button-group{width:100%;display:block;margin-top:10px}#fs_customizer_support .button-group .button{float:none;width:50%;text-align:center}#customize-theme-controls #accordion-section-freemius_upsell{border-top:1px solid #0085ba !important;border-bottom:1px solid #0085ba !important}#customize-theme-controls #accordion-section-freemius_upsell h3.accordion-section-title{color:#fff;background-color:#0085ba;border-left:4px solid #0085ba;transition:.15s background-color ease-in-out, .15s border-color ease-in-out;outline:none;border-bottom:none !important}#customize-theme-controls #accordion-section-freemius_upsell h3.accordion-section-title:hover{background-color:#008ec2;border-left-color:#0073aa}#customize-theme-controls #accordion-section-freemius_upsell h3.accordion-section-title:after{color:#fff}#customize-theme-controls #accordion-section-freemius_upsell .rtl h3.accordion-section-title{border-left:none;border-right:4px solid #0085ba}#customize-theme-controls #accordion-section-freemius_upsell .rtl h3.accordion-section-title:hover{border-right-color:#0073aa} +#fs_customizer_upsell .fs-customizer-plan{padding:10px 20px 20px 20px;border-radius:3px;background:#fff}#fs_customizer_upsell .fs-customizer-plan h2{position:relative;margin:0;line-height:2em;text-transform:uppercase}#fs_customizer_upsell .fs-customizer-plan h2 .button-link{top:-2px}#fs_customizer_upsell .fs-feature{position:relative}#fs_customizer_upsell .dashicons-yes{color:#0085ba;font-size:2em;vertical-align:bottom;margin-left:-7px;margin-right:10px}.rtl #fs_customizer_upsell .dashicons-yes{margin-left:10px;margin-right:-7px}#fs_customizer_upsell .dashicons-editor-help{color:#bbb;cursor:help}#fs_customizer_upsell .dashicons-editor-help .fs-feature-desc{opacity:0;visibility:hidden;-moz-transition:opacity .3s ease-in-out;-o-transition:opacity .3s ease-in-out;-ms-transition:opacity .3s ease-in-out;-webkit-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out;position:absolute;background:#000;color:#fff;font-family:"arial",serif;font-size:12px;padding:10px;z-index:999999;bottom:100%;margin-bottom:5px;left:0;right:0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,.2);box-shadow:1px 1px 1px rgba(0,0,0,.2);line-height:1.3em;font-weight:bold;text-align:left}.rtl #fs_customizer_upsell .dashicons-editor-help .fs-feature-desc{text-align:right}#fs_customizer_upsell .dashicons-editor-help .fs-feature-desc::after{content:" ";display:block;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:#000 transparent transparent transparent;position:absolute;top:100%;left:21px}.rtl #fs_customizer_upsell .dashicons-editor-help .fs-feature-desc::after{right:21px;left:auto}#fs_customizer_upsell .dashicons-editor-help:hover .fs-feature-desc{visibility:visible;opacity:1}#fs_customizer_upsell .button-primary{display:block;text-align:center;margin-top:10px}#fs_customizer_support{display:block !important}#fs_customizer_support .button{float:right}#fs_customizer_support .button-group{width:100%;display:block;margin-top:10px}#fs_customizer_support .button-group .button{float:none;width:50%;text-align:center}#customize-theme-controls #accordion-section-freemius_upsell{border-top:1px solid #0085ba !important;border-bottom:1px solid #0085ba !important}#customize-theme-controls #accordion-section-freemius_upsell h3.accordion-section-title{color:#fff;background-color:#0085ba;border-left:4px solid #0085ba;transition:.15s background-color ease-in-out,.15s border-color ease-in-out;outline:none;border-bottom:none !important}#customize-theme-controls #accordion-section-freemius_upsell h3.accordion-section-title:hover{background-color:#008ec2;border-left-color:#0073aa}#customize-theme-controls #accordion-section-freemius_upsell h3.accordion-section-title:after{color:#fff}#customize-theme-controls #accordion-section-freemius_upsell .rtl h3.accordion-section-title{border-left:none;border-right:4px solid #0085ba}#customize-theme-controls #accordion-section-freemius_upsell .rtl h3.accordion-section-title:hover{border-right-color:#0073aa} diff --git a/freemius/config.php b/freemius/config.php index f88efc4..ccb4212 100644 --- a/freemius/config.php +++ b/freemius/config.php @@ -146,7 +146,7 @@ #-------------------------------------------------------------------------------- if (! defined('WP_FS__IS_HTTP_REQUEST')) { - define('WP_FS__IS_HTTP_REQUEST', isset($_SERVER['HTTP_HOST'])); + define('WP_FS__IS_HTTP_REQUEST', isset($_SERVER['HTTP_HOST']) && isset($_SERVER['REQUEST_METHOD'])); } if (! defined('WP_FS__IS_HTTPS')) { @@ -154,13 +154,13 @@ 'WP_FS__IS_HTTPS', ( WP_FS__IS_HTTP_REQUEST && - // Checks if CloudFlare's HTTPS (Flexible SSL support). - isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && - 'https' === strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) + // Checks if CloudFlare's HTTPS (Flexible SSL support). + isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && + 'https' === strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) ) || - // Check if HTTPS request. - (isset($_SERVER['HTTPS']) && 'on' == $_SERVER['HTTPS']) || - (isset($_SERVER['SERVER_PORT']) && 443 == $_SERVER['SERVER_PORT']) + // Check if HTTPS request. + (isset($_SERVER['HTTPS']) && 'on' == $_SERVER['HTTPS']) || + (isset($_SERVER['SERVER_PORT']) && 443 == $_SERVER['SERVER_PORT']) ); } @@ -289,7 +289,7 @@ if (! defined('WP_FS__TIME_10_MIN_IN_SEC')) { define('WP_FS__TIME_10_MIN_IN_SEC', 600); } -// define( 'WP_FS__TIME_15_MIN_IN_SEC', 900 ); + // define( 'WP_FS__TIME_15_MIN_IN_SEC', 900 ); if (! defined('WP_FS__TIME_12_HOURS_IN_SEC')) { define('WP_FS__TIME_12_HOURS_IN_SEC', 43200); } @@ -362,11 +362,11 @@ is_network_admin() || (( defined('DOING_AJAX') && DOING_AJAX && - (isset($_REQUEST['_fs_network_admin']) /*|| + (isset($_REQUEST['_fs_network_admin']) && 'true' === $_REQUEST['_fs_network_admin'] /*|| ( ! empty( $_REQUEST['action'] ) && 'delete-plugin' === $_REQUEST['action'] )*/) ) || - // Plugin uninstall. - defined('WP_UNINSTALL_PLUGIN')) + // Plugin uninstall. + defined('WP_UNINSTALL_PLUGIN')) ) ); } diff --git a/freemius/includes/class-freemius-abstract.php b/freemius/includes/class-freemius-abstract.php index 48424a1..600eace 100644 --- a/freemius/includes/class-freemius-abstract.php +++ b/freemius/includes/class-freemius-abstract.php @@ -23,7 +23,6 @@ */ abstract class Freemius_Abstract { - #---------------------------------------------------------------------------------- #region Identity #---------------------------------------------------------------------------------- @@ -38,9 +37,12 @@ abstract class Freemius_Abstract * `$fs->is_registered() && $fs->is_tracking_allowed()` * * @since 1.0.1 + * + * @param bool $ignore_anonymous_state Since 2.5.1 + * * @return bool */ - abstract public function is_registered(); + abstract public function is_registered($ignore_anonymous_state = false); /** * Check if the user skipped connecting the account with Freemius. @@ -62,69 +64,6 @@ abstract public function is_activation_mode(); #endregion - #---------------------------------------------------------------------------------- - #region Usage Tracking - #---------------------------------------------------------------------------------- - - /** - * Returns TRUE if the user opted-in and didn't disconnect (opt-out). - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool - */ - abstract public function is_tracking_allowed(); - - /** - * Returns TRUE if the user never opted-in or manually opted-out. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @return bool - */ - public function is_tracking_prohibited() - { - return ! $this->is_registered() || ! $this->is_tracking_allowed(); - } - - /** - * Opt-out from usage tracking. - * - * Note: This will not delete the account information but will stop all tracking. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-out. - * 3. object - API Result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool|object - */ - abstract public function stop_tracking(); - - /** - * Opt-in back into usage tracking. - * - * Note: This will only work if the user opted-in previously. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-in back to usage tracking. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool|object - */ - abstract public function allow_tracking(); - - #endregion - #---------------------------------------------------------------------------------- #region Module Type #---------------------------------------------------------------------------------- diff --git a/freemius/includes/class-freemius.php b/freemius/includes/class-freemius.php index fba1289..4a66909 100644 --- a/freemius/includes/class-freemius.php +++ b/freemius/includes/class-freemius.php @@ -377,6 +377,8 @@ class Freemius extends Freemius_Abstract { const REASON_DIDNT_WORK_AS_EXPECTED = 14; const REASON_TEMPORARY_DEACTIVATION = 15; + #endregion + /** * @author Leo Fajardo (@leorw) * @since 2.3.1 @@ -392,7 +394,8 @@ class Freemius extends Freemius_Abstract { */ private $_pricing_js_path = null; - #endregion + const VERSION_MAX_CHARS = 16; + const LANGUAGE_MAX_CHARS = 8; /* Ctor ------------------------------------------------------------------------------------------------------------------*/ @@ -408,8 +411,10 @@ class Freemius extends Freemius_Abstract { * @param bool $is_init Since 1.2.1 Is initiation sequence. */ private function __construct( $module_id, $slug = false, $is_init = false ) { + $main_file = false; + if ( $is_init && is_numeric( $module_id ) && is_string( $slug ) ) { - $this->store_id_slug_type_path_map( $module_id, $slug ); + $main_file = $this->store_id_slug_type_path_map( $module_id, $slug ); } $this->_module_id = $module_id; @@ -424,7 +429,7 @@ private function __construct( $module_id, $slug = false, $is_init = false ) { $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->get_unique_affix(), WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); - $this->_plugin_main_file_path = $this->_find_caller_plugin_file( $is_init ); + $this->_plugin_main_file_path = $this->_find_caller_plugin_file( $is_init, $main_file ); $this->_plugin_dir_path = plugin_dir_path( $this->_plugin_main_file_path ); $this->_plugin_basename = $this->get_plugin_basename(); $this->_free_plugin_basename = str_replace( '-premium/', '/', $this->_plugin_basename ); @@ -519,7 +524,10 @@ private function __construct( $module_id, $slug = false, $is_init = false ) { * @author Leo Fajardo (@leorw) * @since 1.2.2 */ - ( is_object( $this->_plugin ) ? $this->_plugin->title : $this->get_plugin_name() ), + ( is_object( $this->_plugin ) && isset( $this->_plugin->title ) ? + $this->_plugin->title : + $this->get_plugin_name() + ), $this->get_unique_affix() ); @@ -933,82 +941,27 @@ private function _version_updates_handler() { * @param string $sdk_version */ function _sdk_version_update( $sdk_prev_version, $sdk_version ) { - /** - * @since 1.1.7.3 Fixed unwanted connectivity test cleanup. - */ if ( empty( $sdk_prev_version ) ) { return; } - if ( version_compare( $sdk_prev_version, '2.1.0', '<' ) && - version_compare( $sdk_version, '2.1.0', '>=' ) - ) { - $this->_storage->handle_gdpr_admin_notice = true; - } - - if ( version_compare( $sdk_prev_version, '2.0.0', '<' ) && - version_compare( $sdk_version, '2.0.0', '>=' ) - ) { - $this->migrate_to_subscriptions_collection(); - - $this->consolidate_licenses(); - - // Clear trial_plan since it's now loaded from the plans collection when needed. - $this->_storage->remove( 'trial_plan', true, false ); - } - - if ( version_compare( $sdk_prev_version, '1.2.3', '<' ) && - version_compare( $sdk_version, '1.2.3', '>=' ) - ) { - /** - * Starting from version 1.2.3, paths are stored as relative instead of absolute and some of them can be - * invalid. - * - * @author Leo Fajardo (@leorw) - */ - $this->remove_invalid_paths(); - } - - if ( version_compare( $sdk_prev_version, '1.1.5', '<' ) && - version_compare( $sdk_version, '1.1.5', '>=' ) + if ( + version_compare( $sdk_prev_version, '2.5.1', '<' ) && + version_compare( $sdk_version, '2.5.1', '>=' ) ) { - // On version 1.1.5 merged connectivity and is_on data. - if ( isset( $this->_storage->connectivity_test ) ) { - if ( ! isset( $this->_storage->is_on ) ) { - unset( $this->_storage->connectivity_test ); - } else { - $connectivity_data = $this->_storage->connectivity_test; - $connectivity_data['is_active'] = $this->_storage->is_on['is_active']; - $connectivity_data['timestamp'] = $this->_storage->is_on['timestamp']; - - // Override. - $this->_storage->connectivity_test = $connectivity_data; + if ( $this->is_registered( true ) ) { + /** + * Migrate to new permissions layer. + */ + require_once WP_FS__DIR_INCLUDES . '/supplements/fs-migration-2.5.1.php'; - // Remove previous structure. - unset( $this->_storage->is_on ); - } + $install_by_blog_id = is_multisite() ? + $this->get_blog_install_map() : + array( 0 => $this->_site ); + fs_migrate_251( $this, $install_by_blog_id ); } } - - if ( - version_compare( $sdk_prev_version, '2.2.1', '<' ) && - version_compare( $sdk_version, '2.2.1', '>=' ) - ) { - /** - * Clear the file cache without storing the previous path since it could be a wrong path. For example, - * in the versions of the SDK lower than 2.2.1, it's possible for the path of an add-on to be the same - * as the parent plugin's when the add-on was auto-installed since the relevant method names were not - * skipped in the logic that determines the right path in the `get_caller_main_file_and_type` method - * (e.g. `try_activate_plugin`). Since it was an auto-installation, the caller was the parent plugin - * and so its path was used. In case the stored path is wrong, clearing the cache will resolve issues - * related to data mix-up between plugins (e.g. titles and versions of an add-on and its parent plugin). - * - * @author Leo Fajardo (@leorw) - * @since 2.2.1 - */ - $this->clear_module_main_file_cache( false ); - } } /** @@ -1059,102 +1012,6 @@ private static function migrate_install_plan_to_plan_id( FS_Storage $storage, $b } } - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - */ - private function migrate_to_subscriptions_collection() { - if ( ! is_object( $this->_site ) ) { - return; - } - - if ( isset( $this->_storage->subscription ) && is_object( $this->_storage->subscription ) ) { - $this->_storage->subscriptions = array( fs_get_entity( $this->_storage->subscription, FS_Subscription::get_class_name() ) ); - } - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - */ - private function consolidate_licenses() { - $plugin_licenses = self::get_account_option( 'licenses', WP_FS__MODULE_TYPE_PLUGIN ); - if ( isset( $plugin_licenses[ $this->_slug ] ) ) { - $plugin_licenses = $plugin_licenses[ $this->_slug ]; - } else { - $plugin_licenses = array(); - } - - $theme_licenses = self::get_account_option( 'licenses', WP_FS__MODULE_TYPE_THEME ); - if ( isset( $theme_licenses[ $this->_slug ] ) ) { - $theme_licenses = $theme_licenses[ $this->_slug ]; - } else { - $theme_licenses = array(); - } - - if ( empty( $plugin_licenses ) && empty( $theme_licenses ) ) { - return; - } - - $all_licenses = array(); - $user_id_license_ids_map = array(); - - foreach ( $plugin_licenses as $user_id => $user_licenses ) { - if ( is_array( $user_licenses ) ) { - if ( ! isset( $user_license_ids[ $user_id ] ) ) { - $user_id_license_ids_map[ $user_id ] = array(); - } - - foreach ( $user_licenses as $user_license ) { - $all_licenses[] = $user_license; - $user_id_license_ids_map[ $user_id ][] = $user_license->id; - } - } - } - - foreach ( $theme_licenses as $user_id => $user_licenses ) { - if ( is_array( $user_licenses ) ) { - if ( ! isset( $user_license_ids[ $user_id ] ) ) { - $user_id_license_ids_map[ $user_id ] = array(); - } - - foreach ( $user_licenses as $user_license ) { - $all_licenses[] = $user_license; - $user_id_license_ids_map[ $user_id ][] = $user_license->id; - } - } - } - - self::store_user_id_license_ids_map( - $user_id_license_ids_map, - $this->_module_id - ); - - $this->_store_licenses( true, $this->_module_id, $all_licenses ); - } - - /** - * Remove invalid paths. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.3 - */ - private function remove_invalid_paths() { - // Remove invalid path that is still associated with the current slug if there's any. - $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); - foreach ( $file_slug_map as $plugin_basename => $slug ) { - if ( $slug === $this->_slug && - $plugin_basename !== $this->_plugin_basename && - ! file_exists( $this->get_absolute_path( $plugin_basename ) ) - ) { - unset( $file_slug_map[ $plugin_basename ] ); - self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true ); - - break; - } - } - } - /** * @author Vova Feldman (@svovaf) * @since 1.2.2.7 @@ -1491,46 +1348,6 @@ function _plugins_loaded() { } } - /** - * Add special parameter to WP admin AJAX calls so when we - * process AJAX calls we can identify its source properly. - * - * @author Leo Fajardo (@leorw) - * @since 2.0.0 - */ - static function _enrich_ajax_url() { - $admin_param = is_network_admin() ? - '_fs_network_admin' : - '_fs_blog_admin'; - ?> - - is_plugin() ) { - if ( $this->_is_network_active ) { + if ( version_compare( $GLOBALS['wp_version'], '5.1', '<' ) ) { add_action( 'wpmu_new_blog', array( $this, '_after_new_blog_callback' ), 10, 6 ); + } else { + add_action( 'wp_initialize_site', array( $this, '_after_wp_initialize_site_callback' ), 11, 2 ); } register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) ); @@ -1652,7 +1471,12 @@ private function register_constructor_hooks() { add_action( 'deactivate_blog', array( &$this, '_after_site_deactivated_callback' ) ); add_action( 'archive_blog', array( &$this, '_after_site_deactivated_callback' ) ); add_action( 'make_spam_blog', array( &$this, '_after_site_deactivated_callback' ) ); - add_action( 'deleted_blog', array( &$this, '_after_site_deleted_callback' ), 10, 2 ); + + if ( version_compare( $GLOBALS['wp_version'], '5.1', '<' ) ) { + add_action( 'deleted_blog', array( $this, '_after_site_deleted_callback' ), 10, 2 ); + } else { + add_action( 'wp_delete_site', array( $this, '_after_wpsite_deleted_callback' ) ); + } add_action( 'activate_blog', array( &$this, '_after_site_reactivated_callback' ) ); add_action( 'unarchive_blog', array( &$this, '_after_site_reactivated_callback' ) ); @@ -1677,6 +1501,7 @@ private function register_constructor_hooks() { add_action( 'admin_init', array( &$this, '_add_premium_version_upgrade_selection' ) ); add_action( 'admin_init', array( &$this, '_add_beta_mode_update_handler' ) ); add_action( 'admin_init', array( &$this, '_add_user_change_option' ) ); + add_action( 'admin_init', array( &$this, '_add_email_address_update_option' ) ); $this->add_ajax_action( 'update_billing', array( &$this, '_update_billing_ajax_action' ) ); $this->add_ajax_action( 'start_trial', array( &$this, '_start_trial_ajax_action' ) ); @@ -1706,18 +1531,22 @@ private function register_constructor_hooks() { ); $this->add_filter( 'after_code_type_change', array( &$this, '_after_code_type_change' ) ); - add_action( 'admin_init', array( &$this, '_add_trial_notice' ) ); - add_action( 'admin_init', array( &$this, '_add_affiliate_program_notice' ) ); + add_action( 'admin_init', array( &$this, '_add_trial_notice' ) ); // @phpstan-ignore-line + add_action( 'admin_init', array( &$this, '_add_affiliate_program_notice' ) ); // @phpstan-ignore-line add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_common_css' ) ); /** - * Handle request to reset anonymous mode for `get_reconnect_url()`. + * Handle request to reset anonymous mode for `get_reconnect_url()` or reset the pending activation mode. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 */ - if ( fs_request_is_action( 'reset_anonymous_mode' ) && - $this->get_unique_affix() === fs_request_get( 'fs_unique_affix' ) + if ( + ( + fs_request_is_action( 'reset_anonymous_mode' ) || + fs_request_is_action( 'reset_pending_activation_mode' ) + ) && + $this->get_unique_affix() === fs_request_get_raw( 'fs_unique_affix' ) ) { add_action( 'admin_init', array( &$this, 'connect_again' ) ); } @@ -1813,7 +1642,7 @@ static function _remove_fs_updates_from_plugin_install_page( $updates, $transien * @author Leo Fajardo (@leorw) * @since 2.2.3 * - * @return string + * @return void */ static function _prepend_fs_allow_updater_and_dialog_flag_url_param() { $slug_basename_map = array(); @@ -2048,7 +1877,13 @@ function _hook_action_links_and_register_account_hooks() { return; } - $this->_add_tracking_links(); + if ( + ( self::is_plugins_page() && $this->is_plugin() ) || + ( self::is_themes_page() && $this->is_theme() ) || + fs_request_is_action_secure( $this->get_unique_affix() . '_reconnect' ) + ) { + $this->_add_tracking_links(); + } if ( self::is_plugins_page() && $this->is_plugin() ) { $this->hook_plugin_action_links(); @@ -2094,20 +1929,27 @@ private function _register_account_hooks() { /** * Leverage backtrace to find caller plugin file path. * - * @author Vova Feldman (@svovaf) - * @since 1.0.6 - * - * @param bool $is_init Is initiation sequence. + * @param bool $is_init Is initiation sequence. + * @param string $main_file Since 2.5.0 expects the module's main file path to potentially purge the cached path. * * @return string + * @since 1.0.6 + * + * @author Vova Feldman (@svovaf) */ - private function _find_caller_plugin_file( $is_init = false ) { + private function _find_caller_plugin_file( $is_init = false, $main_file = '' ) { // Try to load the cached value of the file path. if ( isset( $this->_storage->plugin_main_file ) ) { $plugin_main_file = $this->_storage->plugin_main_file; if ( ! empty( $plugin_main_file->path ) ) { $absolute_path = $this->get_absolute_path( $plugin_main_file->path ); if ( file_exists( $absolute_path ) ) { + if ( $is_init && $absolute_path !== $this->get_absolute_path( $main_file ) ) { + // Update cached path if not matching the actual path. + $plugin_main_file->path = $main_file; + $this->_storage->plugin_main_file = $plugin_main_file; + } + return $absolute_path; } } @@ -2148,12 +1990,11 @@ private function _find_caller_plugin_file( $is_init = false ) { * Only the original instantiator that calls dynamic_init can modify the module's path. */ // Find caller module. - $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); $this->_storage->plugin_main_file = (object) array( - 'path' => $id_slug_type_path_map[ $this->_module_id ]['path'], + 'path' => $main_file, ); - return $this->get_absolute_path( $id_slug_type_path_map[ $this->_module_id ]['path'] ); + return $this->get_absolute_path( $main_file ); } /** @@ -2215,6 +2056,8 @@ private function get_module_root_dir_path( $module_type = false ) { * @param number $module_id * @param string $slug * + * @return string Since 2.5.0 return the module's main file path. + * * @since 1.2.2 */ private function store_id_slug_type_path_map( $module_id, $slug ) { @@ -2236,20 +2079,52 @@ private function store_id_slug_type_path_map( $module_id, $slug ) { $store_option = true; } - if ( empty( $id_slug_type_path_map[ $module_id ]['path'] ) || - /** - * This verification is for cases when suddenly the same module - * is installed but with a different folder name. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.3 - */ - ! file_exists( $this->get_absolute_path( - $id_slug_type_path_map[ $module_id ]['path'], - $id_slug_type_path_map[ $module_id ]['type'] - ) ) - ) { - $caller_main_file_and_type = $this->get_caller_main_file_and_type(); + $find_caller = empty( $id_slug_type_path_map[ $module_id ]['path'] ); + + if ( ! $find_caller ) { + /** + * This verification is for cases when suddenly the same module + * is installed but with a different folder name. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.3 + */ + $find_caller = ! file_exists( $this->get_absolute_path( + $id_slug_type_path_map[ $module_id ]['path'], + $id_slug_type_path_map[ $module_id ]['type'] + ) ); + } + + foreach ( $id_slug_type_path_map as $id => $data ) { + if ( empty( $id ) ) { + // Remove maps with empty module ID. + unset( $id_slug_type_path_map[ $id ] ); + $store_option = true; + continue; + } + + /** + * If the module's main file path is identical to the main file path of another module then it means that the cached path of the current module or the other one with the same path is wrong, and therefore, we need to recalculate those paths. + * + * @author Vova Feldman (@svovaf) + * @since 2.5.0 + */ + if ( ! $find_caller ) { + if ( $id == $module_id ) { + continue; + } + + if ( + isset( $data['path'] ) && + $data['path'] === $id_slug_type_path_map[ $module_id ]['path'] + ) { + $find_caller = true; + } + } + } + + if ( $find_caller ) { + $caller_main_file_and_type = $this->get_caller_main_file_and_type( $module_id ); $id_slug_type_path_map[ $module_id ]['type'] = $caller_main_file_and_type->module_type; $id_slug_type_path_map[ $module_id ]['path'] = $caller_main_file_and_type->path; @@ -2260,6 +2135,8 @@ private function store_id_slug_type_path_map( $module_id, $slug ) { if ( $store_option ) { self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true ); } + + return $id_slug_type_path_map[ $module_id ]['path']; } /** @@ -2273,8 +2150,10 @@ private function store_id_slug_type_path_map( $module_id, $slug ) { * add-ons are relying on loading the SDK from the parent module, and also allows themes including the * SDK an internal file instead of directly from functions.php. * @since 1.2.1.7 Knows how to handle cases when an add-on includes the parent module logic. + * + * @param number $module_id @since 2.5.0 */ - private function get_caller_main_file_and_type() { + private function get_caller_main_file_and_type( $module_id ) { self::require_plugin_essentials(); $all_plugins = fs_get_plugins( true ); @@ -2413,10 +2292,12 @@ private function get_caller_main_file_and_type() { } } - return (object) array( + $caller_main_file_and_type = (object) array( 'module_type' => $module_type, 'path' => $caller_file_candidate ); + + return apply_filters( "fs_{$module_id}_caller_main_file_and_type", $caller_main_file_and_type ); } #---------------------------------------------------------------------------------- @@ -2433,6 +2314,13 @@ private function get_caller_main_file_and_type() { * @since 1.1.2 */ function _add_deactivation_feedback_dialog_box() { + if ( + $this->is_clone() || + ( is_object( $this->_site ) && ! $this->is_registered() ) + ) { + return; + } + $subscription_cancellation_dialog_box_template_params = $this->apply_filters( 'show_deactivation_subscription_cancellation', true ) ? $this->_get_subscription_cancellation_dialog_box_template_params() : array(); @@ -2440,7 +2328,7 @@ function _add_deactivation_feedback_dialog_box() { /** * @since 2.3.0 Developers can optionally hide the deactivation feedback form using the 'show_deactivation_feedback_form' filter. */ - $show_deactivation_feedback_form = true; + $show_deactivation_feedback_form = ! self::is_deactivation_snoozed(); if ( $this->has_filter( 'show_deactivation_feedback_form' ) ) { $show_deactivation_feedback_form = $this->apply_filters( 'show_deactivation_feedback_form', true ); } else if ( $this->is_addon() ) { @@ -2545,7 +2433,7 @@ function _get_uninstall_reasons( $user_type = 'long-term' ) { $reason_temporary_deactivation = array( 'id' => self::REASON_TEMPORARY_DEACTIVATION, 'text' => sprintf( - $this->get_text_inline( "It's a temporary %s. I'm just debugging an issue.", 'reason-temporary-x' ), + $this->get_text_inline( "It's a temporary %s - I'm troubleshooting an issue", 'reason-temporary-x' ), strtolower( $this->is_plugin() ? $this->get_text_inline( 'Deactivation', 'deactivation' ) : $this->get_text_inline( 'Theme Switch', 'theme-switch' ) @@ -2710,6 +2598,14 @@ function _submit_uninstall_reason_action() { $this->_storage->store( 'uninstall_reason', $reason ); + if ( self::REASON_TEMPORARY_DEACTIVATION == $reason->id ) { + $snooze_period = fs_request_get( 'snooze_period' ); + + if ( is_numeric( $snooze_period ) && 0 < $snooze_period ) { + self::snooze_deactivation_form( (int) $snooze_period ); + } + } + /** * If the module type is "theme", trigger the uninstall event here (on theme deactivation) since themes do * not support uninstall hook. @@ -2731,6 +2627,73 @@ function _submit_uninstall_reason_action() { exit; } + #-------------------------------------------------------------------------------- + #region Deactivation Feedback Snoozing + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 2.4.3 + * + * @param int $period + * + * @return bool True if the value was set, false otherwise. + */ + private static function snooze_deactivation_form( $period ) { + return ( 0 < $period && self::reset_deactivation_snoozing( $period ) ); + } + + /** + * Check if deactivation feedback form is snoozed. + * + * @author Vova Feldman (@svovaf) + * @since 2.4.3 + * + * @return bool + */ + static function is_deactivation_snoozed() { + $is_snoozed = ( ! is_multisite() || fs_is_network_admin() ) ? + get_transient( 'fs_snooze_period' ) : + get_site_transient( 'fs_snooze_period' ); + + + return ( 'true' === $is_snoozed ); + } + + /** + * Reset deactivation snoozing. When `$period` is `0` will stop deactivation snoozing by deleting the transients. Otherwise, will set the transients for the selected period. + * + * @param int $period Period in seconds. + * + * @author Vova Feldman (@svovaf) + * @since 2.4.3 + */ + private static function reset_deactivation_snoozing( $period = 0 ) { + $value = ( 0 === $period ) ? null : 'true'; + + if ( ! is_multisite() || fs_is_network_admin() ) { + return set_transient( 'fs_snooze_period', $value, $period ); + } else { + return set_site_transient( 'fs_snooze_period', $value, $period ); + } + } + + /** + * The deactivation snooze expiration UNIX timestamp (in sec). + * + * @author Vova Feldman (@svovaf) + * @since 2.4.3 + * + * @return int + */ + static function deactivation_snooze_expires_at() { + return ( ! is_multisite() || fs_is_network_admin() ) ? + (int) get_option( '_transient_timeout_fs_snooze_period' ) : + (int) get_site_option( '_site_transient_timeout_fs_snooze_period' ); + } + + #endregion + /** * @author Leo Fajardo (@leorw) * @since 2.1.4 @@ -2927,6 +2890,13 @@ function get_addon_instance( $id_or_slug ) { return self::instance( $addon_id ); } + /** + * @return Freemius[] + */ + static function _get_all_instances() { + return self::$_instances; + } + #endregion ------------------------------------------------------------------ /** @@ -3437,7 +3407,9 @@ private static function _load_required_static() { add_action( 'plugins_loaded', array( 'Freemius', '_load_textdomain' ), 1 ); } - add_action( 'admin_footer', array( 'Freemius', '_enrich_ajax_url' ) ); + $clone_manager = FS_Clone_Manager::instance(); + add_action( 'init', array( $clone_manager, '_init' ) ); + add_action( 'admin_footer', array( 'Freemius', '_open_support_forum_in_new_page' ) ); if ( self::is_plugins_page() || self::is_themes_page() ) { @@ -3456,44 +3428,207 @@ private static function _load_required_static() { self::$_statics_loaded = true; } + #-------------------------------------------------------------------------------- + #region Clone + #-------------------------------------------------------------------------------- + /** * @author Leo Fajardo (@leorw) + * @since 2.5.0 * - * @since 2.1.3 + * @param bool $only_if_manual_resolution_is_not_hidden + * + * @return bool */ - private static function migrate_options_to_network() { - self::migrate_accounts_to_network(); - - // Migrate API options from site level to network level. - $api_network_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true, true ); - $api_network_options->migrate_to_network(); - - // Migrate API cache to network level storage. - FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME )->migrate_to_network(); + private function is_unresolved_clone( $only_if_manual_resolution_is_not_hidden = false ) { + if ( ! $this->is_clone( $only_if_manual_resolution_is_not_hidden ) ) { + return false; + } - self::$_accounts->set_option( 'ms_migration_complete', true, true ); + return FS_Clone_Manager::instance()->has_temporary_duplicate_mode_expired(); } - #---------------------------------------------------------------------------------- - #region Localization - #---------------------------------------------------------------------------------- - /** - * Load framework's text domain. + * @author Leo Fajardo (@leorw) + * @since 2.5.0 * - * @author Vova Feldman (@svovaf) - * @since 1.2.1 + * @param bool $only_if_manual_resolution_is_not_hidden */ - static function _load_textdomain() { - if ( ! is_admin() ) { - return; + function is_clone( $only_if_manual_resolution_is_not_hidden = false ) { + if ( ! is_object( $this->_site ) ) { + return false; } - global $fs_active_plugins; + $blog_id = null; - // Works both for plugins and themes. - load_plugin_textdomain( - 'freemius', + if ( + fs_is_network_admin() && + FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) + ) { + // Ensure that we're comparing the network install's URL with the relevant subsite's URL. + $blog_id = $this->_storage->network_install_blog_id; + } + + $site_url = Freemius::get_unfiltered_site_url( $blog_id, true, true ); + + if ( ! $this->_site->is_clone( $site_url ) ) { + return false; + } + + return ( + ! $only_if_manual_resolution_is_not_hidden || + ! FS_Clone_Manager::instance()->should_hide_manual_resolution() + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @param int|null $blog_id + * @param bool $strip_protocol + * @param bool $add_trailing_slash + * + * @return string + */ + static function get_unfiltered_site_url( $blog_id = null, $strip_protocol = false, $add_trailing_slash = false ) { + $url = ( ! is_multisite() && defined( 'WP_SITEURL' ) ) ? WP_SITEURL : self::get_site_url_from_wp_option( $blog_id ); + + if ( $strip_protocol ) { + $url = fs_strip_url_protocol( $url ); + } + + if ( $add_trailing_slash ) { + $url = trailingslashit( $url ); + } + + return $url; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.6.0 + * + * @param int|null $blog_id + * + * @return string + */ + private static function get_site_url_from_wp_option( $blog_id = null ) { + global $wp_filter; + + $site_url_filters = array( + 'site_url' => null, + 'pre_option_siteurl' => null, + 'default_option_siteurl' => null, + 'option_siteurl' => null, + ); + + // Detach all URL-related filters to get the actual site's URL (stripped of potential manipulations by multilingual plugins). + foreach ( $site_url_filters as $hook_name => $site_url_filter ) { + if ( ! empty( $wp_filter[ $hook_name ] ) ) { + $site_url_filters[ $hook_name ] = $wp_filter[ $hook_name ]; + unset( $wp_filter[ $hook_name ] ); + } + } + + $url = get_site_url( $blog_id ); + + // Re-attach the filters back. + foreach ( $site_url_filters as $hook_name => $site_url_filter ) { + if ( ! empty( $site_url_filter ) ) { + $wp_filter[ $hook_name ] = $site_url_filter; + } + } + + return $url; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @param number $site_id + */ + function fetch_install_by_id( $site_id ) { + return $this->get_current_or_network_user_api_scope()->get( "/installs/{$site_id}.json" ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @return string|object|bool + */ + function _handle_long_term_duplicate() { + $this->_logger->entrance(); + + $this->delete_current_install( false ); + + $license_key = false; + + if ( + is_object( $this->_license ) && + ! $this->_license->is_utilized( + ( WP_FS__IS_LOCALHOST_FOR_SERVER || FS_Site::is_localhost_by_address( self::get_unfiltered_site_url() ) ) + ) + ) { + $license_key = $this->_license->secret_key; + } + + return $this->opt_in( + false, + false, + false, + $license_key, + false, + false, + false, + null, + array(), + false + ); + } + + #endregion + + /** + * @author Leo Fajardo (@leorw) + * + * @since 2.1.3 + */ + private static function migrate_options_to_network() { + self::migrate_accounts_to_network(); + + // Migrate API options from site level to network level. + $api_network_options = FS_Option_Manager::get_manager( WP_FS__OPTIONS_OPTION_NAME, true, true ); + $api_network_options->migrate_to_network(); + + // Migrate API cache to network level storage. + FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME )->migrate_to_network(); + + self::$_accounts->set_option( 'ms_migration_complete', true, true ); + } + + #---------------------------------------------------------------------------------- + #region Localization + #---------------------------------------------------------------------------------- + + /** + * Load framework's text domain. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1 + */ + static function _load_textdomain() { + if ( ! is_admin() ) { + return; + } + + global $fs_active_plugins; + + // Works both for plugins and themes. + load_plugin_textdomain( + 'freemius', false, $fs_active_plugins->newest->sdk_path . '/languages/' ); @@ -3531,7 +3666,7 @@ static function _add_debug_section() { } else { // Add hidden debug page. $hook = FS_Admin_Menu_Manager::add_subpage( - null, + '', $title, $title, 'manage_options', @@ -3638,7 +3773,7 @@ static function _set_db_option() { self::shoot_ajax_failure(); } - $option_value = fs_request_get( 'option_value' ); + $option_value = fs_request_get_raw( 'option_value' ); if ( ! empty( $option_value ) ) { update_option( $option_name, $option_value ); @@ -3692,6 +3827,10 @@ static function _debug_page_actions() { switch_to_blog( $current_blog_id ); } + } else if ( fs_request_is_action( 'reset_deactivation_snoozing' ) ) { + check_admin_referer( 'reset_deactivation_snoozing' ); + + self::reset_deactivation_snoozing(); } else if ( fs_request_is_action( 'simulate_trial' ) ) { check_admin_referer( 'simulate_trial' ); @@ -3740,55 +3879,69 @@ static function _debug_page_actions() { } /** - * @author Vova Feldman (@svovaf) - * @since 1.0.8 + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @return array */ - static function _debug_page_render() { + static function get_all_modules_sites() { self::$_static_logger->entrance(); + $sites_by_type = array( + WP_FS__MODULE_TYPE_PLUGIN => array(), + WP_FS__MODULE_TYPE_THEME => array(), + ); + + $module_types = array_keys( $sites_by_type ); + if ( ! is_multisite() ) { - $all_plugins_installs = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ); - $all_themes_installs = self::get_all_sites( WP_FS__MODULE_TYPE_THEME ); + foreach ( $module_types as $type ) { + $sites_by_type[ $type ] = self::get_all_sites( $type ); + + foreach ( $sites_by_type[ $type ] as $slug => $install ) { + $sites_by_type[ $type ][ $slug ] = array( $install ); + } + } } else { $sites = self::get_sites(); - $all_plugins_installs = array(); - $all_themes_installs = array(); - foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); - $plugins_installs = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN, $blog_id ); - - foreach ( $plugins_installs as $slug => $install ) { - if ( ! isset( $all_plugins_installs[ $slug ] ) ) { - $all_plugins_installs[ $slug ] = array(); - } - - $install->blog_id = $blog_id; + foreach ( $module_types as $type ) { + $installs = self::get_all_sites( $type, $blog_id ); - $all_plugins_installs[ $slug ][] = $install; - } + foreach ( $installs as $slug => $install ) { + if ( ! isset( $sites_by_type[ $type ][ $slug ] ) ) { + $sites_by_type[ $type ][ $slug ] = array(); + } - $themes_installs = self::get_all_sites( WP_FS__MODULE_TYPE_THEME, $blog_id ); + $install->blog_id = $blog_id; - foreach ( $themes_installs as $slug => $install ) { - if ( ! isset( $all_themes_installs[ $slug ] ) ) { - $all_themes_installs[ $slug ] = array(); + $sites_by_type[ $type ][ $slug ][] = $install; } - $install->blog_id = $blog_id; - - $all_themes_installs[ $slug ][] = $install; } } } + return $sites_by_type; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.0.8 + */ + static function _debug_page_render() { + self::$_static_logger->entrance(); + + $all_modules_sites = self::get_all_modules_sites(); + $licenses_by_module_type = self::get_all_licenses_by_module_type(); $vars = array( - 'plugin_sites' => $all_plugins_installs, - 'theme_sites' => $all_themes_installs, + 'plugin_sites' => $all_modules_sites[ WP_FS__MODULE_TYPE_PLUGIN ], + 'theme_sites' => $all_modules_sites[ WP_FS__MODULE_TYPE_THEME ], 'users' => self::get_all_users(), 'addons' => self::get_all_addons(), 'account_addons' => self::get_all_account_addons(), @@ -3820,6 +3973,10 @@ static function _debug_page_render() { function is_on() { self::$_static_logger->entrance(); + if ( is_object( $this->_site ) && ! $this->is_registered() ) { + return false; + } + if ( isset( $this->_is_on ) ) { return $this->_is_on; } @@ -3891,36 +4048,58 @@ private function should_run_connectivity_test( $flush_if_no_connectivity = false } /** - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 + * @author Leo Fajardo (@leorw) + * @since 2.5.4 * - * @param int|null $blog_id Since 2.0.0. - * @param bool $is_gdpr_test Since 2.0.2. Perform only the GDPR test. + * @param bool $is_update * - * @return object|false + * @return bool */ - private function ping( $blog_id = null, $is_gdpr_test = false ) { - if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY ) { + private function should_turn_fs_on( $is_update = true ) { + if ( + empty( $this->_plugin->opt_in_moderation ) || + ! is_array( $this->_plugin->opt_in_moderation ) + ) { + return true; + } + + $optin_config = $this->_plugin->opt_in_moderation; + + if ( + WP_FS__IS_LOCALHOST && + ( ! isset( $optin_config['localhost'] ) || false !== $optin_config['localhost'] ) + ) { + return true; + } + + $optin_config_key = $is_update ? + 'updates' : + 'new'; + + if ( ! isset( $optin_config[ $optin_config_key ] ) ) { + return true; + } + + $visibility_percentage = $optin_config[ $optin_config_key ]; + + if ( 0 == $visibility_percentage ) { return false; } - $version = $this->get_plugin_version(); + if ( ! is_numeric( $visibility_percentage ) ) { + return true; + } - $is_update = $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() ); + $min = 1; + $max = 100; - return $this->get_api_plugin_scope()->ping( - $this->get_anonymous_id( $blog_id ), - array( - 'is_update' => json_encode( $is_update ), - 'version' => $version, - 'sdk' => $this->version, - 'is_admin' => json_encode( is_admin() ), - 'is_ajax' => json_encode( self::is_ajax() ), - 'is_cron' => json_encode( self::is_cron() ), - 'is_gdpr_test' => $is_gdpr_test, - 'is_http' => json_encode( WP_FS__IS_HTTP_REQUEST ), - ) - ); + if ( function_exists( 'random_int' ) ) { + $random = random_int( $min, $max ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.random_intFound + } else { + $random = rand( $min, $max ); + } + + return ( $random <= $visibility_percentage ); } /** @@ -3931,7 +4110,7 @@ private function ping( $blog_id = null, $is_gdpr_test = false ) { * * @param bool $flush_if_no_connectivity * - * @return bool + * @return bool|null */ function has_api_connectivity( $flush_if_no_connectivity = false ) { $this->_logger->entrance(); @@ -3944,7 +4123,7 @@ function has_api_connectivity( $flush_if_no_connectivity = false ) { isset( $this->_storage->connectivity_test ) && true === $this->_storage->connectivity_test['is_connected'] ) { - unset( $this->_storage->connectivity_test ); + $this->clear_connectivity_info(); } if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) { @@ -3961,36 +4140,47 @@ function has_api_connectivity( $flush_if_no_connectivity = false ) { return $this->_has_api_connection; } - $pong = $this->ping(); - $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); + if ( + ! empty( $this->_storage->connectivity_test ) && + isset( $this->_storage->connectivity_test['is_active'] ) + ) { + $is_active = $this->_storage->connectivity_test['is_active']; + } else { + $is_active = $this->should_turn_fs_on( $this->apply_filters( 'is_plugin_update', $this->is_plugin_update() ) ); - if ( ! $is_connected ) { - // API failure. - $this->_add_connectivity_issue_message( $pong ); + $this->store_connectivity_info( (object) array( 'is_active' => $is_active ), null ); } - if ( $is_connected ) { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); + if ( $is_active ) { + $this->_is_on = true; } - - $this->store_connectivity_info( $pong, $is_connected ); return $this->_has_api_connection; } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + */ + private function clear_connectivity_info() { + unset( $this->_storage->connectivity_test ); + + FS_Api::clear_force_http_flag(); + } + /** * @author Vova Feldman (@svovaf) * @since 1.1.7.4 * - * @param object $pong - * @param bool $is_connected + * @param object $pong + * @param bool|null $is_connected */ private function store_connectivity_info( $pong, $is_connected ) { $this->_logger->entrance(); $version = $this->get_plugin_version(); - if ( ! $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) { + if ( false === $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) { $is_active = false; } else { $is_active = ( isset( $pong->is_active ) && true == $pong->is_active ); @@ -4017,6 +4207,20 @@ private function store_connectivity_info( $pong, $is_connected ) { $this->_is_on = $is_active || ( WP_FS__DEV_MODE && $is_connected && ! WP_FS__SIMULATE_FREEMIUS_OFF ); } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param bool $is_connected + */ + private function update_connectivity_info( $is_connected ) { + $this->store_connectivity_info( + // This is true since we update the connection info only after a successful opt-in or license activation which means that Freemius has already been on even before the process. + (object) array( 'is_active' => true ), + $is_connected + ); + } + /** * Force turning Freemius on. * @@ -4056,9 +4260,9 @@ function get_anonymous_id( $blog_id = null ) { $unique_id = self::$_accounts->get_option( 'unique_id', null, $blog_id ); if ( empty( $unique_id ) || ! is_string( $unique_id ) ) { - $key = fs_strip_url_protocol( get_site_url( $blog_id ) ); + $key = self::get_unfiltered_site_url( $blog_id, true ); - $secure_auth = SECURE_AUTH_KEY; + $secure_auth = defined( 'SECURE_AUTH_KEY' ) ? SECURE_AUTH_KEY : ''; if ( empty( $secure_auth ) || false !== strpos( $secure_auth, ' ' ) || 'put your unique phrase here' === $secure_auth @@ -4085,6 +4289,17 @@ function get_anonymous_id( $blog_id = null ) { return $unique_id; } + /** + * Returns anonymous network ID. + * + * @since 2.4.3 + * + * @return string + */ + function get_anonymous_network_id() { + return $this->get_anonymous_id( get_network()->site_id ); + } + /** * @author Vova Feldman (@svovaf) * @since 1.1.7.4 @@ -4198,377 +4413,24 @@ static function is_valid_email( $email ) { } // Get the UTF encoded domain name. - $domain = idn_to_ascii( $parts[1] ) . '.'; - - return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) ); - } - - /** - * Generate API connectivity issue message. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param mixed $api_result - * @param bool $is_first_failure - */ - function _add_connectivity_issue_message( $api_result, $is_first_failure = true ) { - if ( ! $this->is_premium() && $this->_enable_anonymous ) { - // Don't add message if it's the free version and can run anonymously. - return; - } - - if ( ! function_exists( 'wp_nonce_url' ) ) { - require_once ABSPATH . 'wp-includes/functions.php'; - } - - $current_user = self::_get_current_wp_user(); -// $admin_email = get_option( 'admin_email' ); - $admin_email = $current_user->user_email; - - // Aliases. - $deactivate_plugin_title = $this->esc_html_inline( 'That\'s exhausting, please deactivate', 'deactivate-plugin-title' ); - $deactivate_plugin_desc = $this->esc_html_inline( 'We feel your frustration and sincerely apologize for the inconvenience. Hope to see you again in the future.', 'deactivate-plugin-desc' ); - $install_previous_title = $this->esc_html_inline( 'Let\'s try your previous version', 'install-previous-title' ); - $install_previous_desc = $this->esc_html_inline( 'Uninstall this version and install the previous one.', 'install-previous-desc' ); - $fix_issue_title = $this->esc_html_inline( 'Yes - I\'m giving you a chance to fix it', 'fix-issue-title' ); - $fix_issue_desc = $this->esc_html_inline( 'We will do our best to whitelist your server and resolve this issue ASAP. You will get a follow-up email to %s once we have an update.', 'fix-issue-desc' ); - /* translators: %s: product title (e.g. "Awesome Plugin" requires an access to...) */ - $x_requires_access_to_api = $this->esc_html_inline( '%s requires an access to our API.', 'x-requires-access-to-api' ); - $sysadmin_title = $this->esc_html_inline( 'I\'m a system administrator', 'sysadmin-title' ); - $happy_to_resolve_issue_asap = $this->esc_html_inline( 'We are sure it\'s an issue on our side and more than happy to resolve it for you ASAP if you give us a chance.', 'happy-to-resolve-issue-asap' ); - - $message = false; - if ( is_object( $api_result ) && - isset( $api_result->error ) && - isset( $api_result->error->code ) - ) { - switch ( $api_result->error->code ) { - case 'curl_missing': - $missing_methods = ''; - if ( is_array( $api_result->missing_methods ) && - ! empty( $api_result->missing_methods ) - ) { - foreach ( $api_result->missing_methods as $m ) { - if ( 'curl_version' === $m ) { - continue; - } - - if ( ! empty( $missing_methods ) ) { - $missing_methods .= ', '; - } - - $missing_methods .= sprintf( '%s', $m ); - } - - if ( ! empty( $missing_methods ) ) { - $missing_methods = sprintf( - '

          %s %s', - $this->esc_html_inline( 'Disabled method(s):', 'curl-disabled-methods' ), - $missing_methods - ); - } - } - - $message = sprintf( - $x_requires_access_to_api . ' ' . - $this->esc_html_inline( 'We use PHP cURL library for the API calls, which is a very common library and usually installed and activated out of the box. Unfortunately, cURL is not activated (or disabled) on your server.', 'curl-missing-message' ) . ' ' . - $missing_methods . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
          1. %s
          2. %s
          3. %s
          ', - sprintf( - '%s%s', - $this->get_text_inline( 'I don\'t know what is cURL or how to install it, help me!', 'curl-missing-no-clue-title' ), - ' - ' . sprintf( - $this->get_text_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'curl-missing-no-clue-desc' ), - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - $sysadmin_title, - esc_html( sprintf( $this->get_text_inline( 'Great, please install cURL and enable it in your php.ini file. In addition, search for the \'disable_functions\' directive in your php.ini file and remove any disabled methods starting with \'curl_\'. To make sure it was successfully activated, use \'phpinfo()\'. Once activated, deactivate the %s and reactivate it back again.', 'curl-missing-sysadmin-desc' ), $this->get_module_label( true ) ) ) - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - break; - case 'cloudflare_ddos_protection': - $message = sprintf( - $x_requires_access_to_api . ' ' . - $this->esc_html_inline( 'From unknown reason, CloudFlare, the firewall we use, blocks the connection.', 'cloudflare-blocks-connection-message' ) . ' ' . - $happy_to_resolve_issue_asap . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
          1. %s
          2. %s
          3. %s
          ', - sprintf( - '%s%s', - $fix_issue_title, - ' - ' . sprintf( - $fix_issue_desc, - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ), - $install_previous_title, - $install_previous_desc - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=' . '', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - break; - case 'squid_cache_block': - $message = sprintf( - $x_requires_access_to_api . ' ' . - $this->esc_html_inline( 'It looks like your server is using Squid ACL (access control lists), which blocks the connection.', 'squid-blocks-connection-message' ) . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
          1. %s
          2. %s
          3. %s
          ', - sprintf( - '%s - %s', - $this->esc_html_inline( 'I don\'t know what is Squid or ACL, help me!', 'squid-no-clue-title' ), - sprintf( - $this->esc_html_inline( 'We\'ll make sure to contact your hosting company and resolve the issue. You will get a follow-up email to %s once we have an update.', 'squid-no-clue-desc' ), - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - $sysadmin_title, - sprintf( - $this->esc_html_inline( 'Great, please whitelist the following domains: %s. Once you are done, deactivate the %s and activate it again.', 'squid-sysadmin-desc' ), - // We use a filter since the plugin might require additional API connectivity. - '' . implode( ', ', $this->apply_filters( 'api_domains', array( - 'api.freemius.com', - 'wp.freemius.com' - ) ) ) . '', - $this->_module_type - ) - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - break; -// default: -// $message = $this->get_text_inline( 'connectivity-test-fails-message' ); -// break; - } - } - - $message_id = 'failed_connect_api'; - $type = 'error'; - - $connectivity_test_fails_message = $this->esc_html_inline( 'From unknown reason, the API connectivity test failed.', 'connectivity-test-fails-message' ); - - if ( false === $message ) { - if ( $is_first_failure ) { - // First attempt failed. - $message = sprintf( - $x_requires_access_to_api . ' ' . - $connectivity_test_fails_message . ' ' . - $this->esc_html_inline( 'It\'s probably a temporary issue on our end. Just to be sure, with your permission, would it be o.k to run another connectivity test?', 'connectivity-test-maybe-temporary' ) . '

          ' . - '%s', - '' . $this->get_plugin_name() . '', - sprintf( - '
          %s %s
          ', - sprintf( - '%s', - $this->get_text_inline( 'Yes - do your thing', 'yes-do-your-thing' ) - ), - sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $this->get_text_inline( 'No - just deactivate', 'no-deactivate' ) - ) - ) - ); - - $message_id = 'failed_connect_api_first'; - $type = 'promotion'; - } else { - // Second connectivity attempt failed. - $message = sprintf( - $x_requires_access_to_api . ' ' . - $connectivity_test_fails_message . ' ' . - $happy_to_resolve_issue_asap . - ' %s', - '' . $this->get_plugin_name() . '', - sprintf( - '
          1. %s
          2. %s
          3. %s
          ', - sprintf( - '%s%s', - $fix_issue_title, - ' - ' . sprintf( - $fix_issue_desc, - '' . $admin_email . '' - ) - ), - sprintf( - '%s - %s', - sprintf( 'https://wordpress.org/plugins/%s/download/', $this->_slug ), - $install_previous_title, - $install_previous_desc - ), - sprintf( - '%s - %s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $this->_plugin_basename . '&plugin_status=all&paged=1&s=', 'deactivate-plugin_' . $this->_plugin_basename ), - $deactivate_plugin_title, - $deactivate_plugin_desc - ) - ) - ); - } - } - - $this->_admin_notices->add_sticky( - $message, - $message_id, - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - $type - ); - } - - /** - * Handle user request to resolve connectivity issue. - * This method will send an email to Freemius API technical staff for resolution. - * The email will contain server's info and installed plugins (might be caching issue). - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - */ - function _email_about_firewall_issue() { - check_admin_referer( 'fs_resolve_firewall_issues' ); - - if ( ! current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) { - return; - } - - $this->_admin_notices->remove_sticky( 'failed_connect_api' ); - - $pong = $this->ping(); - - $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); - - if ( $is_connected ) { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); - - $this->store_connectivity_info( $pong, $is_connected ); - - echo $this->get_after_plugin_activation_redirect_url(); - exit; - } - - $current_user = self::_get_current_wp_user(); - $admin_email = $current_user->user_email; - - $error_type = fs_request_get( 'error_type', 'general' ); - - switch ( $error_type ) { - case 'squid': - $title = 'Squid ACL Blocking Issue'; - break; - case 'cloudflare': - $title = 'CloudFlare Blocking Issue'; - break; - default: - $title = 'API Connectivity Issue'; - break; - } - - $custom_email_sections = array(); - - // Add 'API Error' custom email section. - $custom_email_sections['api_error'] = array( - 'title' => 'API Error', - 'rows' => array( - 'ping' => array( - 'API Error', - is_string( $pong ) ? htmlentities( $pong ) : json_encode( $pong ) - ), - ) - ); - - // Send email with technical details to resolve API connectivity issues. - $this->send_email( - 'api@freemius.com', // recipient - $title . ' [' . $this->get_plugin_name() . ']', // subject - $custom_email_sections, - array( "Reply-To: $admin_email <$admin_email>" ) // headers - ); - - $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'Thank for giving us the chance to fix it! A message was just sent to our technical staff. We will get back to you as soon as we have an update to %s. Appreciate your patience.', 'fix-request-sent-message' ), - '' . $admin_email . '' - ), - 'server_details_sent' + /** + * @note - The check of `defined('...')` is there to account for PHP servers compiled with some older version of ICU where the constants are not defined. + * @author - @swashata + */ + $is_new_idn_available = ( + version_compare( PHP_VERSION, '5.6.40') > 0 && + defined( 'IDNA_DEFAULT' ) && + defined( 'INTL_IDNA_VARIANT_UTS46' ) ); - - // Action was taken, tell that API connectivity troubleshooting should be off now. - - echo "1"; - exit; - } - - /** - * Handle connectivity test retry approved by the user. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.7.4 - */ - function _retry_connectivity_test() { - check_admin_referer( 'fs_retry_connectivity_test' ); - - if ( ! current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) { - return; - } - - $this->_admin_notices->remove_sticky( 'failed_connect_api_first' ); - - $pong = $this->ping(); - - $is_connected = $this->get_api_plugin_scope()->is_valid_ping( $pong ); - - if ( $is_connected ) { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); - - $this->store_connectivity_info( $pong, $is_connected ); - - echo $this->get_after_plugin_activation_redirect_url(); + if ( $is_new_idn_available ) { + $domain = idn_to_ascii( $parts[1], IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46 ); } else { - // Add connectivity issue message after 2nd failed attempt. - $this->_add_connectivity_issue_message( $pong, false ); - - echo "1"; + $domain = idn_to_ascii( $parts[1] ); // phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet } - exit; - } + $domain = $domain . '.'; - static function _add_firewall_issues_javascript() { - $params = array(); - fs_require_once_template( 'firewall-issues-js.php', $params ); + return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) ); } #endregion @@ -4805,6 +4667,36 @@ function dynamic_init( array $plugin_info ) { $this->register_after_settings_parse_hooks(); + /** + * If anonymous but there's already a user entity and the user's site is associated with a valid license or trial period, update the anonymous mode accordingly. + * + * @todo Remove this entire `if` block after several releases as starting from this version, the anonymous mode will already be updated accordingly after a purchase. + */ + if ( $this->is_anonymous() ) { + $is_network_level = ( $this->_is_network_active && fs_is_network_admin() ); + + if ( + ! $is_network_level || + FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) + ) { + if ( $this->is_paying_or_trial() ) { + $this->reset_anonymous_mode( $is_network_level ); + } + } else { + $network = get_network(); + + if ( is_object( $network ) ) { + $main_blog_id = $network->site_id; + $first_install = $this->get_install_by_blog_id( $main_blog_id ); + + if ( is_object( $first_install ) ) { + $this->_storage->network_install_blog_id = $main_blog_id; + $this->_storage->network_user_id = $first_install->user_id; + } + } + } + } + if ( $this->should_stop_execution() ) { return; } @@ -4815,50 +4707,9 @@ function dynamic_init( array $plugin_info ) { $this->_has_api_connection = true; $this->_is_on = true; } else { - if ( ! $this->has_api_connectivity() ) { - if ( $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) || - $this->_admin_notices->has_sticky( 'failed_connect_api' ) - ) { - if ( ! $this->_enable_anonymous || $this->is_premium() ) { - // If anonymous mode is disabled, add firewall admin-notice message. - add_action( 'admin_footer', array( 'Freemius', '_add_firewall_issues_javascript' ) ); - - $ajax_action_suffix = $this->_slug . ( $this->is_theme() ? ':theme' : '' ); - add_action( "wp_ajax_fs_resolve_firewall_issues_{$ajax_action_suffix}", array( - &$this, - '_email_about_firewall_issue' - ) ); - - add_action( "wp_ajax_fs_retry_connectivity_test_{$ajax_action_suffix}", array( - &$this, - '_retry_connectivity_test' - ) ); - - /** - * Currently the admin notice manager relies on the module's type and slug. The new AJAX actions manager uses module IDs, hence, consider to replace the if block above with the commented code below after adjusting the admin notices manager to work with module IDs. - * - * @author Vova Feldman (@svovaf) - * @since 2.0.0 - */ - /*$this->add_ajax_action( 'resolve_firewall_issues', array( - &$this, - '_email_about_firewall_issue' - ) ); - - $this->add_ajax_action( 'retry_connectivity_test', array( - &$this, - '_retry_connectivity_test' - ) );*/ - } - } - + if ( false === $this->has_api_connectivity() ) { return; } else { - $this->_admin_notices->remove_sticky( array( - 'failed_connect_api_first', - 'failed_connect_api', - ) ); - if ( $this->_anonymous_mode ) { // Simulate anonymous mode. $this->_is_anonymous = true; @@ -4869,7 +4720,7 @@ function dynamic_init( array $plugin_info ) { /** * This should be executed even if Freemius is off for the core module, - * otherwise, the add-ons dialogbox won't work properly. This is esepcially + * otherwise, the add-ons dialog box won't work properly. This is especially * relevant when the developer decided to turn FS off for existing users. * * @author Vova Feldman (@svovaf) @@ -4907,22 +4758,25 @@ function dynamic_init( array $plugin_info ) { * @since 1.1.7.3 * */ - if ( $this->is_registered() ) { - if ( ! $this->is_sync_cron_on() && $this->is_tracking_allowed() ) { - $this->schedule_sync_cron(); - } + if ( $this->is_registered() && $this->is_tracking_allowed() ) { + $this->maybe_schedule_sync_cron(); } /** * Check if requested for manual blocking background sync. */ if ( fs_request_has( 'background_sync' ) ) { + self::require_pluggable_essentials(); + self::wp_cookie_constants(); + $this->run_manual_sync(); } } } if ( $this->is_registered() ) { + FS_Clone_Manager::instance()->maybe_resolve_new_subsite_install_automatically( $this ); + $this->hook_callback_to_install_sync(); } @@ -4937,6 +4791,28 @@ function dynamic_init( array $plugin_info ) { } if ( $this->is_user_in_admin() ) { + if ( $this->is_registered() && fs_request_has( 'purchase_completed' ) ) { + $this->_admin_notices->add_sticky( + sprintf( + /* translators: %s: License type (e.g. you have a professional license) */ + $this->get_text_inline( 'You have purchased a %s license.', 'you-have-x-license' ), + fs_request_get( 'purchased_plan' ) + ) . + sprintf( + $this->get_text_inline(" The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box.", 'post-purchase-email-sent-message' ), + $this->get_module_label( true ), + ( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? "products' " : '' ), + ( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? 's' : '' ), + sprintf( + '%s', + fs_request_get( 'purchase_email' ) + ) + ), + 'plan_purchased', + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + if ( $this->is_addon() ) { if ( ! $this->is_parent_plugin_installed() ) { $parent_name = $this->get_option( $plugin_info, 'parent_name', null ); @@ -5041,7 +4917,7 @@ function dynamic_init( array $plugin_info ) { * because the updater has some logic that needs to be executed * during AJAX calls. * - * Currently we need to hook to the `http_request_host_is_external` filter. + * Currently, we need to hook to the `http_request_host_is_external` filter. * In the future, there might be additional logic added. * * @author Vova Feldman @@ -5060,7 +4936,8 @@ function dynamic_init( array $plugin_info ) { */ ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->premium_plugin_basename() ) ) ) ) && - $this->has_release_on_freemius() + $this->has_release_on_freemius() && + ( ! $this->is_unresolved_clone( true ) ) ) { FS_Plugin_Updater::instance( $this ); } @@ -5114,165 +4991,85 @@ private function should_use_freemius_updater_and_dialog() { return ( /** * Allow updater and dialog when the `fs_allow_updater_and_dialog` URL query param exists and has `true` - * value, or when the current page is not the "Add Plugins" page (/plugin-install.php) and the `action` - * URL query param doesn't exist or its value is not `install-plugin` so that there will be no conflicts - * with the .org plugins' functionalities (e.g. installation from the "Add Plugins" page and viewing - * plugin details from .org). - */ - ( true === fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) || - ( - ! self::is_plugin_install_page() && - // Disallow updater and dialog when installing a plugin, otherwise .org "add-on" plugins will be affected. - ( 'install-plugin' !== fs_request_get( 'action' ) ) - ) - ); - } - - /** - * @author Leo Fajardo (@leorw) - * - * @since 1.2.1.5 - */ - function _stop_tracking_callback() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'stop_tracking' ); - - $result = $this->stop_tracking( fs_is_network_admin() ); - - if ( true === $result ) { - self::shoot_ajax_success(); - } - - $this->_logger->api_error( $result ); - - self::shoot_ajax_failure( - sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) . - ( $this->is_api_error( $result ) && isset( $result->error ) ? - $result->error->message : - var_export( $result, true ) ) - ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - */ - function _allow_tracking_callback() { - $this->_logger->entrance(); - - $this->check_ajax_referer( 'allow_tracking' ); - - $result = $this->allow_tracking( fs_is_network_admin() ); - - if ( true === $result ) { - self::shoot_ajax_success(); - } - - $this->_logger->api_error( $result ); - - self::shoot_ajax_failure( - sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) . - ( $this->is_api_error( $result ) && isset( $result->error ) ? - $result->error->message : - var_export( $result, true ) ) + * value, or when the current page is not the "Add Plugins" page (/plugin-install.php) and the `action` + * URL query param doesn't exist or its value is not `install-plugin` so that there will be no conflicts + * with the .org plugins' functionalities (e.g. installation from the "Add Plugins" page and viewing + * plugin details from .org). + */ + ( true === fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) || + ( + ! self::is_plugin_install_page() && + // Disallow updater and dialog when installing a plugin, otherwise .org "add-on" plugins will be affected. + ( 'install-plugin' !== fs_request_get( 'action' ) ) + ) ); } /** - * Opt-out from usage tracking. - * - * Note: This will not delete the account information but will stop all tracking. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-out. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 + * @param string[] $permissions + * @param bool $is_enabled + * @param int|null $blog_id * - * @return bool|object + * @return true|object `true` on success, API error object on failure. */ - function stop_site_tracking() { + private function update_site_permissions( array $permissions, $is_enabled, $blog_id = null ) { $this->_logger->entrance(); - if ( ! $this->is_registered() ) { - // User never opted-in. - return false; - } + $params = array( + 'permissions' => implode( ',', $permissions ), + 'is_enabled' => $is_enabled, + ); - if ( $this->is_tracking_prohibited() ) { - // Already disconnected. - return true; + $current_blog_id = get_current_blog_id(); + $is_blog_switched = false; + if ( is_numeric( $blog_id ) && $current_blog_id != $blog_id ) { + $is_blog_switched = $this->switch_to_blog( $blog_id ); } - // Send update to FS. - $result = $this->get_api_site_scope()->call( '/?fields=is_disconnected', 'put', array( - 'is_disconnected' => true - ) ); + $result = $this->api_site_call( '/permissions.json', 'put', $params ); + + if ( $is_blog_switched ) { + $this->switch_to_blog( $current_blog_id ); + } - if ( ! $this->is_api_result_entity( $result ) || - ! isset( $result->is_disconnected ) || - ! $result->is_disconnected + if ( + ! $this->is_api_result_object( $result ) || + ! isset( $result->install_id ) ) { $this->_logger->api_error( $result ); return $result; } - $this->_site->is_disconnected = $result->is_disconnected; - $this->_store_site(); - - $this->clear_sync_cron(); - - // Successfully disconnected. return true; } /** - * Opt-out network from usage tracking. - * - * Note: This will not delete the account information but will stop all tracking. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-out. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 + * @param string[] $permissions + * @param bool $is_enabled + * @param bool $has_site_delegated_connection * - * @return bool|object + * @return true|object `true` on success, API error object on failure. */ - function stop_network_tracking() { + private function update_network_permissions( + array $permissions, + $is_enabled, + &$has_site_delegated_connection + ) { $this->_logger->entrance(); - if ( ! $this->is_registered() ) { - // User never opted-in. - return false; - } - $install_id_2_blog_id = array(); - $installs_map = $this->get_blog_install_map(); + $install_by_blog_id = $this->get_blog_install_map(); - $opt_out_all = true; - - $params = array(); - foreach ( $installs_map as $blog_id => $install ) { - if ( $install->is_tracking_prohibited() ) { - // Already opted-out. - continue; - } + $has_site_delegated_connection = false; + foreach ( $install_by_blog_id as $blog_id => $install ) { if ( $this->is_site_delegated_connection( $blog_id ) ) { - // Opt-out only from non-delegated installs. - $opt_out_all = false; + // Only update permissions of non-delegated installs. + $has_site_delegated_connection = true; continue; } - $params[] = array( 'id' => $install->id ); - $install_id_2_blog_id[ $install->id ] = $blog_id; } @@ -5280,171 +5077,188 @@ function stop_network_tracking() { return true; } - $params[] = array( 'is_disconnected' => true ); + $params = array( + 'permissions' => implode( ',', $permissions ), + 'is_enabled' => $is_enabled, + 'install_ids' => implode( ',', array_keys( $install_id_2_blog_id ) ), + ); // Send update to FS. - $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json", 'put', $params ); + $result = $this->get_current_or_network_user_api_scope()->call( + "/plugins/{$this->_module_id}/installs/permissions.json", + 'put', + $params + ); - if ( ! $this->is_api_result_object( $result, 'installs' ) ) { + if ( ! $this->is_api_result_object( $result, 'installs_metadata' ) ) { $this->_logger->api_error( $result ); return $result; } - foreach ( $result->installs as $r_install ) { - $blog_id = $install_id_2_blog_id[ $r_install->id ]; - $install = $installs_map[ $blog_id ]; - $install->is_disconnected = $r_install->is_disconnected; - $this->_store_site( true, $blog_id, $install ); - } - - $this->clear_sync_cron( $opt_out_all ); - - // Successfully disconnected. return true; } /** - * Opt-out from usage tracking. - * - * Note: This will not delete the account information but will stop all tracking. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-out. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @param bool $is_network_action + * @param mixed $result * - * @return bool|object + * @return string */ - function stop_tracking( $is_network_action = false ) { - $this->_logger->entrance(); + private function get_api_error_message( $result ) { + $error_message = sprintf( $this->get_text_inline( 'There was an unexpected API error while processing your request. Please try again in a few minutes and if it still doesn\'t work, contact the %s\'s author with the following:', + 'unexpected-api-error' ), $this->_module_type ) . ' '; - return $is_network_action ? - $this->stop_network_tracking() : - $this->stop_site_tracking(); + if ( + $this->is_api_error( $result ) && + isset( $result->error ) + ) { + $code = empty( $result->error->code ) ? '' : " Code: {$result->error->code}"; + + $error_message .= "{$result->error->message}{$code}"; + } else { + $error_message .= var_export( $result, true ); + } + + return $error_message; } /** - * Opt-in back into usage tracking. - * - * Note: This will only work if the user opted-in previously. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-in back to usage tracking. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 - * - * @return bool|object + * @author Vova Feldman (@svovaf) + * @since 2.5.1 */ - function allow_site_tracking() { + function _toggle_permission_tracking_callback() { $this->_logger->entrance(); - if ( ! $this->is_registered() ) { - // User never opted-in. - return false; - } + $this->check_ajax_referer( 'toggle_permission_tracking' ); - if ( $this->is_tracking_allowed() ) { - // Tracking already allowed. - return true; + if ( ! $this->is_registered( true ) ) { + self::shoot_ajax_failure( 'User never opted-in.' ); } - $result = $this->get_api_site_scope()->call( '/?is_disconnected', 'put', array( - 'is_disconnected' => false - ) ); - - if ( ! $this->is_api_result_entity( $result ) || - ! isset( $result->is_disconnected ) || - $result->is_disconnected - ) { - $this->_logger->api_error( $result ); + $is_enabled = fs_request_get_bool( 'is_enabled' ); + $permissions = fs_request_get( 'permissions' ); - return $result; + if ( ! is_string( $permissions ) ) { + self::shoot_ajax_failure( 'The permissions param must be a string.' ); } - $this->_site->is_disconnected = $result->is_disconnected; - $this->_store_site(); + $permissions = explode( ',', $permissions ); - $this->schedule_sync_cron(); + $result = $this->toggle_permission_tracking( $permissions, $is_enabled ); - // Successfully reconnected. - return true; + if ( true !== $result ) { + self::shoot_ajax_failure( $this->get_api_error_message( $result ) ); + } + + self::shoot_ajax_success(); } /** - * Opt-in network back into usage tracking. - * - * Note: This will only work if the user opted-in previously. - * - * Returns: - * 1. FALSE - If the user never opted-in. - * 2. TRUE - If successfully opted-in back to usage tracking. - * 3. object - API result on failure. - * - * @author Leo Fajardo (@leorw) - * @since 1.2.1.5 + * @param string[] $permissions + * @param bool $is_enabled + * @param int|null $blog_id * - * @return bool|object + * @return bool|mixed `true` if updated successfully or no update is needed. */ - function allow_network_tracking() { - $this->_logger->entrance(); - - if ( ! $this->is_registered() ) { + private function toggle_permission_tracking( $permissions, $is_enabled, $blog_id = null ) { + if ( ! $this->is_registered( true ) ) { // User never opted-in. return false; } - $install_id_2_blog_id = array(); - $installs_map = $this->get_blog_install_map(); - - $params = array(); - foreach ( $installs_map as $blog_id => $install ) { - if ( $install->is_tracking_allowed() ) { - continue; - } - - $params[] = array( 'id' => $install->id ); - - $install_id_2_blog_id[ $install->id ] = $blog_id; + // Check if permissions are already set as needed. + if ( FS_Permission_Manager::instance( $this )->are_permissions( $permissions, $is_enabled, $blog_id ) ) { + /** + * Note: + * When running on the network admin, there's no need to iterate through all the installs individually since network opt-in permissions are managed for ALL non-delegated installs through a single option (per permission) on the network-level storage. + */ + return true; } - if ( empty( $install_id_2_blog_id ) ) { - return true; + $api_managed_permissions = array_intersect( + $permissions, + FS_Permission_Manager::get_api_managed_permission_ids() + ); + + if ( + in_array( FS_Permission_Manager::PERMISSION_ESSENTIALS, $permissions ) && + ! in_array( FS_Permission_Manager::PERMISSION_SITE, $permissions ) + ) { + $api_managed_permissions[] = FS_Permission_Manager::PERMISSION_SITE; } - $params[] = array( 'is_disconnected' => false ); + if ( ! empty( $api_managed_permissions ) ) { + $has_site_delegated_connection = false; - // Send update to FS. - $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json", 'put', $params ); + if ( + ! $is_enabled && + ! in_array( FS_Permission_Manager::PERMISSION_EXTENSIONS, $api_managed_permissions ) && + false === FS_Permission_Manager::instance( $this )->is_extensions_tracking_allowed( $blog_id ) + ) { + /** + * If we are turning off a permission and the extensions permission is off too, enrich the permissions update request to also turn off extensions tracking, as currently when opting in with extensions tracking disabled the extensions tracking is off but the API isn't aware of it. + * + * @todo Remove this entire `if` after implementing granular opt-in that also sends the permissions to the API when opting in. + */ + $api_managed_permissions[] = FS_Permission_Manager::PERMISSION_EXTENSIONS; + } + if ( is_null( $blog_id ) && fs_is_network_admin() ) { + $result = $this->update_network_permissions( + $api_managed_permissions, + $is_enabled, + $has_site_delegated_connection + ); + } else { + $result = $this->update_site_permissions( + $api_managed_permissions, + $is_enabled, + $blog_id + ); + } - if ( ! $this->is_api_result_object( $result, 'installs' ) ) { - $this->_logger->api_error( $result ); + if ( true !== $result ) { + return $result; + } - return $result; - } + if ( in_array( FS_Permission_Manager::PERMISSION_SITE, $api_managed_permissions ) ) { + if ( $is_enabled ) { + $this->schedule_sync_cron(); + } else { + $this->clear_sync_cron( ! $has_site_delegated_connection ); + } + } - foreach ( $result->installs as $r_install ) { - $blog_id = $install_id_2_blog_id[ $r_install->id ]; - $install = $installs_map[ $blog_id ]; - $install->is_disconnected = $r_install->is_disconnected; - $this->_store_site( true, $blog_id, $install ); + if ( in_array( FS_Permission_Manager::PERMISSION_USER, $api_managed_permissions ) ) { + $this->toggle_user_permission( $is_enabled, $blog_id ); + } } - $this->schedule_sync_cron(); + $this->update_tracking_permissions( + $permissions, + $is_enabled, + $blog_id + ); - // Successfully reconnected. return true; } + /** + * @param bool $is_enabled + * @param int|null $blog_id + */ + private function toggle_user_permission( $is_enabled, $blog_id = null ) { + $network_or_blog_ids = is_numeric( $blog_id ) ? + $blog_id : + fs_is_network_admin(); + + if ( $is_enabled ) { + $this->reset_anonymous_mode( $network_or_blog_ids ); + } else { + $this->skip_connection( $network_or_blog_ids ); + } + } + /** * Opt-in back into usage tracking. * @@ -5458,16 +5272,18 @@ function allow_network_tracking() { * @author Leo Fajardo (@leorw) * @since 1.2.1.5 * - * @param bool $is_network_action + * @bool $is_enabled * * @return bool|object */ - function allow_tracking( $is_network_action = false ) { + private function toggle_site_tracking( $is_enabled, $blog_id = null ) { $this->_logger->entrance(); - return $is_network_action ? - $this->allow_network_tracking() : - $this->allow_site_tracking(); + return $this->toggle_permission_tracking( + FS_Permission_Manager::instance( $this )->get_site_tracking_permission_names(), + $is_enabled, + $blog_id + ); } /** @@ -5488,8 +5304,7 @@ private function reconnect_locally( $is_context_single_site = false ) { if ( ! fs_is_network_admin() || $is_context_single_site ) { if ( $this->is_tracking_prohibited() ) { - $this->_site->is_disconnected = false; - $this->_store_site(); + FS_Permission_Manager::instance( $this )->update_site_tracking( true ); } } else { $installs_map = $this->get_blog_install_map(); @@ -5497,73 +5312,69 @@ private function reconnect_locally( $is_context_single_site = false ) { /** * @var FS_Site $install */ - if ( $install->is_tracking_prohibited() ) { - $install->is_disconnected = false; - $this->_store_site( true, $blog_id, $install ); + if ( ! $this->is_tracking_allowed( $blog_id, $install ) ) { + FS_Permission_Manager::instance( $this )->update_site_tracking( true, $blog_id ); } } } } /** - * @author Vova Feldman (@svovaf) - * @since 2.3.2 + * Update permission tracking flags. When updating in a network context, in addition to updating the network-level flags, also update the permissions on the site-level for all non-delegated sites. * - * @return bool + * @param string[] $permissions + * @param bool $is_enabled + * @param int|null $blog_id + * + * @return array */ - function is_extensions_tracking_allowed() { - return ( true === $this->apply_filters( - 'is_extensions_tracking_allowed', - $this->_storage->get( 'is_extensions_tracking_allowed', null ) - ) ); - } + private function update_tracking_permissions( $permissions, $is_enabled, $blog_id = null ) { + // Alias. + $permission_manager = FS_Permission_Manager::instance( $this ); - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.2 - */ - function _update_tracking_permission_callback() { - $this->_logger->entrance(); + $network_or_blog_ids = is_numeric( $blog_id ) ? + $blog_id : + fs_is_network_admin(); - $this->check_ajax_referer( 'update_tracking_permission' ); + if ( true === $network_or_blog_ids ) { + // Update the permission for all non-delegated sub-sites. + $blog_ids = $this->get_non_delegated_blog_ids(); - $is_enabled = fs_request_get_bool( 'is_enabled', null ); + // Add the network-level to the array, to update the permission on the network-level storage. + array_unshift( $blog_ids, null ); + } + else + { + if ( false === $network_or_blog_ids ) { + $network_or_blog_ids = null; + } - if ( ! is_bool( $is_enabled ) ) { - self::shoot_ajax_failure(); + $blog_ids = is_array( $network_or_blog_ids ) ? + $network_or_blog_ids : + array( $network_or_blog_ids ); } - $permission = fs_request_get( 'permission' ); + $result = array(); + foreach ( $permissions as $permission ) { + $permission = trim( $permission ); + $is_permission_supported = true; + + foreach ( $blog_ids as $id ) { + $is_permission_supported = $permission_manager->update_permission_tracking_flag( + $permission, + $is_enabled, + $id + ); + } - switch ( $permission ) { - case 'extensions': - $this->update_extensions_tracking_flag( $is_enabled ); - break; - default: + if ( ! $is_permission_supported ) { $permission = 'no_match'; - } + } - if ( 'no_match' === $permission ) { - self::shoot_ajax_failure(); + $result[ $permission ] = $is_enabled; } - self::shoot_ajax_success( array( - 'permissions' => array( - $permission => $is_enabled, - ) - ) ); - } - - /** - * @author Leo Fajardo (@leorw) - * @since 2.3.2 - * - * @param bool|null $is_enabled - */ - function update_extensions_tracking_flag( $is_enabled ) { - if ( is_bool( $is_enabled ) ) { - $this->_storage->store( 'is_extensions_tracking_allowed', $is_enabled ); - } + return $result; } /** @@ -5643,6 +5454,7 @@ private function parse_settings( &$plugin_info ) { 'affiliate_moderation' => $this->get_option( $plugin_info, 'has_affiliation' ), 'bundle_id' => $this->get_option( $plugin_info, 'bundle_id', null ), 'bundle_public_key' => $this->get_option( $plugin_info, 'bundle_public_key', null ), + 'opt_in_moderation' => $this->get_option( $plugin_info, 'opt_in', null ), ) ); if ( $plugin->is_updated() ) { @@ -5769,8 +5581,7 @@ private function should_stop_execution() { if ( $this->is_activation_mode() ) { if ( ! is_admin() ) { /** - * If in activation mode, don't execute Freemius outside of the - * admin dashboard. + * If in activation mode, don't execute Freemius outside the admin dashboard. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 @@ -5802,10 +5613,7 @@ private function should_stop_execution() { return true; } - if ( self::is_ajax() && - ! $this->_admin_notices->has_sticky( 'failed_connect_api_first' ) && - ! $this->_admin_notices->has_sticky( 'failed_connect_api' ) - ) { + if ( self::is_ajax() ) { /** * During activation, if running in AJAX mode, unless there's a sticky * connectivity issue notice, don't run Freemius. @@ -5836,19 +5644,17 @@ function _after_code_type_change() { $this->_cache->expire( 'tabs_stylesheets' ); } - if ( $this->is_registered() ) { - if ( ! $this->is_addon() ) { - add_action( - is_admin() ? 'admin_init' : 'init', - array( &$this, '_plugin_code_type_changed' ) - ); - } + if ( ! $this->is_addon() ) { + add_action( + is_admin() ? 'admin_init' : 'init', + array( &$this, '_plugin_code_type_changed' ) + ); + } - if ( $this->is_premium() ) { - // Purge cached payments after switching to the premium version. - // @todo This logic doesn't handle purging the cache for serviceware module upgrade. - $this->get_api_user_scope()->purge_cache( "/plugins/{$this->_module_id}/payments.json?include_addons=true" ); - } + if ( $this->is_registered() && $this->is_premium() ) { + // Purge cached payments after switching to the premium version. + // @todo This logic doesn't handle purging the cache for serviceware module upgrade. + $this->get_api_user_scope()->purge_cache( "/plugins/{$this->_module_id}/payments.json?include_addons=true" ); } } @@ -5901,20 +5707,21 @@ function _plugin_code_type_changed() { $this->do_action( 'after_free_version_reactivation' ); if ( $this->is_paying() && ! $this->is_premium() ) { - $this->_admin_notices->add_sticky( + $this->add_complete_upgrade_instructions_notice( sprintf( /* translators: %s: License type (e.g. you have a professional license) */ $this->get_text_inline( 'You have a %s license.', 'you-have-x-license' ), $this->get_plan_title() - ) . $this->get_complete_upgrade_instructions(), - 'plan_upgraded', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ), + 'plan_upgraded' ); } } - // Schedule code type changes event. - $this->schedule_install_sync(); + if ( $this->is_registered() ) { + // Schedule code type changes event. + $this->schedule_install_sync(); + } /** * Unregister the uninstall hook for the other version of the plugin (with different code type) to avoid @@ -6317,7 +6124,25 @@ function is_anonymous() { if ( ! isset( $this->_is_anonymous ) ) { if ( $this->is_network_anonymous() ) { $this->_is_anonymous = true; - } else if ( ! fs_is_network_admin() ) { + } else if ( fs_is_network_admin() ) { + /** + * When not-network-anonymous, yet, running in the network admin, consider as anonymous only when ALL non-delegated sites are set to anonymous. + */ + $non_delegated_sites = $this->get_non_delegated_blog_ids(); + + foreach ( $non_delegated_sites as $blog_id ) { + $is_anonymous = $this->_storage->get( 'is_anonymous', false, $blog_id ); + + if ( empty( $is_anonymous ) || false === $is_anonymous[ 'is' ] ) { + $this->_is_anonymous = false; + break; + } + } + + if ( false !== $this->_is_anonymous ) { + $this->_is_anonymous = true; + } + } else { if ( ! isset( $this->_storage->is_anonymous ) ) { // Not skipped. $this->_is_anonymous = false; @@ -6373,6 +6198,18 @@ function is_pending_activation() { return $this->_storage->get( 'is_pending_activation', false ); } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + private function clear_pending_activation_mode() { + // Remove the pending activation sticky notice (if it still exists). + $this->_admin_notices->remove_sticky( 'activation_pending' ); + + // Clear the plugin's pending activation mode. + unset( $this->_storage->is_pending_activation ); + } + /** * Check if plugin must be WordPress.org compliant. * @@ -6449,9 +6286,11 @@ private function set_cron_data( $name, $cron_blog_id = 0 ) { private function get_cron_blog_id( $name ) { $this->_logger->entrance( $name ); - /** - * @var object $cron_data - */ + if ( ! is_multisite() ) { + // Not a multisite. + return 0; + } + $cron_data = $this->get_cron_data( $name ); return ( is_object( $cron_data ) && is_numeric( $cron_data->blog_id ) ) ? @@ -6562,18 +6401,23 @@ private function get_cron_target_blog_id( $except_blog_id = 0 ) { return 0; } - if ( $this->_is_network_active && - is_numeric( $this->_storage->network_install_blog_id ) && - $except_blog_id != $this->_storage->network_install_blog_id && - self::is_site_active( $this->_storage->network_install_blog_id ) - ) { - // Try to run cron from the main network blog. - $install = $this->get_install_by_blog_id( $this->_storage->network_install_blog_id ); + if ( $this->_is_network_active ) { + $network_install_blog_id = $this->_storage->network_install_blog_id; - if ( is_object( $install ) && - ( $this->is_premium() || $install->is_tracking_allowed() ) + if ( + is_numeric( $network_install_blog_id ) && + $except_blog_id != $network_install_blog_id && + self::is_site_active( $network_install_blog_id ) ) { - return $this->_storage->network_install_blog_id; + // Try to run cron from the main network blog. + $install = $this->get_install_by_blog_id( $network_install_blog_id ); + + if ( + is_object( $install ) && + $this->is_tracking_allowed( $network_install_blog_id, $install ) + ) { + return $network_install_blog_id; + } } } @@ -6582,7 +6426,7 @@ private function get_cron_target_blog_id( $except_blog_id = 0 ) { foreach ( $installs as $blog_id => $install ) { if ( $except_blog_id != $blog_id && self::is_site_active( $blog_id ) && - ( $this->is_premium() || $install->is_tracking_allowed() ) + $this->is_tracking_allowed( $blog_id, $install ) ) { return $blog_id; } @@ -6614,7 +6458,7 @@ private function clear_cron( $name, $action_tag = '', $is_network_clear = false /** * @var FS_Site $install */ - if ( $install->is_tracking_allowed() ) { + if ( $this->is_tracking_allowed( $blog_id, $install ) ) { $clear_cron = false; break; } @@ -6625,14 +6469,7 @@ private function clear_cron( $name, $action_tag = '', $is_network_clear = false return; } - /** - * @var object $cron_data - */ - $cron_data = $this->get_cron_data( $name ); - - $cron_blog_id = is_object( $cron_data ) && isset( $cron_data->blog_id ) ? - $cron_data->blog_id : - 0; + $cron_blog_id = $this->get_cron_blog_id( $name ); $this->clear_cron_data( $name ); @@ -6669,14 +6506,7 @@ private function get_next_scheduled_cron( $name, $action_tag = '' ) { return false; } - /** - * @var object $cron_data - */ - $cron_data = $this->get_cron_data( $name ); - - $cron_blog_id = is_object( $cron_data ) && isset( $cron_data->blog_id ) ? - $cron_data->blog_id : - 0; + $cron_blog_id = $this->get_cron_blog_id( $name ); if ( 0 < $cron_blog_id ) { switch_to_blog( $cron_blog_id ); @@ -6787,7 +6617,7 @@ private function execute_cron( $name, $callable ) { } else { $installs = $this->get_blog_install_map(); foreach ( $installs as $blog_id => $install ) { - if ( $this->is_premium() || $install->is_tracking_allowed() ) { + if ( $this->is_tracking_allowed( $blog_id, $install ) ) { if ( ! isset( $users_2_blog_ids[ $install->user_id ] ) ) { $users_2_blog_ids[ $install->user_id ] = array(); } @@ -6852,8 +6682,6 @@ private function get_sync_cron_blog_id() { * @since 1.1.7.3 */ private function run_manual_sync() { - self::require_pluggable_essentials(); - if ( ! $this->is_user_admin() ) { return; } @@ -6937,6 +6765,24 @@ private function is_sync_cron_on() { return $this->is_cron_on( 'sync' ); } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + private function maybe_schedule_sync_cron() { + $next_schedule = $this->next_sync_cron(); + + // The event is properly scheduled, so no need to reschedule it. + if ( + is_numeric( $next_schedule ) && + $next_schedule > time() + ) { + return; + } + + $this->schedule_sync_cron(); + } + /** * @author Vova Feldman (@svovaf) * @since 1.1.7.3 @@ -7043,6 +6889,10 @@ private function get_install_sync_cron_blog_id() { * @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding excluded specified blog ID from being the cron executor. */ private function schedule_install_sync( $except_blog_id = 0 ) { + if ( $this->is_clone() ) { + return; + } + $this->schedule_cron( 'install_sync', 'install_sync', 'single', WP_FS__SCRIPT_START_TIME, false, $except_blog_id ); } @@ -7131,29 +6981,92 @@ function _sync_install_cron_method( array $blog_ids, $current_blog_id = null ) { /** * Show a notice that activation is currently pending. * + * @todo Add some sort of mechanism to allow users to update the email address they would like to opt-in with when $is_suspicious_email is true. + * * @author Vova Feldman (@svovaf) * @since 1.0.7 * - * @param bool|string $email + * @param bool|string $email_address * @param bool $is_pending_trial Since 1.2.1.5 - */ - function _add_pending_activation_notice( $email = false, $is_pending_trial = false ) { - if ( ! is_string( $email ) ) { - $current_user = self::_get_current_wp_user(); - $email = $current_user->user_email; + * @param bool $is_suspicious_email Since 2.5.0 Set to true when there's an indication that email address the user opted in with is fake/dummy/placeholder. + * @param bool $has_upgrade_context Since 2.5.3 + * @param bool $support_email_address Since 2.5.3 + */ + function _add_pending_activation_notice( + $email_address = false, + $is_pending_trial = false, + $is_suspicious_email = false, + $has_upgrade_context = false, + $support_email_address = false + ) { + if ( ! is_string( $email_address ) ) { + $current_user = self::_get_current_wp_user(); + $email_address = $current_user->user_email; + } + + $formatted_message_args = array( + "{$this->get_plugin_name()}", + "{$email_address}", + ); + + if ( ! $has_upgrade_context || ! fs_is_network_admin() ) { + /* translators: %3$s: action (e.g.: "start the trial" or "complete the opt-in") */ + $formatted_message = $this->get_text_inline( 'You should receive a confirmation email for %1$s to your mailbox at %2$s. Please make sure you click the button in that email to %3$s.', 'pending-activation-message' ); + + $formatted_message_args[] = $is_pending_trial ? + $this->get_text_inline( 'start the trial', 'start-the-trial' ) : + $this->get_text_inline( 'complete the opt-in', 'complete-the-opt-in' ); + + $notice_title = $this->get_text_inline( 'Thanks!', 'thanks' ); + } else { + /* translators: %3$s: What the user is expected to receive via email (e.g.: "the installation instructions" or "a license key") */ + $formatted_message = $this->get_text_inline( 'You should receive %3$s for %1$s to your mailbox at %2$s in the next 5 minutes.' ); + + if ( $this->has_release_on_freemius() ) { + $formatted_message_args[] = $this->get_text_x_inline( + 'the installation instructions', + 'Part of the message telling the user what they should receive via email.', + 'the-installation-instructions-phrase' + ); + } else { + $formatted_message_args[] = $this->get_text_x_inline( + 'a license key', + 'Part of the message telling the user what they should receive via email.', + 'a-license-key-phrase' + ); + + $formatted_message .= ( ' ' . sprintf( + /* translators: %s: activation link (e.g.: Click here) */ + $this->get_text_inline( '%s to activate the license once you get it.', 'license-activation-link-message' ), + sprintf( + '%s', + $this->get_activation_url( array( + 'fs_action' => 'reset_pending_activation_mode', + 'require_license' => 'true', + 'fs_unique_affix' => $this->get_unique_affix(), + ) ), + $this->get_text_x_inline( 'Click here', 'Part of an activation link message.', 'click-here' ) + ) + ) ); + } + + $formatted_message_args[] = ( ! empty( $support_email_address ) ) ? + ( "{$support_email_address}" ) : + $this->get_text_x_inline( + "the product's support email address", + 'Part of the message that tells the user to check their spam folder for a specific email.', + 'product-support-email-address-phrase' + ); + + $formatted_message .= ( ' ' . $this->get_text_inline( 'If you didn\'t get the email, try checking your spam folder or search for emails from %4$s.', 'check-spam-folder-message' ) ); + + $notice_title = $this->get_text_inline( 'Thanks for upgrading.', 'after-upgrade-thank-you-message' ); } $this->_admin_notices->add_sticky( - sprintf( - $this->get_text_inline( 'You should receive an activation email for %s to your mailbox at %s. Please make sure you click the activation button in that email to %s.', 'pending-activation-message' ), - '' . $this->get_plugin_name() . '', - '' . $email . '', - ( $is_pending_trial ? - $this->get_text_inline( 'start the trial', 'start-the-trial' ) : - $this->get_text_inline( 'complete the install', 'complete-the-install' ) ) - ), + vsprintf( $formatted_message, $formatted_message_args ), 'activation_pending', - 'Thanks!' + $notice_title ); } @@ -7193,6 +7106,20 @@ function _admin_init_action() { /** * Don't redirect if activating multiple plugins at once (bulk activation). */ + } else if ( + self::is_deactivation_snoozed() && + ( + // Either running the free code base. + ! $this->is_premium() || + // Or if has a free version. + ! $this->is_only_premium() || + // If premium only, don't redirect if license is activated. + ( $this->is_registered() && ! $this->can_use_premium_code() ) + ) + ) { + /** + * Don't redirect if activating during the deactivation snooze period (aka troubleshooting), unless activating a paid product version that the admin didn't enter its license key yet. + */ } else if ( ! $is_migration ) { $this->_redirect_on_activation_hook(); return; @@ -7206,7 +7133,7 @@ function _admin_init_action() { if ( fs_request_is_action( $this->get_unique_affix() . '_skip_activation' ) ) { check_admin_referer( $this->get_unique_affix() . '_skip_activation' ); - $this->skip_connection( null, fs_is_network_admin() ); + $this->skip_connection( fs_is_network_admin() ); fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) ); } @@ -7379,8 +7306,6 @@ function _enqueue_connect_essentials() { fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' ); fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); - - fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' ); } /** @@ -7416,14 +7341,14 @@ function _add_connect_pointer_script() { apply_filters( 'optin_pointer_execute', " - optin.pointer('open'); + optin.pointer('open'); - // Tag the opt-in pointer with custom class. - $('.wp-pointer #fs_connect') - .parents('.wp-pointer.wp-pointer-top') - .addClass('fs-opt-in-pointer'); + // Tag the opt-in pointer with custom class. + $('.wp-pointer #fs_connect') + .parents('.wp-pointer.wp-pointer-top') + .addClass('fs-opt-in-pointer'); - ", 'element', 'optin' ) ?> + ", 'element', 'optin' ) ?> } } }); @@ -7439,7 +7364,7 @@ function _add_connect_pointer_script() { * * @return string */ - function current_page_url() { + static function current_page_url() { $url = 'http'; if ( isset( $_SERVER["HTTPS"] ) ) { @@ -7471,7 +7396,7 @@ function _is_plugin_page() { } /* Events - ------------------------------------------------------------------------------------------------------------------*/ + ------------------------------------------------------------------------------------------------------------------*/ /** * Delete site install from Database. * @@ -7637,10 +7562,19 @@ private function get_previous_theme_slug() { * @author Leo Fajardo (@leorw) * @since 1.2.2 * - * @return string + * @return bool */ private function can_activate_previous_theme() { - $slug = $this->get_previous_theme_slug(); + return $this->can_activate_theme( $this->get_previous_theme_slug() ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @return bool + */ + private function can_activate_theme( $slug ) { if ( false !== $slug && current_user_can( 'switch_themes' ) ) { $theme_instance = wp_get_theme( $slug ); @@ -7737,6 +7671,10 @@ function _activate_plugin_event_hook() { ( current_filter() !== ( 'activate_' . $this->_free_plugin_basename ) ) : $this->is_premium(); + if ( $is_premium_version_activation && $this->is_pending_activation() ) { + $this->clear_pending_activation_mode(); + } + $this->_logger->info( 'Activating ' . ( $is_premium_version_activation ? 'premium' : 'free' ) . ' plugin version.' ); if ( $this->is_plugin() ) { @@ -7803,7 +7741,9 @@ function _activate_plugin_event_hook() { $plugin_version = $this->_storage->is_anonymous_ms['version']; $network = true; } else { - $plugin_version = $this->_storage->is_anonymous['version']; + $plugin_version = isset( $this->_storage->is_anonymous ) ? + $this->_storage->is_anonymous['version'] : + null; $network = false; } @@ -7894,7 +7834,7 @@ function _activate_plugin_event_hook() { $has_api_connectivity = $this->has_api_connectivity( WP_FS__DEV_MODE || $is_premium_version_activation ); if ( ! $this->_anonymous_mode && - $has_api_connectivity && + ( false !== $has_api_connectivity ) && ! $this->_isAutoInstall ) { // Store hint that the plugin was just activated to enable auto-redirection to settings. @@ -7951,7 +7891,7 @@ private function maybe_activate_addon_license() { ); } else { // Activate the license. - $install = $this->get_api_site_scope()->call( + $install = $this->api_site_call( '/', 'put', array( 'license_key' => $this->apply_filters( 'license_key', $license->secret_key ) ) @@ -8531,9 +8471,9 @@ function _deactivate_plugin_hook() { ) ); } } else { - if ( ! $this->has_api_connectivity() ) { + if ( false === $this->has_api_connectivity() && ! $this->is_premium() ) { // Reset connectivity test cache. - unset( $this->_storage->connectivity_test ); + $this->clear_connectivity_info(); $storage_keys_for_removal[] = 'connectivity_test'; } @@ -8610,6 +8550,20 @@ private function set_anonymous_mode( $is_anonymous = true, $network_or_blog_id = $this->_is_anonymous = $is_anonymous; } + /** + * @author Vova Feldman (@svovaf) + * @since 2.5.1 + * + * @param bool|int $network_or_blog_id + */ + private function unset_anonymous_mode( $network_or_blog_id = 0 ) { + if ( true === $network_or_blog_id ) { + unset( $this->_storage->is_anonymous_ms ); + } else { + $this->_storage->remove( 'is_anonymous', true, $network_or_blog_id ); + } + } + /** * @author Vova Feldman (@svovaf) * @since 2.0.0 @@ -8626,9 +8580,17 @@ private function set_anonymous_mode( $is_anonymous = true, $network_or_blog_id = * @uses Freemius::is_network_anonymous() to check if the super-admin network skipped. * @uses Freemius::is_network_delegated_connection() to check if the super-admin network delegated the connection to the site admins. */ - function _after_new_blog_callback( $blog_id, $user_id, $domain, $path, $network_id, $meta ) { + public function _after_new_blog_callback( $blog_id, $user_id, $domain, $path, $network_id, $meta ) { $this->_logger->entrance(); + if ( ! $this->_is_network_active ) { + FS_Clone_Manager::instance()->store_blog_install_info( $blog_id ); + return; + } + + $site = null; + $new_blog_id = $blog_id; + if ( $this->is_premium() && $this->is_network_connected() && is_object( $this->_license ) && @@ -8662,9 +8624,13 @@ function _after_new_blog_callback( $blog_id, $user_id, $domain, $path, $network_ } } + $site = $this->_site; + $this->switch_to_blog( $current_blog_id ); - if ( is_object( $this->_site ) ) { + if ( is_object( $site ) ) { + FS_Clone_Manager::instance()->store_blog_install_info( $blog_id, $site ); + // Already connected (with or without a license), so no need to continue. return; } @@ -8697,6 +8663,8 @@ function _after_new_blog_callback( $blog_id, $user_id, $domain, $path, $network_ false ); + $site = $this->_site; + $this->switch_to_blog( $current_blog_id ); } else { /** @@ -8707,8 +8675,8 @@ function _after_new_blog_callback( $blog_id, $user_id, $domain, $path, $network_ $has_delegated_site = false; $sites = self::get_sites(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); + foreach ( $sites as $wp_site ) { + $blog_id = self::get_site_blog_id( $wp_site ); if ( $this->is_site_delegated_connection( $blog_id ) ) { $has_delegated_site = true; @@ -8722,19 +8690,74 @@ function _after_new_blog_callback( $blog_id, $user_id, $domain, $path, $network_ $this->skip_site_connection( $blog_id ); } } + + /** + * Store the new blog's information even if there's no install so that when a clone install is stored in the new blog's storage, we can try to resolve it automatically. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + FS_Clone_Manager::instance()->store_blog_install_info( $new_blog_id, $site ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.5.0 + * + * @param \WP_Site $new_site + * @param array $args + */ + public function _after_wp_initialize_site_callback( WP_Site $new_site, $args ) { + $this->_logger->entrance(); + + $this->_after_new_blog_callback( + $new_site->id, + // Dummy user ID (not in use). + 0, + $new_site->domain, + $new_site->path, + $new_site->network_id, + // Dummy meta, not in use. + array() + ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * - * @param bool|int $network_or_blog_id Since 2.0.0. + * @param bool|int|int[] $network_or_blog_ids Since 2.0.0. */ - private function reset_anonymous_mode( $network_or_blog_id = 0 ) { - if ( true === $network_or_blog_id ) { - unset( $this->_storage->is_anonymous_ms ); - } else { - $this->_storage->remove( 'is_anonymous', true, $network_or_blog_id ); + private function reset_anonymous_mode( $network_or_blog_ids = false ) { + if ( true === $network_or_blog_ids ) { + $this->unset_anonymous_mode( true ); + + if ( fs_is_network_admin() ) { + $this->_is_anonymous = null; + } + + // Rest anonymous mode for all non-delegated sub-sites. + $blog_ids = $this->get_non_delegated_blog_ids(); + } + else + { + if ( false === $network_or_blog_ids ) { + $network_or_blog_ids = 0; + } + + $blog_ids = is_array( $network_or_blog_ids ) ? + $network_or_blog_ids : + array( $network_or_blog_ids ); + + foreach ( $blog_ids as $blog_id ) { + if ( 0 === $blog_id || get_current_blog_id() == $blog_id ) { + $this->_is_anonymous = null; + } + } + } + + foreach ( $blog_ids as $blog_id ) { + $this->unset_anonymous_mode( $blog_id ); } /** @@ -8745,15 +8768,31 @@ private function reset_anonymous_mode( $network_or_blog_id = 0 ) { * @author Leo Fajardo (@leorw) * @since 1.2.2 */ - if ( ! $this->_is_network_active || - 0 === $network_or_blog_id || - get_current_blog_id() == $network_or_blog_id || - ( true === $network_or_blog_id && fs_is_network_admin() ) - ) { + if ( ! $this->_is_network_active ) { $this->_is_anonymous = null; } } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.3 + */ + private function update_license_required_permissions_if_anonymous() { + if ( ! $this->is_anonymous() ) { + return; + } + + $this->reset_anonymous_mode( fs_is_network_admin() ); + + FS_Permission_Manager::instance( $this )->update_permissions_tracking_flag( array( + 'essentials' => true, + 'events' => true, + 'diagnostic' => false, + 'extensions' => false, + 'site' => false, + ) ); + } + /** * This is used to ensure that before redirecting to the opt-in page after resetting the anonymous mode or * deleting the account in the network level, the URL of the page to redirect to is correct. @@ -8778,15 +8817,27 @@ private function maybe_set_slug_and_network_menu_exists_flag() { * @since 1.1.7 */ function connect_again() { - if ( ! $this->is_anonymous() ) { + if ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) { return; } - $this->reset_anonymous_mode( fs_is_network_admin() ); + if ( $this->is_anonymous() ) { + $this->reset_anonymous_mode( fs_is_network_admin() ); + } + + $activation_url_params = array(); + + if ( $this->is_pending_activation() ) { + $this->clear_pending_activation_mode(); + + if ( fs_request_get_bool( 'require_license' ) ) { + $activation_url_params['require_license'] = true; + } + } $this->maybe_set_slug_and_network_menu_exists_flag(); - fs_redirect( $this->get_activation_url() ); + fs_redirect( $this->get_activation_url( $activation_url_params ) ); } /** @@ -8795,44 +8846,42 @@ function connect_again() { * @author Vova Feldman (@svovaf) * @since 1.1.1 * - * @param array|null $sites Since 2.0.0. Specific sites. - * @param bool $skip_all_network Since 2.0.0. If true, skip connection for all sites. + * @param bool|int|int[] $network_or_blog_ids Since 2.5.1 */ - function skip_connection( $sites = null, $skip_all_network = false ) { + function skip_connection( $network_or_blog_ids = false ) { $this->_logger->entrance(); $this->_admin_notices->remove_sticky( 'connect_account' ); - if ( $skip_all_network ) { + if ( true === $network_or_blog_ids ) { $this->set_anonymous_mode( true, true ); - } - if ( ! $skip_all_network && empty( $sites ) ) { - $this->skip_site_connection(); - } else { - $uids = array(); + if ( fs_is_network_admin() ) { + $this->_is_anonymous = null; + } - if ( $skip_all_network ) { - $this->set_anonymous_mode( true, true ); + // Rest anonymous mode for all non-delegated sub-sites. + $blog_ids = $this->get_non_delegated_blog_ids(); + } + else + { + if ( false === $network_or_blog_ids ) { + $network_or_blog_ids = 0; + } - $sites = self::get_sites(); - foreach ( $sites as $site ) { - $blog_id = self::get_site_blog_id( $site ); - $this->skip_site_connection( $blog_id, false ); - $uids[] = $this->get_anonymous_id( $blog_id ); - } - } else if ( ! empty( $sites ) ) { - foreach ( $sites as $site ) { - $uids[] = $site['uid']; - $this->skip_site_connection( $site['blog_id'], false ); + $blog_ids = is_array( $network_or_blog_ids ) ? + $network_or_blog_ids : + array( $network_or_blog_ids ); + + foreach ( $blog_ids as $blog_id ) { + if ( 0 === $blog_id || get_current_blog_id() == $blog_id ) { + $this->_is_anonymous = null; } } + } - // Send anonymous skip event. - // No user identified info nor any tracking will be sent after the user skips the opt-in. - $this->get_api_plugin_scope()->call( 'skip.json', 'put', array( - 'uids' => $uids, - ) ); + foreach ( $blog_ids as $blog_id ) { + $this->skip_site_connection( $blog_id ); } $this->network_upgrade_mode_completed(); @@ -8847,18 +8896,12 @@ function skip_connection( $sites = null, $skip_all_network = false ) { * @param int|null $blog_id * @param bool $send_skip */ - private function skip_site_connection( $blog_id = null, $send_skip = true ) { + private function skip_site_connection( $blog_id = null ) { $this->_logger->entrance(); $this->_admin_notices->remove_sticky( 'connect_account', $blog_id ); $this->set_anonymous_mode( true, $blog_id ); - - if ( $send_skip ) { - $this->get_api_plugin_scope()->call( 'skip.json', 'put', array( - 'uids' => array( $this->get_anonymous_id( $blog_id ) ), - ) ); - } } /** @@ -9230,7 +9273,7 @@ private function get_themes_data_for_api() { * @param string[] $override * @param bool $include_plugins Since 1.1.8 by default include plugin changes. * @param bool $include_themes Since 1.1.8 by default include plugin changes. - * @param bool $include_blog_data Since 2.3.0 by default include the current blog's data (language, charset, title, and URL). + * @param bool $include_blog_data Since 2.3.0 by default include the current blog's data (language, title, and URL). * * @return array */ @@ -9240,7 +9283,10 @@ private function get_install_data_for_api( $include_themes = true, $include_blog_data = true ) { - if ( $this->is_extensions_tracking_allowed() ) { + // Alias. + $permissions = FS_Permission_Manager::instance( $this ); + + if ( $permissions->is_extensions_tracking_allowed() ) { if ( ! defined( 'WP_FS__TRACK_PLUGINS' ) || false !== WP_FS__TRACK_PLUGINS ) { /** * @since 1.1.8 Also send plugin updates. @@ -9268,21 +9314,23 @@ private function get_install_data_for_api( $versions = $this->get_versions(); - $blog_data = $include_blog_data ? - array( - 'language' => get_bloginfo( 'language' ), - 'charset' => get_bloginfo( 'charset' ), - 'title' => get_bloginfo( 'name' ), - 'url' => get_site_url(), - ) : - array(); + $blog_data = array(); + if ( $include_blog_data ) { + $blog_data['url'] = self::get_unfiltered_site_url(); + + if ( $permissions->is_diagnostic_tracking_allowed() ) { + $blog_data = array_merge( $blog_data, array( + 'language' => self::get_sanitized_language(), + 'title' => get_bloginfo( 'name' ), + ) ); + } + } return array_merge( $versions, $blog_data, array( 'version' => $this->get_plugin_version(), 'is_premium' => $this->is_premium(), // Special params. 'is_active' => true, - 'is_disconnected' => $this->is_tracking_prohibited(), 'is_uninstalled' => false, ), $override ); } @@ -9297,6 +9345,7 @@ private function get_install_data_for_api( * * @param string[] string $override * @param bool $only_diff + * @param bool $is_keepalive * @param bool $include_plugins Since 1.1.8 by default include plugin changes. * @param bool $include_themes Since 1.1.8 by default include plugin changes. * @@ -9305,6 +9354,7 @@ private function get_install_data_for_api( private function get_installs_data_for_api( array $override, $only_diff = false, + $is_keepalive = false, $include_plugins = true, $include_themes = true ) { @@ -9342,6 +9392,10 @@ private function get_installs_data_for_api( $sites = self::get_sites(); + $subsite_data_for_api_by_install_id = array(); + $install_url_by_install_id = array(); + $subsite_registration_date_by_install_id = array(); + foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); @@ -9353,19 +9407,64 @@ private function get_installs_data_for_api( continue; } - if ( ! $this->is_premium() && $install->is_tracking_prohibited() ) { + if ( ! $this->is_tracking_allowed( $blog_id, $install ) ) { // Don't send updates regarding opted-out installs. continue; } - $install_data = $this->get_site_info( $site ); + $install_data = $this->get_site_info( $site, true ); + + if ( FS_Clone_Manager::instance()->is_temporary_duplicate_by_blog_id( $install_data['blog_id'] ) ) { + continue; + } + + $uid = $install_data['uid']; + $url = $install_data['url']; + $registration_date = $install_data['registration_date']; + + if ( isset( $subsite_data_for_api_by_install_id[ $install->id ] ) ) { + $clone_subsite_data = $subsite_data_for_api_by_install_id[ $install->id ]; + $clone_install_url = $install_url_by_install_id[ $install->id ]; + $clone_subsite_registration_date = $subsite_registration_date_by_install_id[ $install->id ]; + + $skip = false; + + if ( + ! empty( $install_data['registration_date'] ) && + ! empty( $clone_subsite_registration_date ) + ) { + /** + * If the current subsite was created after the other subsite that is also linked to the same install ID, we assume that it's a clone (not the original), and therefore, would skip its processing. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.1 + */ + $skip = ( strtotime( $install_data['registration_date'] ) > strtotime( $clone_subsite_registration_date ) ); + } else if ( + /** + * If we already have an install with the same URL as the subsite it's stored in, skip the current subsite. Otherwise, replace the existing install's data with the current subsite's install's data if the URLs match. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + fs_strip_url_protocol( untrailingslashit( $clone_install_url ) ) === fs_strip_url_protocol( untrailingslashit( $clone_subsite_data['url'] ) ) || + fs_strip_url_protocol( untrailingslashit( $install->url ) ) !== fs_strip_url_protocol( untrailingslashit( $url ) ) + ) { + $skip = true; + } - $uid = $install_data['uid']; + if ( $skip ) { + // Store the skipped subsite's ID so that the clone resolution manager can try to resolve the clone install that is stored in that subsite later on. + FS_Clone_Manager::instance()->store_blog_install_info( $blog_id ); + continue; + } + } unset( $install_data['blog_id'] ); unset( $install_data['uid'] ); + unset( $install_data['url'] ); + unset( $install_data['registration_date'] ); - $install_data['is_disconnected'] = $install->is_disconnected; $install_data['is_active'] = $this->is_active_for_site( $blog_id ); $install_data['is_uninstalled'] = $install->is_uninstalled; @@ -9388,18 +9487,26 @@ private function get_installs_data_for_api( $is_common_diff_for_any_site = $is_common_diff_for_any_site || $is_common_diff; } - if ( ! empty( $install_data ) || $is_common_diff ) { + if ( ! empty( $install_data ) || $is_common_diff || $is_keepalive ) { // Add install ID and site unique ID. $install_data['id'] = $install->id; $install_data['uid'] = $uid; + $install_data['url'] = $url; - $installs_data[] = $install_data; + $subsite_data_for_api_by_install_id[ $install->id ] = $install_data; + $install_url_by_install_id[ $install->id ] = $install->url; + $subsite_registration_date_by_install_id[ $install->id ] = $registration_date; } } } restore_current_blog(); + $installs_data = array_merge( + $installs_data, + array_values( $subsite_data_for_api_by_install_id ) + ); + if ( 0 < count( $installs_data ) && ( $is_common_diff_for_any_site || ! $only_diff ) ) { if ( ! $only_diff ) { $installs_data[] = $common; @@ -9437,8 +9544,12 @@ private function get_install_diff_for_api( $site, $install, $override = array() if ( ( is_bool( $install->{$p} ) || ! empty( $install->{$p} ) ) && $install->{$p} != $v ) { - $install->{$p} = $v; - $diff[ $p ] = $v; + $val = self::get_api_sanitized_property( $p, $v ); + + if ( $install->{$p} != $val ) { + $install->{$p} = $val; + $diff[ $p ] = $val; + } } } else { $special[ $p ] = $v; @@ -9460,7 +9571,79 @@ private function get_install_diff_for_api( $site, $install, $override = array() $diff = array_merge( $diff, $special ); } - return $diff; + return $diff; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.1 + */ + private function send_pending_clone_update_once() { + $this->_logger->entrance(); + + if ( ! empty( $this->_storage->clone_id ) ) { + return; + } + + $install_clone = $this->get_api_site_scope()->call( + '/clones', + 'post', + array( 'site_url' => self::get_unfiltered_site_url() ) + ); + + if ( $this->is_api_result_entity( $install_clone ) ) { + $this->_storage->clone_id = $install_clone->id; + } + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.1 + * + * @param string $resolution_type + * @param FS_Site $clone_context_install + */ + function send_clone_resolution_update( $resolution_type, $clone_context_install ) { + $this->_logger->entrance(); + + if ( empty( $this->_storage->clone_id ) ) { + return; + } + + $new_install_id = null; + $current_site = null; + + $flush = false; + + /** + * If the current site is now different from the context install before the clone resolution, we need to override `$this->_site` so that the API call below will be made with the right install scope entity. + */ + if ( $clone_context_install->id != $this->_site->id ) { + $new_install_id = $this->_site->id; + $current_site = $this->_site; + $this->_site = $clone_context_install; + + $flush = true; + } + + $this->get_api_site_scope( $flush )->call( + "/clones/{$this->_storage->clone_id}", + 'put', + array( + 'resolution' => $resolution_type, + 'new_install_id' => $new_install_id, + ) + ); + + if ( is_object( $current_site ) ) { + /** + * Ensure that the install scope entity is updated back to the previous install entity. + */ + $this->_site = $current_site; + + // Restore the previous install scope entity of the API. + $this->get_api_site_scope( true ); + } } /** @@ -9471,10 +9654,11 @@ private function get_install_diff_for_api( $site, $install, $override = array() * * @param string[] string $override * @param bool $flush + * @param bool $is_two_way_sync @since 2.5.0 If true and there's a successful API request, the install sync cron will be cleared. * * @return false|object|string */ - private function send_install_update( $override = array(), $flush = false ) { + private function send_install_update( $override = array(), $flush = false, $is_two_way_sync = false ) { $this->_logger->entrance(); $check_properties = $this->get_install_data_for_api( $override ); @@ -9485,7 +9669,6 @@ private function send_install_update( $override = array(), $flush = false ) { $params = $this->get_install_diff_for_api( $check_properties, $this->_site, $override ); } - $keepalive_only_update = false; if ( empty( $params ) ) { $keepalive_only_update = $this->should_send_keepalive_update(); @@ -9500,10 +9683,9 @@ private function send_install_update( $override = array(), $flush = false ) { } } - if ( ! $keepalive_only_update ) { + if ( $is_two_way_sync ) { /** - * Do not update the last install sync timestamp after a keepalive-only call since there were no actual - * updates sent. + * Update last install sync timestamp during a two-way sync call as we expect that updates are sent during this call. * * @author Leo Fajardo (@leorw) * @since 2.2.3 @@ -9519,11 +9701,11 @@ private function send_install_update( $override = array(), $flush = false ) { $this->set_keepalive_timestamp(); // Send updated values to FS. - $site = $this->get_api_site_scope()->call( '/', 'put', $params ); + $site = $this->api_site_call( '/', 'put', $params, true ); - if ( ! $keepalive_only_update && $this->is_api_result_entity( $site ) ) { + if ( $is_two_way_sync && $this->is_api_result_entity( $site ) ) { /** - * Do not clear scheduled sync after a keepalive-only call since there were no actual updates sent. + * Clear scheduled install sync after a two-way sync call. * * @author Leo Fajardo (@leorw) * @since 2.2.3 @@ -9545,37 +9727,29 @@ private function send_install_update( $override = array(), $flush = false ) { * * @param string[] string $override * @param bool $flush + * @param bool $is_two_way_sync @since 2.5.0 If true and there's a successful API request, the install sync cron will be cleared. * * @return false|object|string */ - private function send_installs_update( $override = array(), $flush = false ) { + private function send_installs_update( $override = array(), $flush = false, $is_two_way_sync = false ) { $this->_logger->entrance(); - $installs_data = $this->get_installs_data_for_api( $override, ! $flush ); + /** + * Pass `true` to use the network level storage since the update is for many installs. + * + * @author Leo Fajardo (@leorw) + * @since 2.2.3 + */ + $should_send_keepalive = $this->should_send_keepalive_update( true ); - $keepalive_only_update = false; - if ( empty( $installs_data ) ) { - /** - * Pass `true` to use the network level storage since the update is for many installs. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - $keepalive_only_update = $this->should_send_keepalive_update( true ); + $installs_data = $this->get_installs_data_for_api( $override, ! $flush, $should_send_keepalive ); - if ( ! $keepalive_only_update ) { - /** - * There are no updates to send including keepalive. - * - * @author Leo Fajardo (@leorw) - * @since 2.2.3 - */ - return false; - } + if ( empty( $installs_data ) ) { + return false; } - if ( ! $keepalive_only_update ) { - // Update last install sync timestamp if there were actual updates sent (i.e., not a keepalive-only call). + if ( $is_two_way_sync ) { + // Update last install sync timestamp during a two-way sync call as we expect that updates are sent during this call. $this->set_cron_execution_timestamp( 'install_sync' ); } @@ -9590,8 +9764,8 @@ private function send_installs_update( $override = array(), $flush = false ) { // Send updated values to FS. $result = $this->get_api_user_scope()->call( "/plugins/{$this->_plugin->id}/installs.json", 'put', $installs_data ); - if ( ! $keepalive_only_update && $this->is_api_result_object( $result, 'installs' ) ) { - // I successfully sent installs update (there was an actual update sent and it's not just a keepalive-only call), clear scheduled sync if exist. + if ( $is_two_way_sync && $this->is_api_result_object( $result, 'installs' ) ) { + // I successfully sent a two-way installs update, clear the scheduled install sync if it exists. $this->clear_install_sync_cron(); } @@ -9641,10 +9815,10 @@ private function maybe_sync_install_user() { * @param string[] string $override * @param bool $flush */ - private function sync_install( $override = array(), $flush = false ) { + function sync_install( $override = array(), $flush = false ) { $this->_logger->entrance(); - $site = $this->send_install_update( $override, $flush ); + $site = $this->send_install_update( $override, $flush, true ); if ( false === $site ) { // No sync required. @@ -9673,7 +9847,7 @@ private function sync_install( $override = array(), $flush = false ) { private function sync_installs( $override = array(), $flush = false ) { $this->_logger->entrance(); - $result = $this->send_installs_update( $override, $flush ); + $result = $this->send_installs_update( $override, $flush, true ); if ( false === $result ) { // No sync required. @@ -9828,8 +10002,8 @@ function _uninstall_plugin_event( $check_user = true ) { // Send uninstall event. $this->send_installs_update( $params ); } else { - // Send uninstall event. - $this->send_install_update( $params ); + // Send uninstall event and handle the result. + $this->sync_install( $params ); } } @@ -9845,7 +10019,7 @@ function _uninstall_plugin_event( $check_user = true ) { * @param string $is_premium * @param string $caller * - * @return string + * @return void */ function set_basename( $is_premium, $caller ) { $basename = plugin_basename( $caller ); @@ -9926,7 +10100,17 @@ public static function _uninstall_plugin_hook() { return; } - $fs->_uninstall_plugin_event(); + if ( + ! $fs->is_clone() && + /** + * If there's a context install, run this method only when there's also a context user (e.g., when cloning a subsite of a multisite network into a single-site installation, it's possible for an install to be associated with a non-existing user entity; we want Freemius to be off in this case, while we are trying to recover the user). + * + * @author Leo Fajardo + */ + ( ! is_object( $fs->_site ) || $fs->is_registered() ) + ) { + $fs->_uninstall_plugin_event(); + } $fs->do_action( 'after_uninstall' ); } @@ -10037,7 +10221,7 @@ function get_slug() { * @return string */ function get_premium_slug() { - return is_object( $this->_plugin ) ? + return ( is_object( $this->_plugin ) && ! empty( $this->_plugin->premium_slug ) ) ? $this->_plugin->premium_slug : "{$this->_slug}-premium"; } @@ -10090,6 +10274,28 @@ function get_bundle_public_key() { null; } + /** + * Get whether the SDK has been initiated in the context of a Bundle. + * + * This will return true, if `bundle_id` is present in the SDK init parameters. + * + * ```php + * $my_fs = fs_dynamic_init( array( + * // ... + * 'bundle_id' => 'XXXX', // Will return true since we have bundle id. + * 'bundle_public_key' => 'pk_XXXX', + * ) ); + * ``` + * + * @author Swashata Ghosh (@swashata) + * @since 2.5.0 + * + * @return bool True if we are running in bundle context, false otherwise. + */ + private function has_bundle_context() { + return ! is_null( $this->get_bundle_id() ); + } + /** * @author Vova Feldman (@svovaf) * @since 1.2.1.5 @@ -10121,7 +10327,20 @@ function get_parent_id() { function get_usage_tracking_terms_url() { return $this->apply_filters( 'usage_tracking_terms_url', - "https://freemius.com/wordpress/usage-tracking/{$this->_plugin->id}/{$this->_slug}/" + "https://freemius.com/product/opt-in/{$this->_plugin->id}/{$this->_slug}/" + ); + } + + /** + * @todo (For LiteSDK) We can refactor this and other related functions giving links to several landing pages on freemius.com to come from a separate class like `FS_Terms_Pages`. This would get a `FS_WP_Hook` (hypothetical) instance as a dependency and use it to hook into the `license_activation_terms_url` or related filters. The entry level instance from `ms_fs()` would hold a public read-only variable `my_fs()->terms_pages` which would be an instance of `FS_Terms_Pages` and would hold all the links to the terms pages. + * @since 2.5.8 + * + * @return string + */ + function get_license_activation_terms_url() { + return $this->apply_filters( + 'license_activation_terms_url', + "https://freemius.com/product/license-activation/{$this->_plugin->id}/{$this->_slug}/" ); } @@ -10134,7 +10353,7 @@ function get_usage_tracking_terms_url() { function get_eula_url() { return $this->apply_filters( 'eula_url', - "https://freemius.com/terms/{$this->_plugin->id}/{$this->_slug}/" + "https://freemius.com/product/{$this->_plugin->id}/{$this->_slug}/legal/eula/" ); } @@ -10322,7 +10541,7 @@ function get_plugin_folder_name() { #endregion ------------------------------------------------------------------ /* Account - ------------------------------------------------------------------------------------------------------------------*/ + ------------------------------------------------------------------------------------------------------------------*/ /** * Find plugin's slug by plugin's basename. @@ -10386,9 +10605,14 @@ static function get_all_users() { */ private static function get_all_sites( $module_type = WP_FS__MODULE_TYPE_PLUGIN, - $blog_id = null + $blog_id = null, + $is_backup = false ) { - $sites = self::get_account_option( 'sites', $module_type, $blog_id ); + $sites = self::get_account_option( + ( $is_backup ? 'prev_' : '' ) . 'sites', + $module_type, + $blog_id + ); if ( ! is_array( $sites ) ) { $sites = array(); @@ -10748,10 +10972,20 @@ private static function get_all_account_addons() { * * @author Vova Feldman (@svovaf) * @since 1.0.1 + * + * @param bool $ignore_anonymous_state Since 2.5.1 + * * @return bool */ - function is_registered() { - return is_object( $this->_user ); + function is_registered( $ignore_anonymous_state = false ) { + return ( + is_object( $this->_user ) && + ( + $this->is_premium() || + $ignore_anonymous_state || + ! $this->is_anonymous() + ) + ); } /** @@ -10762,8 +10996,34 @@ function is_registered() { * * @return bool */ - function is_tracking_allowed() { - return ( is_object( $this->_site ) && $this->_site->is_tracking_allowed() ); + function is_tracking_allowed( $blog_id = null, $install = null ) { + if ( is_null( $install ) ) { + $install = is_null( $blog_id ) ? + $this->_site : + $this->get_install_by_blog_id( $blog_id ); + } + + return ( + is_object( $install ) && + FS_Permission_Manager::instance( $this )->is_homepage_url_tracking_allowed( $blog_id ) + ); + } + + /** + * Returns TRUE if the user never opted-in or manually opted-out. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param int|null $blog_id + * + * @return bool + */ + function is_tracking_prohibited( $blog_id = null ) { + return ( + ! $this->is_registered( true ) || + ! $this->is_tracking_allowed( $blog_id ) + ); } /** @@ -10808,6 +11068,52 @@ function get_site() { return $this->_site; } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function store_site( $site ) { + $this->_site = $site; + $this->_store_site( true ); + } + + /** + * Deletes the current install with an option to back it up in case restoration will be needed (e.g., if the automatic clone resolution attempt fails). + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function delete_current_install( $back_up ) { + // Back up and delete the unique ID. + if ( $back_up ) { + self::$_accounts->set_option( 'prev_unique_id', $this->get_anonymous_id() ); + } + + self::$_accounts->set_option( 'unique_id', null ); + + if ( $back_up ) { + // Back up the install before deleting it so that it can be restored later on if necessary (e.g., if the automatic clone resolution attempt fails). + $this->back_up_site(); + } + + $this->_delete_site(); + $this->_site = null; + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function restore_backup_site() { + self::$_accounts->set_option( + 'unique_id', + self::$_accounts->get_option( 'prev_unique_id' ) + ); + + $sites = self::get_all_sites( $this->_module_type, null, true ); + $this->store_site( clone $sites[ $this->_slug ] ); + } + /** * Get plugin add-ons. * @@ -11252,7 +11558,7 @@ function get_plan() { function is_trial() { $this->_logger->entrance(); - if ( ! $this->is_registered() || ! is_object( $this->_site ) ) { + if ( ! $this->is_registered( true ) || ! is_object( $this->_site ) ) { return false; } @@ -11366,7 +11672,7 @@ function get_trial_plan() { function is_paying() { $this->_logger->entrance(); - if ( ! $this->is_registered() ) { + if ( ! $this->is_registered( true ) ) { return false; } @@ -12089,7 +12395,7 @@ private function can_activate_license_on_network( FS_Plugin_License $license ) { } else { $url = is_object( $site ) ? $site->siteurl : - get_site_url( $blog_id ); + self::get_unfiltered_site_url( $blog_id ); $disconnected_site_ids[] = $blog_id; } @@ -12139,7 +12445,7 @@ private function activate_license_on_many_installs( $install_2_blog_map = array(); foreach ( $blog_2_install_map as $blog_id => $install ) { - $params[] = array( 'id' => $install->id ); + $params[] = array( 'id' => $install->id, 'url' => $install->url ); $install_2_blog_map[ $install->id ] = $blog_id; } @@ -12462,7 +12768,21 @@ function is_whitelabeled( $ignore_data_debug_mode = false, $blog_id = null ) { } else if ( $is_whitelabeled_flag ) { $is_whitelabeled = true; } else { - $addon_ids = $this->get_updated_account_addons(); + if ( $this->is_registered() || $this->is_premium() ) { + $addon_ids = $this->get_updated_account_addons(); + } else { + $addons = self::get_all_addons(); + + $plugin_addons = isset( $addons[ $this->_plugin->id ] ) ? + $addons[ $this->_plugin->id ] : + array(); + + $addon_ids = array(); + foreach ( $plugin_addons as $addon ) { + $addon_ids[] = $addon->id; + } + } + $installed_addons = $this->get_installed_addons(); foreach ( $installed_addons as $fs_addon ) { $addon_ids[] = $fs_addon->get_id(); @@ -12891,6 +13211,75 @@ function _add_license_activation_dialog_box() { fs_require_template( 'forms/resend-key.php', $vars ); } + /** + * Displays an email address update dialog box when the user clicks on the email address "Edit" button on the "Account" page. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function _add_email_address_update_dialog_box() { + $vars = array( 'id' => $this->_module_id ); + + fs_require_template( 'forms/email-address-update.php', $vars ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function _add_email_address_update_option() { + if ( ! $this->should_handle_user_change() ) { + return; + } + + // Add email address update AJAX handler. + $this->add_ajax_action( 'update_email_address', array( &$this, '_email_address_update_ajax_handler' ) ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + function _email_address_update_ajax_handler() { + $this->check_ajax_referer( 'update_email_address' ); + + $new_email_address = fs_request_get( 'email_address' ); + $transfer_type = fs_request_get( 'transfer_type' ); + + $result = $this->update_email( $new_email_address ); + + if ( ! FS_Api::is_api_error( $result ) ) { + self::shoot_ajax_success(); + } + + $error = ''; + + if ( FS_Api::is_api_error_object( $result ) ) { + switch ( $result->error->code ) { + case 'user_exist': + case 'account_verification_required': + $error = array( + 'code' => 'change_ownership', + 'url' => $this->get_account_url( 'change_owner', array( + 'state' => 'init', + 'candidate_email' => $new_email_address, + 'transfer_type' => $transfer_type, + ) ), + ); + + break; + } + } + + if ( empty( $error ) ) { + $error = is_object( $result ) ? + var_export( $result->error, true ) : + $result; + } + + self::shoot_ajax_failure( $error ); + } + /** * Returns a collection of IDs of installs that are associated with the context product and its add-ons, and activated with foreign licenses. * @@ -13098,10 +13487,15 @@ function _add_license_activation() { ( $is_network_admin && $this->is_network_active() && ! $this->is_network_delegated_connection() ) || ( ! $is_network_admin && ( ! $this->is_network_active() || $this->is_delegated_connection() ) ) ) { - /** - * @since 1.2.0 Add license action link only on plugins page. - */ - $this->_add_license_action_link(); + if ( + $this->is_premium() || + ( $this->has_paid_plan() && ! $this->has_premium_version() ) + ) { + /** + * @since 1.2.0 Add license action link only on plugins page. + */ + $this->_add_license_action_link(); + } } } @@ -13273,7 +13667,7 @@ function _set_beta_mode_ajax_handler() { self::shoot_ajax_failure(); } - $site = $this->get_api_site_scope()->call( + $site = $this->api_site_call( '', 'put', array( @@ -13309,7 +13703,7 @@ function _activate_license_ajax_action() { $this->check_ajax_referer( 'activate_license' ); - $license_key = trim( fs_request_get( 'license_key' ) ); + $license_key = trim( fs_request_get_raw( 'license_key' ) ); if ( empty( $license_key ) ) { exit; @@ -13326,7 +13720,8 @@ function _activate_license_ajax_action() { fs_request_get( 'blog_id', null ), fs_request_get( 'module_id', null, 'post' ), fs_request_get( 'user_id', null ), - fs_request_get_bool( 'is_extensions_tracking_allowed', null ) + fs_request_get_bool( 'is_extensions_tracking_allowed', null ), + fs_request_get_bool( 'is_diagnostic_tracking_allowed', null ) ); if ( @@ -13571,6 +13966,9 @@ function should_use_external_pricing() { * @param null|int $blog_id * @param null|number $plugin_id * @param null|number $license_owner_id + * @param bool|null $is_extensions_tracking_allowed + * @param bool|null $is_diagnostic_tracking_allowed Since 2.5.0.2 to allow license activation with minimal data footprint. + * * * @return array { * @var bool $success @@ -13585,7 +13983,8 @@ private function activate_license( $blog_id = null, $plugin_id = null, $license_owner_id = null, - $is_extensions_tracking_allowed = null + $is_extensions_tracking_allowed = null, + $is_diagnostic_tracking_allowed = null ) { $this->_logger->entrance(); @@ -13605,7 +14004,10 @@ private function activate_license( $this : $this->get_addon_instance( $plugin_id ); - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); + FS_Permission_Manager::instance( $this )->update_permissions_tracking_flag( array( + FS_Permission_Manager::PERMISSION_DIAGNOSTIC => $is_diagnostic_tracking_allowed, + FS_Permission_Manager::PERMISSION_EXTENSIONS => $is_extensions_tracking_allowed, + ) ); $error = false; $next_page = false; @@ -13635,6 +14037,8 @@ private function activate_license( } if ( is_object( $user ) ) { + $result = true; + if ( $is_network_activation_or_migration && ! $has_valid_blog_id ) { // If no specific blog ID was provided, activate the license for all sites in the network. $blog_2_install_map = array(); @@ -13656,22 +14060,10 @@ private function activate_license( if ( ! empty( $blog_2_install_map ) ) { $result = $fs->activate_license_on_many_installs( $user, $license_key, $blog_2_install_map ); - - if ( true !== $result ) { - $error = FS_Api::is_api_error_object( $result ) ? - $result->error->message : - var_export( $result, true ); - } } - if ( empty( $error ) && ! empty( $site_ids ) ) { + if ( true === $result && ! empty( $site_ids ) ) { $result = $fs->activate_license_on_many_sites( $user, $license_key, $site_ids ); - - if ( true !== $result ) { - $error = FS_Api::is_api_error_object( $result ) ? - $result->error->message : - var_export( $result, true ); - } } } else { if ( $fs->is_registered() ) { @@ -13697,13 +14089,11 @@ private function activate_license( $api = $fs->get_api_site_scope(); - $install = $api->call( $fs->add_show_pending( '/' ), 'put', $params ); + $result = $api->call( $fs->add_show_pending( '/' ), 'put', $params ); + + if ( ! FS_Api::is_api_error( $result ) ) { + $install = $result; - if ( FS_Api::is_api_error( $install ) ) { - $error = FS_Api::is_api_error_object( $install ) ? - $install->error->message : - var_export( $install->error, true ); - } else { $fs->reconnect_locally( $has_valid_blog_id ); if ( @@ -13716,16 +14106,18 @@ private function activate_license( } } else /* ( $fs->is_addon() && $fs->get_parent_instance()->is_registered() ) */ { $result = $fs->activate_license_on_site( $user, $license_key ); - - if ( true !== $result ) { - $error = FS_Api::is_api_error_object( $result ) ? - $result->error->message : - var_export( $result, true ); - } } } - if ( empty( $error ) ) { + if ( true !== $result && ! FS_Api::is_api_result_entity( $result ) ) { + if ( FS_Api::is_blocked( $result ) ) { + $result->error->message = $this->generate_api_blocked_notice_message_from_result( $result ); + } + + $error = FS_Api::is_api_error_object( $result ) ? + $result->error->message : + var_export( $result, true ); + } else { $fs->network_upgrade_mode_completed(); $fs->_user = $user; @@ -13770,8 +14162,8 @@ private function activate_license( } } - $all_sites = self::get_sites(); - $pending_sites = array(); + $all_sites = self::get_sites(); + $pending_blog_ids = array(); /** * Check if there are any sites that are not connected, skipped, nor delegated. For every site that falls into that category, if the product is freemium, skip the connection. If the product is premium only, delegate the connection to the site administrator. @@ -13801,14 +14193,14 @@ private function activate_license( continue; } - $pending_sites[] = self::get_site_info( $site ); + $pending_blog_ids[] = $blog_id; } - if ( ! empty( $pending_sites ) ) { + if ( ! empty( $pending_blog_ids ) ) { if ( $fs->is_freemium() && $fs->is_enable_anonymous() ) { - $fs->skip_connection( $pending_sites ); + $fs->skip_connection( $pending_blog_ids ); } else { - $fs->delegate_connection( $pending_sites ); + $fs->delegate_connection( $pending_blog_ids ); } } } @@ -13886,7 +14278,7 @@ private function get_parent_and_addons_installs_info() { $addon_info = $fs->_get_addon_info( $addon_id, $is_installed ); - if ( ! $addon_info['is_connected'] ) { + if ( ! isset( $addon_info['is_connected'] ) || ! $addon_info['is_connected'] ) { // Add-on is not associated with an install entity. continue; } @@ -13944,11 +14336,11 @@ function _network_activate_ajax_action() { $this->delegate_connection(); } else { if ( ! empty( $sites_by_action['delegate'] ) ) { - $this->delegate_connection( $sites_by_action['delegate'] ); + $this->delegate_connection( self::get_sites_blog_ids( $sites_by_action['delegate'] ) ); } if ( ! empty( $sites_by_action['skip'] ) ) { - $this->skip_connection( $sites_by_action['skip'] ); + $this->skip_connection( self::get_sites_blog_ids( $sites_by_action['skip'] ) ); } if ( empty( $sites_by_action['allow'] ) ) { @@ -14266,15 +14658,37 @@ function has_affiliate_program() { return $this->_plugin->has_affiliate_program(); } + /** + * Get Plugin ID under which we will track affiliate application. + * + * This could either be the Bundle ID or the main plugin ID. + * + * @return number Bundle ID if developer has provided one, else the main plugin ID. + */ + private function get_plugin_id_for_affiliate_terms() { + return $this->has_bundle_context() ? + $this->get_bundle_id() : + $this->_plugin->id; + } + /** * @author Leo Fajardo (@leorw) * @since 1.2.4 */ private function fetch_affiliate_terms() { if ( ! is_object( $this->plugin_affiliate_terms ) ) { - $plugins_api = $this->get_api_plugin_scope(); + /** + * In case we have a bundle set in SDK configuration, we would like to use that for affiliates, not the main plugin. + */ + $plugins_api = $this->has_bundle_context() ? + $this->get_api_bundle_scope() : + $this->get_api_plugin_scope(); + $affiliate_terms = $plugins_api->get( '/aff.json?type=affiliation', false ); + /** + * At this point, we intentionally don't fallback to the main plugin, because the developer has chosen to use bundle. So it makes sense the affiliate program should be in context to the bundle too. + */ if ( ! $this->is_api_result_entity( $affiliate_terms ) ) { return; } @@ -14292,8 +14706,10 @@ private function fetch_affiliate_and_custom_terms() { $application_data = $this->_storage->affiliate_application_data; $flush = ( ! isset( $application_data['status'] ) || 'pending' === $application_data['status'] ); + $plugin_id_for_affiliate = $this->get_plugin_id_for_affiliate_terms(); + $users_api = $this->get_api_user_scope(); - $result = $users_api->get( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json", $flush ); + $result = $users_api->get( "/plugins/{$plugin_id_for_affiliate}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json", $flush ); if ( $this->is_api_result_object( $result, 'affiliates' ) ) { if ( ! empty( $result->affiliates ) ) { $affiliate = new FS_Affiliate( $result->affiliates[0] ); @@ -14393,15 +14809,17 @@ function _submit_affiliate_application() { var_export( $next_page, true ) ); } else if ( $this->is_pending_activation() ) { - self::shoot_ajax_failure( $this->get_text_inline( 'Account is pending activation.', 'account-is-pending-activation' ) ); + self::shoot_ajax_failure( $this->get_text_inline( 'Account is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again.', 'account-is-pending-activation' ) ); } } $this->fetch_affiliate_terms(); + $plugin_id_for_affiliate = $this->get_plugin_id_for_affiliate_terms(); + $api = $this->get_api_user_scope(); $result = $api->call( - ( "/plugins/{$this->_plugin->id}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json" ), + ( "/plugins/{$plugin_id_for_affiliate}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json" ), 'post', $affiliate ); @@ -14522,7 +14940,11 @@ function version_upgrade_checkout_link( $new_version ) { return sprintf( $this->get_text_inline( '%s to access version %s security & feature updates, and support.', 'x-for-updates-and-support' ), - sprintf( '%s', $url, $purchase_license_text ), + sprintf( + '%s', + $this->apply_filters( 'update_notice_checkout_url', $url ), + $purchase_license_text + ), $new_version ); } @@ -14592,7 +15014,7 @@ function checkout_url( */ $params = array_merge( $params, $extra ); - return $this->_get_admin_page_url( 'pricing', $params, $network ); + return $this->apply_filters( 'checkout_url', $this->_get_admin_page_url( 'pricing', $params, $network ) ); } /** @@ -14863,6 +15285,16 @@ static function is_cron() { return ( defined( 'DOING_CRON' ) && DOING_CRON ); } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @return bool + */ + static function is_admin_post() { + return ( 'admin-post.php' === self::get_current_page() ); + } + /** * Check if a real user is visiting the admin dashboard. * @@ -14876,7 +15308,7 @@ function is_user_in_admin() { is_admin() && ! self::is_ajax() && ! self::is_cron() && - ( 'admin-post.php' !== self::get_current_page() ) + ! self::is_admin_post() ); } @@ -15028,20 +15460,20 @@ function is_network_active() { * @author Leo Fajardo (@leorw) * @since 2.0.0 * - * @param array|null $sites + * @param bool|int[] $all_or_blog_ids */ - private function delegate_connection( $sites = null ) { + private function delegate_connection( $all_or_blog_ids = true ) { $this->_logger->entrance(); $this->_admin_notices->remove_sticky( 'connect_account' ); - if ( is_null( $sites ) ) { + if ( true === $all_or_blog_ids ) { // All sites delegation. - $this->_storage->store( 'is_delegated_connection', true, true, true ); + $this->_storage->store( 'is_delegated_connection', true, true ); } else { // Specified sites delegation. - foreach ( $sites as $site ) { - $this->delegate_site_connection( $site['blog_id'] ); + foreach ( $all_or_blog_ids as $blog_id ) { + $this->delegate_site_connection( $blog_id ); } } @@ -15057,7 +15489,7 @@ private function delegate_connection( $sites = null ) { * @param int $blog_id */ private function delegate_site_connection( $blog_id ) { - $this->_storage->store( 'is_delegated_connection', true, $blog_id, true ); + $this->_storage->store( 'is_delegated_connection', true, $blog_id ); } /** @@ -15097,7 +15529,7 @@ function is_site_delegated_connection( $blog_id = 0 ) { } /** - * Check if delegated the connection. When running within the the network admin, + * Check if delegated the connection. When running within the network admin, * and haven't specified the blog ID, checks if network level delegated. If running * within a site admin or specified a blog ID, check if delegated the connection for * the current context site. @@ -15157,12 +15589,17 @@ function is_active_for_site( $blog_id ) { } /** + * @todo Implement pagination when accessing the subsites collection. + * * @author Leo Fajardo (@leorw) * @since 2.0.0 * + * @param int $limit Default to 1,000 + * @param int $offset Default to 0 + * * @return array Active & public sites collection. */ - static function get_sites() { + static function get_sites( $limit = 1000, $offset = 0 ) { if ( ! is_multisite() ) { return array(); } @@ -15184,27 +15621,11 @@ static function get_sites() { 'mature' => 0, 'spam' => 0, 'deleted' => 0, + 'number' => $limit, + 'offset' => $offset, ); - if ( function_exists( 'get_sites' ) ) { - // For WP 4.6 and above. - return get_sites( $args ); - } else if ( function_exists( 'wp_' . 'get_sites' ) ) { - // For WP 3.7 to WP 4.5. - /** - * This is a hack suggested previously proposed by the TRT. Our SDK is compliant with older WP versions and we'd like to keep it that way. - * - * @todo Remove this hack once this false-positive error is removed from the Theme Sniffer. - * - * @since 2.3.3 - * @author Vova Feldman (@svovaf) - */ - $fn = 'wp_' . 'get_sites'; - return $fn( $args ); - } else { - // For WP 3.6 and below. - return get_blog_list( 0, 'all' ); - } + return get_sites( $args ); } /** @@ -15253,7 +15674,7 @@ private function get_address_to_blog_map() { $address_to_blog_map = array(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); - $address = trailingslashit( fs_strip_url_protocol( get_site_url( $blog_id ) ) ); + $address = self::get_unfiltered_site_url( $blog_id, true, true ); $address_to_blog_map[ $address ] = $blog_id; } @@ -15289,6 +15710,42 @@ function get_blog_install_map() { return $install_map; } + /** + * @author Vova Feldman (@svovaf) + * @since 2.5.1 + * + * @param bool|null $is_delegated When `true`, returns only connection delegated blog IDs. When `false`, only non-delegated blog IDs. + * + * @return int[] + */ + private function get_blog_ids( $is_delegated = null ) { + $blog_ids = array(); + + $sites = self::get_sites(); + foreach ( $sites as $site ) { + $blog_id = self::get_site_blog_id( $site ); + + if ( + is_null( $is_delegated ) || + $is_delegated === $this->is_site_delegated_connection( $blog_id ) + ) { + $blog_ids[] = $blog_id; + } + } + + return $blog_ids; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 2.5.1 + * + * @return int[] + */ + private function get_non_delegated_blog_ids() { + return $this->get_blog_ids( false ); + } + /** * Gets a map of module IDs that the given user has opted-in to. * @@ -15373,11 +15830,16 @@ function find_first_install() { * * @param int $blog_id * @param FS_Site $install + * @param bool $flush * * @return bool Since 2.3.1 returns if a switch was made. */ - function switch_to_blog( $blog_id, FS_Site $install = null ) { - if ( ! is_numeric( $blog_id ) || $blog_id == $this->_context_is_network_or_blog_id ) { + function switch_to_blog( $blog_id, FS_Site $install = null, $flush = false ) { + if ( ! is_numeric( $blog_id ) ) { + return false; + } + + if ( ! $flush && $blog_id == $this->_context_is_network_or_blog_id ) { return false; } @@ -15441,7 +15903,7 @@ function switch_to_blog( $blog_id, FS_Site $install = null ) { unset( $this->_site_api ); unset( $this->_user_api ); - return false; + return true; } /** @@ -15470,21 +15932,41 @@ static function get_site_blog_id( &$site ) { $site['blog_id'] ); } + /** + * @author Vova Feldman (@svovaf) + * @since 2.5.1 + * + * @param WP_Site[]|array[] $sites + * + * @return int[] + */ + static function get_sites_blog_ids( $sites ) { + $blog_ids = array(); + foreach ( $sites as $site ) { + $blog_ids[] = self::get_site_blog_id( $site ); + } + + return $blog_ids; + } + /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param array|WP_Site|null $site + * @param bool $load_registration Since 2.5.1 When set to `true` the method will attempt to return the subsite's registration date, regardless of the `$site` type and value. In most calls, the registration date will be returned anyway, even when the value is `false`. This param is purely for performance optimization. * * @return array */ - function get_site_info( $site = null ) { + function get_site_info( $site = null, $load_registration = false ) { $this->_logger->entrance(); $switched = false; + $registration_date = null; + if ( is_null( $site ) ) { - $url = get_site_url(); + $url = self::get_unfiltered_site_url(); $name = get_bloginfo( 'name' ); $blog_id = null; } else { @@ -15496,26 +15978,44 @@ function get_site_info( $site = null ) { } if ( $site instanceof WP_Site ) { - $url = $site->siteurl; - $name = $site->blogname; + $url = $site->siteurl; + $name = $site->blogname; + $registration_date = $site->registered; } else { - $url = get_site_url( $blog_id ); + $url = self::get_unfiltered_site_url( $blog_id ); $name = get_bloginfo( 'name' ); } } + if ( empty( $registration_date ) && $load_registration ) { + $blog_details = get_blog_details( $blog_id, false ); + + if ( is_object( $blog_details ) && isset( $blog_details->registered ) ) { + $registration_date = $blog_details->registered; + } + } + $info = array( - 'uid' => $this->get_anonymous_id( $blog_id ), - 'url' => $url, - 'title' => $name, - 'language' => get_bloginfo( 'language' ), - 'charset' => get_bloginfo( 'charset' ), + 'uid' => $this->get_anonymous_id( $blog_id ), + 'url' => $url, ); + // Add these diagnostic information only if user allowed to track. + if ( FS_Permission_Manager::instance( $this )->is_diagnostic_tracking_allowed() ) { + $info = array_merge( $info, array( + 'title' => $name, + 'language' => self::get_sanitized_language(), + ) ); + } + if ( is_numeric( $blog_id ) ) { $info['blog_id'] = $blog_id; } + if ( ! empty( $registration_date ) ) { + $info[ 'registration_date' ] = $registration_date; + } + if ( $switched ) { restore_current_blog(); } @@ -15738,6 +16238,10 @@ private function update_multisite_data_after_site_deactivation( $context_blog_id } } + if ( ! $this->is_registered() ) { + return; + } + if ( $this->is_sync_cron_scheduled() && $context_blog_id == $this->get_sync_cron_blog_id() ) { @@ -15771,6 +16275,10 @@ public function _after_site_deactivated_callback( $context_blog_id = 0 ) { $this->update_multisite_data_after_site_deactivation( $context_blog_id ); + if ( ! $this->is_registered() ) { + return; + } + $current_blog_id = get_current_blog_id(); $this->switch_to_blog( $context_blog_id ); @@ -15804,6 +16312,10 @@ public function _after_site_deleted_callback( $context_blog_id = 0, $drop = fals $this->update_multisite_data_after_site_deactivation( $context_blog_id ); + if ( ! $this->is_registered() ) { + return; + } + $current_blog_id = get_current_blog_id(); $this->switch_to_blog( $context_blog_id ); @@ -15821,6 +16333,20 @@ public function _after_site_deleted_callback( $context_blog_id = 0, $drop = fals $this->switch_to_blog( $current_blog_id ); } + /** + * Executed after site deletion, called from wp_delete_site + * + * @author Dario Curvino (@dudo) + * @since 2.5.0 + * + * @param WP_Site $old_site + */ + public function _after_wpsite_deleted_callback( WP_Site $old_site ) { + $this->_logger->entrance(); + + $this->_after_site_deleted_callback( $old_site->blog_id, true ); + } + /** * Executed after site re-activation. * @@ -15935,9 +16461,17 @@ function is_theme_settings_page() { * @return bool */ function is_product_settings_page() { + $page = fs_request_get( 'page', '', 'get' ); + $menu_slug = $this->_menu->get_slug(); + + if ( $page === $menu_slug ) { + return true; + } + return fs_starts_with( - fs_request_get( 'page', '', 'get' ), - $this->_menu->get_slug() + // e.g., {$menu_slug}-account, {$menu_slug}-affiliation, etc. + $page, + ( $menu_slug . '-' ) ); } @@ -16017,10 +16551,11 @@ function get_account_tab_url( $tab, $action = false, $params = array(), $add_act * * @param bool|string $topic * @param bool|string $message + * @param bool|string $summary Since 2.5.1. * * @return string */ - function contact_url( $topic = false, $message = false ) { + function contact_url( $topic = false, $message = false, $summary = false ) { $params = array(); if ( is_string( $topic ) ) { $params['topic'] = $topic; @@ -16029,6 +16564,10 @@ function contact_url( $topic = false, $message = false ) { $params['message'] = $message; } + if ( is_string( $summary ) ) { + $params['summary'] = $summary; + } + if ( $this->is_addon() ) { $params['addon_id'] = $this->get_id(); @@ -16067,7 +16606,7 @@ function get_addons_url() { } /* Logger - ------------------------------------------------------------------------------------------------------------------*/ + ------------------------------------------------------------------------------------------------------------------*/ /** * @param string $id * @param bool $prefix_slug @@ -16092,7 +16631,7 @@ function get_options_manager( $id, $load_options = false, $prefix_slug = true ) } /* Security - ------------------------------------------------------------------------------------------------------------------*/ + ------------------------------------------------------------------------------------------------------------------*/ private static function _encrypt( $str ) { if ( is_null( $str ) ) { return null; @@ -16179,64 +16718,6 @@ private static function decrypt_entity( FS_Entity $entity ) { return $clone; } - /** - * Tries to activate account based on POST params. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.2 - * - * @deprecated Not in use, outdated. - */ - function _activate_account() { - if ( $this->is_registered() ) { - // Already activated. - return; - } - - self::_clean_admin_content_section(); - - if ( fs_request_is_action( 'activate' ) && fs_request_is_post() ) { -// check_admin_referer( 'activate_' . $this->_plugin->public_key ); - - // Verify matching plugin details. - if ( $this->_plugin->id != fs_request_get( 'plugin_id' ) || $this->_slug != fs_request_get( 'plugin_slug' ) ) { - return; - } - - $user = new FS_User(); - $user->id = fs_request_get( 'user_id' ); - $user->public_key = fs_request_get( 'user_public_key' ); - $user->secret_key = fs_request_get( 'user_secret_key' ); - $user->email = fs_request_get( 'user_email' ); - $user->first = fs_request_get( 'user_first' ); - $user->last = fs_request_get( 'user_last' ); - $user->is_verified = fs_request_get_bool( 'user_is_verified' ); - - $site = new FS_Site(); - $site->id = fs_request_get( 'install_id' ); - $site->public_key = fs_request_get( 'install_public_key' ); - $site->secret_key = fs_request_get( 'install_secret_key' ); - $site->plan_id = fs_request_get( 'plan_id' ); - - $plans = array(); - $plans_data = json_decode( urldecode( fs_request_get( 'plans' ) ) ); - foreach ( $plans_data as $p ) { - $plan = new FS_Plugin_Plan( $p ); - if ( $site->plan_id == $plan->id ) { - $plan->title = fs_request_get( 'plan_title' ); - $plan->name = fs_request_get( 'plan_name' ); - } - - $plans[] = $plan; - } - - $this->_set_account( $user, $site, $plans ); - - // Reload the page with the keys. - fs_redirect( $this->_get_admin_page_url() ); - } - } - /** * @author Vova Feldman (@svovaf) * @since 1.0.7 @@ -16245,7 +16726,7 @@ function _activate_account() { * * @return FS_User|false */ - static function _get_user_by_email( $email ) { + public static function _get_user_by_email( $email ) { self::$_static_logger->entrance(); $email = trim( strtolower( $email ) ); @@ -16312,20 +16793,6 @@ private function _load_account() { ) { // Load site. $this->_site = $site; - - // Load plans. - $this->_plans = $plans[ $this->_slug ]; - if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) { - $this->_sync_plans(); - } else { - for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { - if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) { - $this->_plans[ $i ] = self::decrypt_entity( $this->_plans[ $i ] ); - } else { - unset( $this->_plans[ $i ] ); - } - } - } } $user = null; @@ -16354,7 +16821,30 @@ private function _load_account() { /** * This is a special fault tolerance mechanism to handle a scenario that the user data is missing. */ - $user = $this->sync_user_by_current_install(); + if ( + ! isset( $this->_storage->user_recovery_from_install_last_attempt_timestamp ) || + time() > ( $this->_storage->user_recovery_from_install_last_attempt_timestamp + FS_Clone_Manager::CLONE_RESOLUTION_MAX_EXECUTION_TIME ) + ) { + $user = $this->sync_user_by_current_install(); + } else { + return; + } + + if ( is_object( $user ) ) { + $this->_storage->user_was_recovered_from_install = true; + } else { + $this->_storage->user_recovery_from_install_attempts = isset( $this->_storage->user_recovery_from_install_attempts ) ? + ( $this->_storage->user_recovery_from_install_attempts + 1 ) : + 1; + + if ( $this->_storage->user_recovery_from_install_attempts >= 3 ) { + $this->delete_current_install( false ); + } else { + $this->_storage->user_recovery_from_install_last_attempt_timestamp = time(); + + return; + } + } } $this->_user = ( $user instanceof FS_User ) ? @@ -16368,6 +16858,23 @@ private function _load_account() { } if ( is_object( $this->_site ) ) { + // Load plans. + $this->_plans = isset( $plans[ $this->_slug ] ) ? + $plans[ $this->_slug ] : + array(); + + if ( ! is_array( $this->_plans ) || empty( $this->_plans ) ) { + $this->_sync_plans(); + } else { + for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { + if ( $this->_plans[ $i ] instanceof FS_Plugin_Plan ) { + $this->_plans[ $i ] = self::decrypt_entity( $this->_plans[ $i ] ); + } else { + unset( $this->_plans[ $i ] ); + } + } + } + $this->_license = $this->_get_license_by_id( $this->_site->license_id ); if ( $this->_site->version != $this->get_plugin_version() ) { @@ -16386,6 +16893,15 @@ private function _load_account() { if ( $this->is_theme() ) { $this->_register_account_hooks(); } + + if ( $this->is_user_in_admin() && $this->is_clone() ) { + if ( empty( FS_Clone_Manager::instance()->get_clone_identification_timestamp() ) ) { + FS_Clone_Manager::instance()->store_clone_identification_timestamp(); + } + + FS_Clone_Manager::instance()->maybe_update_clone_resolution_support_flag( $this->_storage->sdk_last_version ); + $this->send_pending_clone_update_once(); + } } /** @@ -16465,19 +16981,131 @@ private function _set_account( FS_User $user, FS_Site $site, $plans = false ) { */ private function get_versions() { $versions = array(); - $versions['platform_version'] = get_bloginfo( 'version' ); - $versions['sdk_version'] = $this->version; - $versions['programming_language_version'] = phpversion(); + $versions['sdk_version'] = $this->version; + + // Collect these diagnostic information only if it's allowed. + if ( FS_Permission_Manager::instance( $this )->is_diagnostic_tracking_allowed() ) { + $versions['platform_version'] = get_bloginfo( 'version' ); + $versions['programming_language_version'] = phpversion(); + } foreach ( $versions as $k => $version ) { - if ( is_string( $versions[ $k ] ) && ! empty( $versions[ $k ] ) ) { - $versions[ $k ] = substr( $versions[ $k ], 0, 16 ); - } + $versions[ $k ] = self::get_api_sanitized_property( $k, $version ); } return $versions; } + /** + * Get sanitized site language. + * + * @param string $language + * @param int $max_len + * + * @since 2.5.1 + * @author Vova Feldman (@svovaf) + * + * @return string + */ + private static function get_sanitized_language( $language = '', $max_len = self::LANGUAGE_MAX_CHARS ) { + if ( empty( $language ) ) { + $language = get_bloginfo( 'language' ); + } + + return substr( $language, 0, $max_len ); + } + + /** + * Get core version stripped from pre-release and build. + * + * @since 2.5.1 + * @author Vova Feldman (@svovaf) + * + * @param string $version + * @param int $parts + * @param int $max_len + * @param bool $include_pre_release + * + * @return string + */ + private static function get_core_version( + $version, + $parts = 3, + $max_len = self::VERSION_MAX_CHARS, + $include_pre_release = false + ) { + if ( empty( $version ) ) { + // Version is empty. + return ''; + } + + if ( is_numeric( $version ) ) { + $is_float_version = is_float( $version ); + + $version = (string) $version; + + /** + * Casting a whole float number to a string cuts the decimal point. This part make sure to add the missing decimal part to the version. + */ + if ( $is_float_version && false === strpos( $version, '.' ) ) { + $version .= '.0'; + } + } + + if ( ! is_string( $version ) ) { + return ''; + } + + if ( $parts < 1 ) { + return ''; + } + + $pre_release_regex = $include_pre_release ? + '(\-(alpha|beta|RC)([0-9]+)?)?' : + ''; + + if ( 0 === preg_match( '/^([0-9]+(\.[0-9]+){0,' . ( $parts - 1 ) . '}' . $pre_release_regex . ')/i', $version, $matches ) ) { + // Version is not starting with a digit. + return ''; + } + + return substr( $matches[1], 0, $max_len ); + } + + /** + * @param string $prop + * @param mixed $val + * + * @return mixed + *@author Vova Feldman (@svovaf) + * + * @since 2.5.1 + */ + private static function get_api_sanitized_property( $prop, $val ) { + if ( ! is_string( $val ) || empty( $val ) ) { + return $val; + } + + switch ( $prop ) { + case 'programming_language_version': + // Get core PHP version, which can have up to 3 parts (ignore pre-releases). + return self::get_core_version( $val ); + case 'platform_version': + // Get the exact WordPress version, which can have up to 3 parts (including pre-releases). + return self::get_core_version( $val, 3, self::VERSION_MAX_CHARS, true ); + case 'sdk_version': + // Get the exact SDK version, which can have up to 4 parts. + return self::get_core_version( $val, 4 ); + case 'version': + // Get the entire version but just limited in length. + return substr( $val, 0, self::VERSION_MAX_CHARS ); + case 'language': + return self::get_sanitized_language( $val ); + default: + return $val; + } + } + /** * @author Leo Fajardo (@leorw) * @since 2.3.0 @@ -16530,23 +17158,22 @@ function get_opt_in_params( $override_with = array(), $network_level_or_blog_id $versions = $this->get_versions(); $params = array_merge( $versions, array( - 'user_firstname' => $current_user->user_firstname, - 'user_lastname' => $current_user->user_lastname, - 'user_nickname' => $current_user->user_nicename, - 'user_email' => $current_user->user_email, - 'user_ip' => WP_FS__REMOTE_ADDR, - 'plugin_slug' => $this->_slug, - 'plugin_id' => $this->get_id(), - 'plugin_public_key' => $this->get_public_key(), - 'plugin_version' => $this->get_plugin_version(), - 'return_url' => fs_nonce_url( $return_url, $activation_action ), - 'account_url' => fs_nonce_url( $this->_get_admin_page_url( + 'user_firstname' => $current_user->user_firstname, + 'user_lastname' => $current_user->user_lastname, + 'user_email' => $current_user->user_email, + 'plugin_slug' => $this->_slug, + 'plugin_id' => $this->get_id(), + 'plugin_public_key' => $this->get_public_key(), + 'plugin_version' => $this->get_plugin_version(), + 'return_url' => fs_nonce_url( $return_url, $activation_action ), + 'account_url' => fs_nonce_url( $this->_get_admin_page_url( 'account', array( 'fs_action' => 'sync_user' ) ), 'sync_user' ), - 'is_premium' => $this->is_premium(), - 'is_active' => true, - 'is_uninstalled' => false, + 'is_premium' => $this->is_premium(), + 'is_active' => true, + 'is_uninstalled' => false, + 'is_localhost' => WP_FS__IS_LOCALHOST, ) ); if ( $this->is_addon() ) { @@ -16567,12 +17194,17 @@ function get_opt_in_params( $override_with = array(), $network_level_or_blog_id $site = $this->get_site_info( $site ); - $params = array_merge( $params, array( + $diagnostic_info = array(); + if ( FS_Permission_Manager::instance( $this )->is_diagnostic_tracking_allowed() ) { + $diagnostic_info = array( + 'site_name' => $site['title'], + 'language' => self::get_sanitized_language( $site['language'] ), + ); + } + + $params = array_merge( $params, $diagnostic_info, array( 'site_uid' => $site['uid'], 'site_url' => $site['url'], - 'site_name' => $site['title'], - 'language' => $site['language'], - 'charset' => $site['charset'], ) ); } @@ -16597,6 +17229,10 @@ function get_opt_in_params( $override_with = array(), $network_level_or_blog_id ); } + if ( is_multisite() && function_exists( 'get_network' ) ) { + $params['network_uid'] = $this->get_anonymous_network_id(); + } + return array_merge( $params, $override_with ); } @@ -16615,9 +17251,10 @@ function get_opt_in_params( $override_with = array(), $network_level_or_blog_id * In this case, the user and site info will be sent to the server but no * data will be saved to the WP installation's database. * @param number|bool $trial_plan_id - * @param bool $is_disconnected Whether or not to opt in without tracking. + * @param bool $is_disconnected Whether to opt in without tracking. * @param null|bool $is_marketing_allowed * @param array $sites If network-level opt-in, an array of containing details of sites. + * @param bool $redirect * * @return string|object * @use WP_Error @@ -16631,7 +17268,8 @@ function opt_in( $trial_plan_id = false, $is_disconnected = false, $is_marketing_allowed = null, - $sites = array() + $sites = array(), + $redirect = true ) { $this->_logger->entrance(); @@ -16655,7 +17293,7 @@ function opt_in( $fs_user, false, $trial_plan_id, - true, + $redirect, true, $sites ); @@ -16723,13 +17361,15 @@ function opt_in( $params['is_marketing_allowed'] = $is_marketing_allowed; } - $params['is_disconnected'] = $is_disconnected; - $params['format'] = 'json'; + $params['is_disconnected'] = $is_disconnected; + $params['format'] = 'json'; + $params['is_extensions_tracking_allowed'] = FS_Permission_Manager::instance( $this )->is_extensions_tracking_allowed(); + $params['is_diagnostic_tracking_allowed'] = FS_Permission_Manager::instance( $this )->is_diagnostic_tracking_allowed(); $request = array( 'method' => 'POST', 'body' => $params, - 'timeout' => WP_FS__DEBUG_SDK ? 60 : 30, + 'timeout' => 60, ); $url = $this->add_show_pending( WP_FS__ADDRESS . '/action/service/user/install/' ); @@ -16753,9 +17393,25 @@ function opt_in( $this->maybe_modify_api_curl_error_message( $result ); + if ( FS_Api::is_blocked( $result ) ) { + $result->error->message = $this->generate_api_blocked_notice_message_from_result( $result ); + } + + $is_connected = null; + + if ( empty( $license_key ) && $this->is_enable_anonymous() ) { + $this->skip_connection( fs_is_network_admin() ); + + $is_connected = ( ! FS_Api::is_blocked( $result ) ); + } + + $this->update_connectivity_info( $is_connected ); + return $result; } + $this->update_connectivity_info( true ); + // Module is being uninstalled, don't handle the returned data. if ( $is_uninstall ) { return true; @@ -16815,7 +17471,8 @@ function opt_in( true ), false, $filtered_license_key, - ! empty( $params['trial_plan_id'] ) + ! empty( $params['trial_plan_id'] ), + isset( $decoded->is_suspicious_email ) && $decoded->is_suspicious_email ); } else if ( isset( $decoded->install_secret_key ) ) { return $this->install_with_new_user( @@ -16828,6 +17485,9 @@ function opt_in( ( isset( $decoded->is_extensions_tracking_allowed ) && ! is_null( $decoded->is_extensions_tracking_allowed ) ? $decoded->is_extensions_tracking_allowed : null ), + ( isset( $decoded->is_diagnostic_tracking_allowed ) && ! is_null( $decoded->is_diagnostic_tracking_allowed ) ? + $decoded->is_diagnostic_tracking_allowed : + null ), $decoded->install_id, $decoded->install_public_key, $decoded->install_secret_key, @@ -16844,6 +17504,9 @@ function opt_in( ( isset( $decoded->is_extensions_tracking_allowed ) && ! is_null( $decoded->is_extensions_tracking_allowed ) ? $decoded->is_extensions_tracking_allowed : null ), + ( isset( $decoded->is_diagnostic_tracking_allowed ) && ! is_null( $decoded->is_diagnostic_tracking_allowed ) ? + $decoded->is_diagnostic_tracking_allowed : + null ), $decoded->installs, false ); @@ -16925,6 +17588,9 @@ function setup_network_account( // Only network level opt-in can have more than one install. $is_network_level_opt_in = true; } + + $this->update_connectivity_info( true ); + // $is_network_level_opt_in = self::is_ajax_action_static( 'network_activate', $this->_module_id ); // If Freemius was OFF before, turn it on. $this->turn_on(); @@ -16941,15 +17607,11 @@ function setup_network_account( $this->_admin_notices->remove_sticky( 'connect_account' ); if ( $this->is_pending_activation() || ! $this->has_settings_menu() ) { - // Remove pending activation sticky notice (if still exist). - $this->_admin_notices->remove_sticky( 'activation_pending' ); - - // Remove plugin from pending activation mode. - unset( $this->_storage->is_pending_activation ); + $this->clear_pending_activation_mode(); if ( ! $this->is_paying_or_trial() ) { $this->_admin_notices->add_sticky( - sprintf( $this->get_text_inline( '%s activation was successfully completed.', 'plugin-x-activation-message' ), '' . $this->get_plugin_name() . '' ), + sprintf( $this->get_text_inline( '%s opt-in was successfully completed.', 'plugin-x-activation-message' ), '' . $this->get_plugin_name() . '' ), 'activation_complete' ); } @@ -16961,24 +17623,23 @@ function setup_network_account( ! $this->has_settings_menu() ) { if ( $this->is_paying() ) { - $this->_admin_notices->add_sticky( + $this->add_complete_upgrade_instructions_notice( sprintf( $this->get_text_inline( 'Your account was successfully activated with the %s plan.', 'activation-with-plan-x-message' ), $this->get_plan_title() - ) . $this->get_complete_upgrade_instructions(), - 'plan_upgraded', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ), + 'plan_upgraded' ); } else { $trial_plan = $this->get_trial_plan(); - $this->_admin_notices->add_sticky( + $this->add_complete_upgrade_instructions_notice( sprintf( $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), '' . $this->get_plugin_name() . '' - ) . $this->get_complete_upgrade_instructions( $trial_plan->title ), + ), 'trial_started', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + $trial_plan->title ); } } @@ -17055,6 +17716,10 @@ function _install_with_new_user() { return; } + $has_pending_activation_confirmation_param = fs_request_has( 'pending_activation' ); + + $this->update_license_required_permissions_if_anonymous(); + if ( ( $this->is_plugin() && fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) || // @todo This logic should be improved because it's executed on every load of a theme. $this->is_theme() @@ -17067,10 +17732,11 @@ function _install_with_new_user() { $this->install_many_pending_with_user( fs_request_get( 'user_id' ), - fs_request_get( 'user_public_key' ), - fs_request_get( 'user_secret_key' ), + fs_request_get_raw( 'user_public_key' ), + fs_request_get_raw( 'user_secret_key' ), fs_request_get_bool( 'is_marketing_allowed', null ), fs_request_get_bool( 'is_extensions_tracking_allowed', null ), + fs_request_get_bool( 'is_diagnostic_tracking_allowed', null ), $pending_sites_info['blog_ids'], $pending_sites_info['license_key'], $pending_sites_info['trial_plan_id'] @@ -17078,19 +17744,28 @@ function _install_with_new_user() { } else { $this->install_with_new_user( fs_request_get( 'user_id' ), - fs_request_get( 'user_public_key' ), - fs_request_get( 'user_secret_key' ), + fs_request_get_raw( 'user_public_key' ), + fs_request_get_raw( 'user_secret_key' ), fs_request_get_bool( 'is_marketing_allowed', null ), fs_request_get_bool( 'is_extensions_tracking_allowed', null ), + fs_request_get_bool( 'is_diagnostic_tracking_allowed', null ), fs_request_get( 'install_id' ), - fs_request_get( 'install_public_key' ), - fs_request_get( 'install_secret_key' ), + fs_request_get_raw( 'install_public_key' ), + fs_request_get_raw( 'install_secret_key' ), true, fs_request_get_bool( 'auto_install' ) ); } - } else if ( fs_request_has( 'pending_activation' ) ) { - $this->set_pending_confirmation( fs_request_get( 'user_email' ), true ); + } else if ( $has_pending_activation_confirmation_param ) { + $this->set_pending_confirmation( + fs_request_get( 'user_email' ), + true, + false, + false, + fs_request_get_bool( 'is_suspicious_email' ), + fs_request_get_bool( 'has_upgrade_context' ), + fs_request_get( 'support_email_address' ) + ); } } } @@ -17138,6 +17813,7 @@ private function setup_user( $id, $public_key, $secret_key ) { * @param string $user_secret_key * @param bool|null $is_marketing_allowed * @param bool|null $is_extensions_tracking_allowed Since 2.3.2 + * @param bool|null $is_diagnostic_tracking_allowed Since 2.5.0.2 * @param number $install_id * @param string $install_public_key * @param string $install_secret_key @@ -17152,6 +17828,7 @@ private function install_with_new_user( $user_secret_key, $is_marketing_allowed, $is_extensions_tracking_allowed, + $is_diagnostic_tracking_allowed, $install_id, $install_public_key, $install_secret_key, @@ -17185,7 +17862,7 @@ private function install_with_new_user( $site->secret_key = $install_secret_key; $this->_site = $site; - $site_result = $this->get_api_site_scope()->get(); + $site_result = $this->get_api_site_scope( true )->get(); $site = new FS_Site( $site_result ); $this->_site = $site; @@ -17193,7 +17870,10 @@ private function install_with_new_user( $this->disable_opt_in_notice_and_lock_user(); } - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); + FS_Permission_Manager::instance( $this )->update_permissions_tracking_flag( array( + FS_Permission_Manager::PERMISSION_DIAGNOSTIC => $is_diagnostic_tracking_allowed, + FS_Permission_Manager::PERMISSION_EXTENSIONS => $is_extensions_tracking_allowed, + ) ); return $this->setup_account( $this->_user, @@ -17214,12 +17894,13 @@ private function install_with_new_user( * @param string $user_secret_key * @param bool|null $is_marketing_allowed * @param bool|null $is_extensions_tracking_allowed Since 2.3.2 + * @param bool|null $is_diagnostic_tracking_allowed Since 2.5.0.2 * @param array $site_ids * @param bool $license_key * @param bool $trial_plan_id * @param bool $redirect * - * @return string If redirect is `false`, returns the next page the user should be redirected to. + * @return void */ private function install_many_pending_with_user( $user_id, @@ -17227,6 +17908,7 @@ private function install_many_pending_with_user( $user_secret_key, $is_marketing_allowed, $is_extensions_tracking_allowed, + $is_diagnostic_tracking_allowed, $site_ids, $license_key = false, $trial_plan_id = false, @@ -17238,7 +17920,10 @@ private function install_many_pending_with_user( $this->disable_opt_in_notice_and_lock_user(); } - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); + FS_Permission_Manager::instance( $this )->update_permissions_tracking_flag( array( + FS_Permission_Manager::PERMISSION_DIAGNOSTIC => $is_diagnostic_tracking_allowed, + FS_Permission_Manager::PERMISSION_EXTENSIONS => $is_extensions_tracking_allowed, + ) ); $sites = array(); foreach ( $site_ids as $site_id ) { @@ -17259,6 +17944,7 @@ private function install_many_pending_with_user( * @param string $user_secret_key * @param bool|null $is_marketing_allowed * @param bool|null $is_extensions_tracking_allowed Since 2.3.2 + * @param bool|null $is_diagnostic_tracking_allowed Since 2.5.0.2 * @param object[] $installs * @param bool $redirect * @param bool $auto_install Since 1.2.1.7 If `true` and setting up an account with a valid license, will redirect (or return a URL) to the account page with a special parameter to trigger the auto installation processes. @@ -17271,6 +17957,7 @@ private function install_many_with_new_user( $user_secret_key, $is_marketing_allowed, $is_extensions_tracking_allowed, + $is_diagnostic_tracking_allowed, array $installs, $redirect = true, $auto_install = false @@ -17281,7 +17968,10 @@ private function install_many_with_new_user( $this->disable_opt_in_notice_and_lock_user(); } - $this->update_extensions_tracking_flag( $is_extensions_tracking_allowed ); + FS_Permission_Manager::instance( $this )->update_permissions_tracking_flag( array( + FS_Permission_Manager::PERMISSION_DIAGNOSTIC => $is_diagnostic_tracking_allowed, + FS_Permission_Manager::PERMISSION_EXTENSIONS => $is_extensions_tracking_allowed, + ) ); $install_ids = array(); @@ -17289,12 +17979,13 @@ private function install_many_with_new_user( $install_ids[] = $install->id; } - $left = count( $install_ids ); - $offset = 0; + $items_per_request = 25; + $left = count( $install_ids ); + $offset = 0; $installs = array(); while ( $left > 0 ) { - $result = $this->get_api_user_scope()->get( "/plugins/{$this->_module_id}/installs.json?ids=" . implode( ',', array_slice( $install_ids, $offset, 25 ) ) ); + $result = $this->get_api_user_scope()->get( "/plugins/{$this->_module_id}/installs.json?ids=" . implode( ',', array_slice( $install_ids, $offset, $items_per_request ) ) ); if ( ! $this->is_api_result_object( $result, 'installs' ) ) { // @todo Handle API error. @@ -17302,7 +17993,8 @@ private function install_many_with_new_user( $installs = array_merge( $installs, $result->installs ); - $left -= 25; + $left -= $items_per_request; + $offset += $items_per_request; } foreach ( $installs as &$install ) { @@ -17325,6 +18017,9 @@ private function install_many_with_new_user( * @param bool $redirect * @param string|bool $license_key Since 1.2.1.5 * @param bool $is_pending_trial Since 1.2.1.5 + * @param bool $is_suspicious_email Since 2.5.0 + * @param bool $has_upgrade_context Since 2.5.3 + * @param bool|string $support_email_address Since 2.5.3 * * @return string Since 1.2.1.5 if $redirect is `false`, return the pending activation page. */ @@ -17332,22 +18027,33 @@ private function set_pending_confirmation( $email = false, $redirect = true, $license_key = false, - $is_pending_trial = false + $is_pending_trial = false, + $is_suspicious_email = false, + $has_upgrade_context = false, + $support_email_address = false ) { - if ( $this->_ignore_pending_mode ) { + $is_network_admin = fs_is_network_admin(); + + if ( $this->_ignore_pending_mode && ! $has_upgrade_context ) { /** * If explicitly asked to ignore pending mode, set to anonymous mode - * if require confirmation before finalizing the opt-in. + * if require confirmation before finalizing the opt-in except after completing a purchase (otherwise, in this case, they wouldn't see any notice telling them that they should receive their license key via email). * * @author Vova Feldman * @since 1.2.1.6 */ - $this->skip_connection( null, fs_is_network_admin() ); + $this->skip_connection( $is_network_admin ); } else { // Install must be activated via email since // user with the same email already exist. $this->_storage->is_pending_activation = true; - $this->_add_pending_activation_notice( $email, $is_pending_trial ); + $this->_add_pending_activation_notice( + $email, + $is_pending_trial, + $is_suspicious_email, + $has_upgrade_context, + $support_email_address + ); } if ( ! empty( $license_key ) ) { @@ -17362,8 +18068,8 @@ private function set_pending_confirmation( $next_page = $this->get_after_activation_url( 'after_pending_connect_url' ); - // Reload the page with with pending activation message. if ( $redirect ) { + // Reload the page with a pending activation message. fs_redirect( $next_page ); } @@ -17384,15 +18090,18 @@ function _install_with_current_user() { } if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) && fs_request_is_post() ) { -// check_admin_referer( 'activate_existing_' . $this->_plugin->public_key ); + check_admin_referer( $this->get_unique_affix() . '_activate_existing' ); /** * @author Vova Feldman (@svovaf) * @since 1.1.9 Add license key if given. */ - $license_key = fs_request_get( 'license_secret_key' ); + $license_key = fs_request_get_raw( 'license_secret_key' ); - $this->update_extensions_tracking_flag( fs_request_get_bool( 'is_extensions_tracking_allowed', null ) ); + FS_Permission_Manager::instance( $this )->update_permissions_tracking_flag( array( + FS_Permission_Manager::PERMISSION_DIAGNOSTIC => fs_request_get_bool( 'is_diagnostic_tracking_allowed', null ), + FS_Permission_Manager::PERMISSION_EXTENSIONS => fs_request_get_bool( 'is_extensions_tracking_allowed', null ), + ) ); $this->install_with_current_user( $license_key ); } @@ -17410,7 +18119,7 @@ function _install_with_current_user() { * * @return object|string If redirect is `false`, returns the next page the user should be redirected to, or the API error object if failed to install. */ - private function install_with_current_user( + function install_with_current_user( $license_key = false, $trial_plan_id = false, $sites = array(), @@ -17607,6 +18316,14 @@ private function _activate_addon_account( return; } + $permission_ids = FS_Permission_Manager::get_all_permission_ids(); + $permissions = array(); + foreach ( $permission_ids as $permission_id ) { + $permissions[ $permission_id ] = FS_Permission_Manager::instance( $parent_fs )->is_permission( $permission_id, true ); + } + + FS_Permission_Manager::instance( $this )->update_permissions_tracking_flag( $permissions ); + /** * Do not override the `uid` if network-level opt-in since the call to `get_sites_for_network_level_optin()` * already returns the data for the current blog. @@ -17793,9 +18510,6 @@ private function handle_account_connection( $installs, $is_site_level ) { $this->send_installs_update(); } - // Switch install context back to the first install. - $this->_site = $first_install; - $current_blog = get_current_blog_id(); foreach ( $blog_2_install_map as $blog_id => $install ) { @@ -17804,7 +18518,12 @@ private function handle_account_connection( $installs, $is_site_level ) { $this->do_action( 'after_account_connection', $this->_user, $install ); } - $this->switch_to_blog( $current_blog ); + // Switch install context back to the first install. + $this->switch_to_blog( + $current_blog, + $first_install, + ( $this->_site->id != $first_install->id ) + ); $this->do_action( 'after_network_account_connection', $this->_user, $blog_2_install_map ); } @@ -17852,9 +18571,7 @@ private function activate_parent_account( Freemius $parent_fs ) { $parent_fs->_admin_notices->remove_sticky( 'connect_account' ); if ( $parent_fs->is_pending_activation() ) { - $parent_fs->_admin_notices->remove_sticky( 'activation_pending' ); - - unset( $parent_fs->_storage->is_pending_activation ); + $parent_fs->clear_pending_activation_mode(); } // Get user information based on parent's plugin. @@ -17868,6 +18585,8 @@ private function activate_parent_account( Freemius $parent_fs ) { // Sync add-on plans. $parent_fs->_sync_plans(); + $parent_fs->update_license_required_permissions_if_anonymous(); + $parent_fs->_set_account( $user, $parent_fs->_site ); } @@ -17908,6 +18627,10 @@ function _prepare_admin_menu() { // return; // } + if ( is_object( $this->_site ) && ! $this->is_registered() ) { + return; + } + /** * When running from a site admin with a network activated module and the connection * was NOT delegated and the user still haven't skipped or opted-in, then hide the @@ -17926,7 +18649,7 @@ function _prepare_admin_menu() { $should_hide_site_admin_settings = $this->apply_filters( 'should_hide_site_admin_settings_on_network_activation_mode', $should_hide_site_admin_settings ); - if ( ( ! $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) || + if ( ( false === $this->has_api_connectivity() && ! $this->is_enable_anonymous() ) || $should_hide_site_admin_settings ) { $this->_menu->remove_menu_item( $should_hide_site_admin_settings ); @@ -18023,7 +18746,7 @@ private function override_plugin_menu_with_activation() { if ( ! $this->has_settings_menu() ) { // Add the opt-in page without a menu item. $hook = FS_Admin_Menu_Manager::add_subpage( - null, + '', $this->get_plugin_name(), $this->get_plugin_name(), 'manage_options', @@ -18455,7 +19178,7 @@ private function embed_submenu_items() { $hook = FS_Admin_Menu_Manager::add_subpage( $item['show_submenu'] ? $top_level_menu_slug : - null, + '', $item['page_title'], $menu_item, $capability, @@ -18470,7 +19193,7 @@ private function embed_submenu_items() { FS_Admin_Menu_Manager::add_subpage( $item['show_submenu'] ? $top_level_menu_slug : - null, + '', $item['page_title'], $menu_item, $capability, @@ -18862,7 +19585,7 @@ function check_ajax_referer( $tag ) { * * @return string */ - private static function get_ajax_action_static( $tag, $module_id = null ) { + static function get_ajax_action_static( $tag, $module_id = null ) { $action = "fs_{$tag}"; if ( ! empty( $module_id ) ) { @@ -18885,10 +19608,10 @@ private static function get_ajax_action_static( $tag, $module_id = null ) { * @uses do_action() */ function do_action( $tag, $arg = '' ) { - $this->_logger->entrance( $tag ); - $args = func_get_args(); + $this->_logger->entrance( $tag ); + call_user_func_array( 'do_action', array_merge( array( $this->get_action_tag( $tag ) ), array_slice( $args, 1 ) ) @@ -19028,6 +19751,30 @@ static function shoot_ajax_failure( $error = '' ) { wp_send_json( $result ); } + /** + * Returns an AJAX URL with a special extra param to indicate whether the request was triggered from the network admin or blog admin. + * + * @author Vova Feldman (@svovaf) + * @since 2.5.1 + * + * @param string $wrap_with By default, returns the AJAX URL wrapped with single quotes. + * + * @return string + */ + static function ajax_url( $wrap_with = "'") { + if ( fs_is_network_admin() ) { + $param_name = '_fs_network_admin'; + } else { + $param_name = '_fs_blog_admin'; + } + + $url = admin_url( 'admin-ajax.php', 'relative' ); + $url .= ( false === strpos( $url, '?' ) ) ? '?' : '&'; + $url .= "{$param_name}=true"; + + return "{$wrap_with}{$url}{$wrap_with}"; + } + /** * Apply filter, specific for the current context plugin. * @@ -19042,9 +19789,10 @@ static function shoot_ajax_failure( $error = '' ) { * @uses apply_filters() */ function apply_filters( $tag, $value ) { + $args = func_get_args(); + $this->_logger->entrance( $tag ); - $args = func_get_args(); array_unshift( $args, $this->get_unique_affix() ); return call_user_func_array( 'fs_apply_filter', $args ); @@ -19105,7 +19853,7 @@ function override_i18n( $key_value ) { } /* Account Page - ------------------------------------------------------------------------------------------------------------------*/ + ------------------------------------------------------------------------------------------------------------------*/ /** * Update site information. * @@ -19116,7 +19864,7 @@ function override_i18n( $key_value ) { * @param null|int $network_level_or_blog_id Since 2.0.0 * @param \FS_Site $site Since 2.0.0 */ - private function _store_site( $store = true, $network_level_or_blog_id = null, FS_Site $site = null ) { + private function _store_site( $store = true, $network_level_or_blog_id = null, FS_Site $site = null, $is_backup = false ) { $this->_logger->entrance(); if ( is_null( $site ) ) { @@ -19131,9 +19879,12 @@ private function _store_site( $store = true, $network_level_or_blog_id = null, F $site_clone = clone $site; - $sites = self::get_all_sites( $this->_module_type, $network_level_or_blog_id ); + $sites = self::get_all_sites( $this->_module_type, $network_level_or_blog_id, $is_backup ); - if ( is_object( $this->_user ) && $this->_user->id != $site->user_id ) { + if ( + ! $is_backup && + is_object( $this->_user ) && $this->_user->id != $site->user_id + ) { $this->sync_user_by_current_install( $site->user_id ); $prev_stored_user_id = $this->_storage->get( 'prev_user_id', false, $network_level_or_blog_id ); @@ -19158,7 +19909,26 @@ private function _store_site( $store = true, $network_level_or_blog_id = null, F $sites[ $this->_slug ] = $site_clone; - $this->set_account_option( 'sites', $sites, $store, $network_level_or_blog_id ); + $this->set_account_option( + ( $is_backup ? 'prev_' : '' ) . 'sites', + $sites, + $store, + $network_level_or_blog_id + ); + } + + /** + * Stores the context site in the sites backup storage. This logic is used before deleting the site info so that it can be restored later on if necessary (e.g., if the automatic clone resolution attempt fails). + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + private function back_up_site() { + $this->_logger->entrance(); + + $site_clone = clone $this->_site; + + $this->_store_site( true, null, $site_clone, true ); } /** @@ -20061,7 +20831,7 @@ function _set_data_debug_mode() { return; } - $license_or_user_key = fs_request_get( 'license_or_user_key' ); + $license_or_user_key = fs_request_get_raw( 'license_or_user_key' ); $transient_value = ( ! empty( $license_or_user_key ) ) ? 'true' : @@ -20399,10 +21169,10 @@ private function _sync_plugin_license( $this->switch_to_blog( $current_blog_id ); } - $result = $this->send_install_update( array(), true ); + $result = $this->send_install_update( array(), true, true ); $is_valid = $this->is_api_result_entity( $result ); } else { - $result = $this->send_installs_update( array(), true ); + $result = $this->send_installs_update( array(), true, true ); $is_valid = $this->is_api_result_object( $result, 'installs' ); } @@ -20412,39 +21182,55 @@ private function _sync_plugin_license( $this->switch_to_blog( $this->_storage->network_install_blog_id ); } - // Show API messages only if not background sync or if paying customer. + // Show API message only if not background sync or if paying customer. if ( ! $background || $this->is_paying() ) { // Try to ping API to see if not blocked. - if ( ! FS_Api::test() ) { + if ( FS_Api::is_blocked( $result ) ) { /** - * Failed to ping API - blocked! - * * @author Vova Feldman (@svovaf) * @since 1.1.6 Only show message related to one of the Freemius powered plugins. Once it will be resolved it will fix the issue for all plugins anyways. There's no point to scare users with multiple error messages. */ - $api = $this->get_api_site_scope(); + if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) { + // Add notice immediately if not a background sync. + $add_notice = ( ! $background ); + + if ( ! $add_notice ) { + $counter = (int) get_transient( '_fs_api_connection_retry_counter' ); + + // We only want to add the notice after 3 consecutive failures. + $add_notice = ( 3 <= $counter ); + + if ( ! $add_notice ) { + /** + * Update counter transient only if notice shouldn't be added. If it is added the transient will be reset anyway, because the retries mechanism should only start counting if the admin isn't aware of the connectivity issue. + * + * Also, since the background sync happens once a day, setting the transient expiration for a week should be enough to count 3 failures, if there's an actual connectivity issue. + */ + set_transient( '_fs_api_connection_retry_counter', $counter + 1, WP_FS__TIME_WEEK_IN_SEC ); + } + } - if ( ! self::$_global_admin_notices->has_sticky( 'api_blocked' ) ) { - self::$_global_admin_notices->add( - sprintf( - $this->get_text_inline( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$s', 'server-blocking-access' ), - $this->get_plugin_name(), - '' . implode( ', ', $this->apply_filters( 'api_domains', array( - 'api.freemius.com', - 'wp.freemius.com' - ) ) ) . '' - ) . '
          ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $result->error, true ), - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', - 'error', - $background, - 'api_blocked' - ); + // Add notice instantly for not-background sync and only after 3 failed attempts for background sync. + if ( $add_notice ) { + self::$_global_admin_notices->add( + $this->generate_api_blocked_notice_message_from_result( $result ), + '', + 'error', + $background, + 'api_blocked' + ); + + add_action( 'admin_footer', array( 'Freemius', '_add_api_connectivity_notice_handler_js' ) ); + + // Notice was just shown, reset connectivity counter. + delete_transient( '_fs_api_connection_retry_counter' ); + } } - } else { + } else if ( is_object( $result ) ) { // Authentication params are broken. $this->_admin_notices->add( $this->get_text_inline( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.', 'wrong-authentication-param-message' ) . '
          ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $result->error, true ), - $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', + '', 'error' ); } @@ -20454,6 +21240,9 @@ private function _sync_plugin_license( return; } + // API is working now. Delete the transient and start afresh. + delete_transient('_fs_api_connection_retry_counter'); + if ( $is_site_level_sync ) { $site = new FS_Site( $result ); } else { @@ -20645,7 +21434,7 @@ private function _sync_plugin_license( } if ( ! $this->is_addon() && - $this->_site->is_beta() !== $site->is_beta + $this->_site->is_beta() !== $site->is_beta() ) { // Beta flag updated. $this->_site = $site; @@ -20703,14 +21492,7 @@ private function _sync_plugin_license( break; case 'upgraded': case 'activated': - $this->_admin_notices->add_sticky( - ( 'activated' === $plan_change ) ? - $this->get_text_inline( 'Your plan was successfully activated.', 'plan-activated-message' ) : - $this->get_text_inline( 'Your plan was successfully upgraded.', 'plan-upgraded-message' ) . - $this->get_complete_upgrade_instructions(), - 'plan_upgraded', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' - ); + $this->add_after_plan_activation_or_upgrade_instructions_notice( 'upgraded' === $plan_change ); $this->_admin_notices->remove_sticky( array( 'trial_started', @@ -20772,13 +21554,13 @@ private function _sync_plugin_license( $this->_admin_notices->remove_sticky( 'plan_upgraded' ); break; case 'trial_started': - $this->_admin_notices->add_sticky( + $this->add_complete_upgrade_instructions_notice( sprintf( $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), '' . $this->get_plugin_name() . '' - ) . $this->get_complete_upgrade_instructions( $this->get_trial_plan()->title ), + ), 'trial_started', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + $this->get_trial_plan()->title ); $this->_admin_notices->remove_sticky( array( @@ -20815,6 +21597,42 @@ private function _sync_plugin_license( } } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param mixed $result + * + * @return string + */ + private function generate_api_blocked_notice_message_from_result( $result ) { + $api_domains = $this->apply_filters( 'api_domains', array( + 'api.freemius.com', + 'wp.freemius.com', + ) ); + + $api_domains_list_items = ''; + + foreach( $api_domains as $api_domain ) { + $api_domains_list_items .= "
        • {$api_domain}
        • "; + } + + $error_message = sprintf( + $this->get_text_inline( 'Your server is blocking the access to Freemius\' API, which is crucial for %1$s synchronization. Please contact your host to whitelist the following domains:%2$s', 'server-blocking-access' ), + $this->get_plugin_name(), + "
            {$api_domains_list_items}
          " . $this->get_text_inline( 'Show error details', 'show-error-details' ) . " " + ); + + $error_message = + "
          {$error_message}
          " . + ''; + + return $error_message; + } + /** * Include the required JS at the footer of the admin to trigger the license activation dialog box. * @@ -20946,11 +21764,9 @@ protected function _activate_license( $background = false, $premium_license = nu } if ( ! $background ) { - $this->_admin_notices->add_sticky( - $this->get_text_inline( 'Your license was successfully activated.', 'license-activated-message' ) . - $this->get_complete_upgrade_instructions(), - 'license_activated', - $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + $this->add_complete_upgrade_instructions_notice( + $this->get_text_inline( 'Your license was successfully activated.', 'license-activated-message' ), + 'license_activated' ); } @@ -21200,8 +22016,7 @@ function start_trial( $plan_name = false ) { if ( ! $this->is_api_result_entity( $plan ) ) { // Some API error while trying to start the trial. $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'Unexpected API error. Please contact the %s\'s author with the following error.', 'unexpected-api-error' ), $this->_module_type ) - . ' ' . var_export( $plan, true ), + $this->get_api_error_message( $plan ), $oops_text, 'error' ); @@ -21379,13 +22194,18 @@ function _fetch_latest_version( ) { $this->_logger->entrance(); + if ( $this->is_unresolved_clone( true ) ) { + return false; + } + $switch_to_blog_id = null; /** * @since 1.1.7.3 Check for plugin updates from Freemius only if opted-in. * @since 1.1.7.4 Also check updates for add-ons. */ - if ( ! $this->is_registered() && + if ( + ( ! $this->is_registered() || ! FS_Permission_Manager::instance( $this )->is_essentials_tracking_allowed() ) && ! $this->_is_addon_id( $addon_id ) ) { if ( ! is_multisite() ) { @@ -21395,6 +22215,10 @@ function _fetch_latest_version( $installs_map = $this->get_blog_install_map(); foreach ( $installs_map as $blog_id => $install ) { + if ( ! FS_Permission_Manager::instance( $this )->is_essentials_tracking_allowed( $blog_id ) ) { + continue; + } + /** * @var FS_Site $install */ @@ -21491,9 +22315,11 @@ private function download_latest_directly( $plugin_id = false ) { private function get_latest_download_api_url( $plugin_id = false ) { $this->_logger->entrance(); - return $this->get_api_site_scope()->get_signed_url( + $download_api_url = $this->get_api_site_scope()->get_signed_url( $this->_get_latest_version_endpoint( $plugin_id, 'zip' ) ); + + return str_replace( 'http:', 'https:', $download_api_url ); } /** @@ -21688,7 +22514,6 @@ private function sync_addons( $flush = false ) { private function update_email( $new_email ) { $this->_logger->entrance(); - $api = $this->get_api_user_scope(); $user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,email,is_verified", 'put', array( 'email' => $new_email, @@ -21704,7 +22529,6 @@ private function update_email( $new_email ) { $this->_store_user(); } else { // handle different error cases. - } return $user; @@ -21780,15 +22604,32 @@ private function is_array_instanceof( $array, $class ) { * @uses FS_Api * * @param string $candidate_email + * @param string $transfer_type * * @return bool Is ownership change successfully initiated. */ - private function init_change_owner( $candidate_email ) { + private function init_change_owner( $candidate_email, $transfer_type ) { $this->_logger->entrance(); + $installs_info_by_slug_map = $this->get_parent_and_addons_installs_info(); + $install_ids = array(); + + foreach ( $installs_info_by_slug_map as $slug => $install_info ) { + $install = $install_info['install']; + + if ( $this->_user->id != $install->user_id ) { + // Skip add-on installs that are not owned by the parent product's install's owner. + continue; + } + + $install_ids[ $slug ] = $install->id; + } + $api = $this->get_api_site_scope(); $result = $api->call( "/users/{$this->_user->id}.json", 'put', array( 'email' => $candidate_email, + 'transfer_type' => $transfer_type, + 'install_ids' => implode( ',', array_values( $install_ids ) ), 'after_confirm_url' => $this->_get_admin_page_url( 'account', array( 'fs_action' => 'change_owner' ) @@ -21810,28 +22651,113 @@ private function init_change_owner( $candidate_email ) { private function complete_change_owner() { $this->_logger->entrance(); - $site_result = $this->get_api_site_scope( true )->get(); - $site = new FS_Site( $site_result ); - $this->_site = $site; + $install_ids = fs_request_get( 'install_ids' ); + + if ( ! empty( $install_ids ) ) { + $install_ids = explode( ',', $install_ids ); + + foreach ( $install_ids as $key => $install_id ) { + if ( ! FS_Site::is_valid_id( $install_id ) ) { + unset( $install_ids[ $key ] ); + } + } + } - $user = new FS_User(); - $user->id = fs_request_get( 'user_id' ); + if ( ! is_array( $install_ids ) ) { + $install_ids = array(); + } + + $user = new FS_User(); + $user->id = fs_request_get( 'user_id' ); + $user->public_key = fs_request_get_raw( 'user_public_key' ); + $user->secret_key = fs_request_get_raw( 'user_secret_key' ); + + $prev_user = $this->_user; + $this->_user = $user; + + $result = $this->get_api_user_scope( true )->get( + "/installs.json?install_ids=" . implode( ',', $install_ids ) + ); + + $current_blog_sites = self::get_all_sites( $this->get_module_type() ); + + if ( $this->is_api_result_object( $result, 'installs' ) ) { + $site_id_slug_map = array(); + + foreach ( $current_blog_sites as $slug => $site ) { + $site_id_slug_map[ $site->id ] = $slug; + } + + foreach ( $result->installs as $install ) { + $site = new FS_Site( $install ); + + if ( ! isset( $site_id_slug_map[ $install->id ] ) ) { + continue; + } + + $current_blog_sites[ $site_id_slug_map[ $install->id ] ] = clone $site; + + if ( $this->_site->id == $site->id ) { + $this->_site = $site; + } + } + } // Validate install's user and given user. if ( $user->id != $this->_site->user_id ) { + $this->_user = $prev_user; + return false; } - $user->public_key = fs_request_get( 'user_public_key' ); - $user->secret_key = fs_request_get( 'user_secret_key' ); + $this->set_account_option( 'sites', $current_blog_sites, true ); // Fetch new user information. - $this->_user = $user; $user_result = $this->get_api_user_scope( true )->get(); $user = new FS_User( $user_result ); $this->_user = $user; - $this->_set_account( $user, $site ); + $this->_set_account( $user, $this->_site ); + + $remove_user = true; + $all_modules_sites = self::get_all_modules_sites(); + + foreach ( $all_modules_sites as $sites_by_module_type ) { + foreach ( $sites_by_module_type as $sites_by_slug ) { + foreach ( $sites_by_slug as $site ) { + if ( $prev_user->id == $site->user_id ) { + $remove_user = false; + break; + } + } + + if ( ! $remove_user ) { + break; + } + } + + if ( ! $remove_user ) { + break; + } + } + + if ( $remove_user ) { + $users = self::get_all_users(); + + if ( isset( $users[ $prev_user->id ] ) ) { + unset( $users[ $prev_user->id ] ); + } else { + // If the prev user wasn't found by the key, iterate over the users collection. + foreach ( $users as $key => $user ) { + if ( $user->id == $prev_user->id ) { + unset( $users[ $key ] ); + break; + } + } + } + + $this->set_account_option( 'users', $users, true ); + } return true; } @@ -22076,26 +23002,23 @@ private function _handle_account_edits() { if ( $is_parent_plugin_action ) { if ( $is_network_action && ! empty( $blog_id ) ) { - if ( $this->is_registered() ) { - if ( $this->is_tracking_prohibited() ) { - if ( $this->allow_site_tracking() ) { + if ( $this->is_registered( true ) ) { + if ( $this->is_tracking_prohibited( $blog_id ) ) { + if ( $this->toggle_site_tracking( true, $blog_id ) ) { $this->_admin_notices->add( - sprintf( $this->get_text_inline( 'We appreciate your help in making the %s better by letting us track some usage data.', 'opt-out-message-appreciation' ), $this->_module_type ), + sprintf( $this->get_text_inline( 'Sharing diagnostic data with %s helps to provide functionality that\'s more relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the plugin should be translated and tailored to.', 'opt-out-message-appreciation' ), "{$this->get_plugin_title()}" ), $this->get_text_inline( 'Thank you!', 'thank-you' ) ); } } else { - if ( $this->stop_site_tracking() ) { + if ( $this->toggle_site_tracking( false, $blog_id ) ) { + $install = $this->get_install_by_blog_id( $blog_id ); + $this->_admin_notices->add( sprintf( - $this->get_text_inline( 'We will no longer be sending any usage data of %s on %s to %s.', 'opted-out-successfully' ), - $this->get_plugin_title(), - fs_strip_url_protocol( get_site_url( $blog_id ) ), - sprintf( - '%s', - 'https://freemius.com', - 'freemius.com' - ) + $this->get_text_inline( 'Diagnostic data will no longer be sent from %s to %s.', 'opted-out-successfully' ), + self::get_unfiltered_site_url( $blog_id, true ), + "{$this->get_plugin_title()}" ) ); } @@ -22243,18 +23166,32 @@ private function _handle_account_edits() { $state = fs_request_get( 'state', 'init' ); switch ( $state ) { case 'init': - $candidate_email = fs_request_get( 'candidate_email', '' ); + // The nonce is injected by the error handler in `_email_address_update_ajax_handler` function. + check_admin_referer( 'change_owner' ); - if ( $this->init_change_owner( $candidate_email ) ) { - $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Please check your mailbox, you should receive an email via %s to confirm the ownership change. From security reasons, you must confirm the change within the next 15 min. If you cannot find the email, please check your spam folder.', 'change-owner-request-sent-x' ), '' . $this->_user->email . '' ) ); + $candidate_email = fs_request_get( 'candidate_email' ); + $transfer_type = fs_request_get( 'transfer_type' ); + + if ( $this->init_change_owner( $candidate_email, $transfer_type ) ) { + if ( 'transfer' === $transfer_type ) { + $this->_admin_notices->add( sprintf( $this->get_text_inline( 'A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.', 'change-owner-request-sent-x-transfer' ), '' . $this->_user->email . '' ) ); + } else { + $this->_admin_notices->add( sprintf( $this->get_text_inline( 'A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder.', 'change-owner-request-sent-x' ), '' . $this->_user->email . '' ) ); + } } break; case 'owner_confirmed': + // We cannot (or need not to) check the nonce and referer here, because the link comes from the email sent by our API. $candidate_email = fs_request_get( 'candidate_email', '' ); + if ( ! is_email($candidate_email ) ) { + return; + } + $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.', 'change-owner-request_owner-confirmed' ), '' . $candidate_email . '' ) ); break; case 'candidate_confirmed': + // We do not need to validate the authenticity of this request here, because the `complete_change_owner` does that for us through API calls. if ( $this->complete_change_owner() ) { $this->_admin_notices->add_sticky( sprintf( $this->get_text_inline( '%s is the new owner of the account.', 'change-owner-request_candidate-confirmed' ), '' . $this->_user->email . '' ), @@ -22269,37 +23206,6 @@ private function _handle_account_edits() { return; - case 'update_email': - check_admin_referer( 'update_email' ); - - $new_email = fs_request_get( 'fs_email_' . $this->get_unique_affix(), '' ); - $result = $this->update_email( $new_email ); - - if ( isset( $result->error ) ) { - switch ( $result->error->code ) { - case 'user_exist': - $this->_admin_notices->add( - $this->get_text_inline( 'Sorry, we could not complete the email update. Another user with the same email is already registered.', 'user-exist-message' ) . ' ' . - sprintf( $this->get_text_inline( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.', 'user-exist-message_ownership' ), $this->_module_type, '' . $new_email . '' ) . - sprintf( - '', - $this->get_account_url( 'change_owner', array( - 'state' => 'init', - 'candidate_email' => $new_email - ) ), - $this->get_text_inline( 'Change Ownership', 'change-ownership' ) - ), - $oops_text, - 'error' - ); - break; - } - } else { - $this->_admin_notices->add( $this->get_text_inline( 'Your email was successfully updated. You should receive an email with confirmation instructions in few moments.', 'email-updated-message' ) ); - } - - return; - case 'update_user_name': check_admin_referer( 'update_user_name' ); @@ -22319,6 +23225,10 @@ private function _handle_account_edits() { #region Actions that might be called from external links (e.g. email) + /** + * !!IMPORTANT!!: We cannot check for a valid nonce in this region, because the links could be coming from emails. + */ + case 'cancel_trial': $result = $this->cancel_subscription_or_trial( $plugin_id ); if ( $this->is_api_error( $result ) ) { @@ -22383,6 +23293,18 @@ private function _handle_account_edits() { } } + /** + * Adds CSS classes for the body tag in the admin. + * + * @param string $classes Space-separated string of class names. + * + * @return string $classes FS Admin body tag class names. + */ + public function fs_addons_body_class( $classes ) { + $classes .= ' plugins-php'; + return $classes; + } + /** * Account page resources load. * @@ -22399,14 +23321,7 @@ function _account_page_load() { if ( $this->has_addons() ) { wp_enqueue_script( 'plugin-install' ); add_thickbox(); - - function fs_addons_body_class( $classes ) { - $classes .= ' plugins-php'; - - return $classes; - } - - add_filter( 'admin_body_class', 'fs_addons_body_class' ); + add_filter( 'admin_body_class', array( $this, 'fs_addons_body_class' ) ); } if ( $this->has_paid_plan() && @@ -22461,7 +23376,26 @@ function _affiliation_page_render() { fs_enqueue_local_style( 'fs_affiliation', '/admin/affiliation.css' ); - $vars = array( 'id' => $this->_module_id ); + $is_bundle_context = $this->has_bundle_context(); + + $plugin_title = $this->get_plugin_title(); + + if ( $is_bundle_context ) { + $plugin_title = $this->plugin_affiliate_terms->plugin_title; + + // Add the suffix "Bundle" only if the word is not present in the title itself. + if ( false === mb_stripos( $plugin_title, fs_text_inline( 'Bundle', 'bundle' ) ) ) { + $plugin_title = $this->apply_filters( + 'formatted_bundle_title', + $plugin_title . ' ' . fs_text_inline( 'Bundle', 'bundle' ) + ); + } + } + + $vars = array( + 'id' => $this->_module_id, + 'plugin_title' => $plugin_title, + ); echo $this->apply_filters( "/forms/affiliation.php", fs_get_template( '/forms/affiliation.php', $vars ) ); } @@ -22522,14 +23456,7 @@ function _addons_page_load() { wp_enqueue_script( 'plugin-install' ); add_thickbox(); - - function fs_addons_body_class( $classes ) { - $classes .= ' plugins-php'; - - return $classes; - } - - add_filter( 'admin_body_class', 'fs_addons_body_class' ); + add_filter( 'admin_body_class', array( $this, 'fs_addons_body_class' ) ); if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) { $this->_admin_notices->add( @@ -22562,7 +23489,7 @@ function _addons_page_render() { } /* Pricing & Upgrade - ------------------------------------------------------------------------------------------------------------------*/ + ------------------------------------------------------------------------------------------------------------------*/ /** * Render pricing page. * @@ -22606,11 +23533,11 @@ function _fs_pricing_ajax_action_handler() { $params = array( 'is_enriched' => true, 'trial' => fs_request_get_bool( 'trial' ), - 'sandbox' => fs_request_get( 'sandbox' ), - 's_ctx_type' => fs_request_get( 's_ctx_type' ), - 's_ctx_id' => fs_request_get( 's_ctx_id' ), - 's_ctx_ts' => fs_request_get( 's_ctx_ts' ), - 's_ctx_secure' => fs_request_get( 's_ctx_secure' ), + 'sandbox' => fs_request_get_raw( 'sandbox' ), + 's_ctx_type' => fs_request_get_raw( 's_ctx_type' ), + 's_ctx_id' => fs_request_get_raw( 's_ctx_id' ), + 's_ctx_ts' => fs_request_get_raw( 's_ctx_ts' ), + 's_ctx_secure' => fs_request_get_raw( 's_ctx_secure' ), ); $bundle_id = $this->get_bundle_id(); @@ -22704,7 +23631,17 @@ private static function _hide_admin_notices() { } static function _clean_admin_content_section_hook() { - self::_hide_admin_notices(); + $hide_admin_notices = true; + + if ( fs_request_is_action( 'allow_clone_resolution_notice' ) ) { + check_admin_referer( 'fs_allow_clone_resolution_notice' ); + + $hide_admin_notices = false; + } + + if ( $hide_admin_notices ) { + self::_hide_admin_notices(); + } // Hide footer. echo ''; @@ -22721,17 +23658,17 @@ static function _clean_admin_content_section() { } /* CSS & JavaScript - ------------------------------------------------------------------------------------------------------------------*/ + ------------------------------------------------------------------------------------------------------------------*/ /* function _enqueue_script($handle, $src) { - $url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src ); + $url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src ); - $this->_logger->entrance( 'script = ' . $url ); + $this->_logger->entrance( 'script = ' . $url ); - wp_enqueue_script( $handle, $url ); - }*/ + wp_enqueue_script( $handle, $url ); + }*/ /* SDK - ------------------------------------------------------------------------------------------------------------------*/ + ------------------------------------------------------------------------------------------------------------------*/ private $_user_api; /** @@ -22743,7 +23680,7 @@ static function _clean_admin_content_section() { * * @return FS_Api */ - private function get_api_user_scope( $flush = false ) { + function get_api_user_scope( $flush = false ) { if ( ! isset( $this->_user_api ) || $flush ) { $this->_user_api = $this->get_api_user_scope_by_user( $this->_user ); } @@ -22822,13 +23759,56 @@ private function get_api_site_scope( $flush = false ) { $this->_site->public_key, ! $this->is_live(), $this->_site->secret_key, - $this->get_sdk_version() + $this->get_sdk_version(), + self::get_unfiltered_site_url() ); } return $this->_site_api; } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @param string $path + * @param string $method + * @param array $params + * @param bool $flush_instance + * + * @return array|mixed|string|void + * @throws Freemius_Exception + */ + private function api_site_call( $path, $method = 'GET', $params = array(), $flush_instance = false ) { + $result = $this->get_api_site_scope( $flush_instance )->call( $path, $method, $params ); + + /** + * Checks if the local install's URL is different from the remote install's URL, update the local install if necessary, and then run the clone handler if the install's URL is different from the URL of the site. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + */ + if ( + $this->is_registered() && + FS_Api::is_api_result_entity( $result ) && + isset( $result->url ) + ) { + $stored_local_url = trailingslashit( $this->_site->url ); + $stored_remote_url = trailingslashit( $result->url ); + + if ( $stored_local_url !== $stored_remote_url ) { + $this->_site->url = $result->url; + $this->_store_site(); + } + + if ( fs_strip_url_protocol( $stored_remote_url ) !== self::get_unfiltered_site_url( null, true, true ) ) { + FS_Clone_Manager::instance()->maybe_run_clone_resolution(); + } + } + + return $result; + } + private $_plugin_api; /** @@ -23264,7 +24244,7 @@ function _add_fs_theme_activation_dialog() { } /* Action Links - ------------------------------------------------------------------------------------------------------------------*/ + ------------------------------------------------------------------------------------------------------------------*/ private $_action_links_hooked = false; private $_action_links = array(); @@ -23435,15 +24415,6 @@ function _add_tracking_links() { $this->_logger->entrance(); - /** - * @author Vova Feldman (@svovaf) - * @since 2.3.2 Allow opting out from usage-tracking for paid products too by giving the appropriate warning letting the user know the automatic updates mechanism cannot function without an ongoing connection to the licensing and updates engine. - */ - /*if ( $this->is_premium() ) { - // Don't add opt-in/out for premium code base. - return; - }*/ - if ( $this->is_only_premium() && $this->is_free_plan() ) { // Don't add tracking links for premium-only products that were opted-in by relation (add-on or a parent product) before activating any license. return; @@ -23451,10 +24422,13 @@ function _add_tracking_links() { if ( $this->is_addon() && - ! $this->is_only_premium() && - $this->_parent->is_anonymous() + ! $this->is_only_premium() ) { - return; + $parent = $this->get_parent_instance(); + + if ( is_object( $parent ) && $parent->is_anonymous() ) { + return; + } } if ( fs_is_network_admin() ) { @@ -23503,23 +24477,15 @@ function _add_tracking_links() { } } - if ( $this->add_ajax_action( 'stop_tracking', array( &$this, '_stop_tracking_callback' ) ) ) { - return; - } - - if ( $this->add_ajax_action( 'allow_tracking', array( &$this, '_allow_tracking_callback' ) ) ) { - return; - } - - if ( $this->add_ajax_action( 'update_tracking_permission', array( &$this, '_update_tracking_permission_callback' ) ) ) { + if ( $this->add_ajax_action( 'toggle_permission_tracking', array( &$this, '_toggle_permission_tracking_callback' ) ) ) { return; } $link_text_id = ''; $url = '#'; - if ( $this->is_registered() ) { - if ( $this->is_tracking_allowed() ) { + if ( $this->is_registered( true ) ) { + if ( $this->is_registered() && $this->is_tracking_allowed() ) { $link_text_id = $this->get_text_inline( 'Opt Out', 'opt-out' ); } else { $link_text_id = $this->get_text_inline( 'Opt In', 'opt-in' ); @@ -23716,9 +24682,12 @@ function add_sticky_admin_message( $message, $id, $title = '', $type = 'success' */ private function is_premium_version_installed() { $premium_plugin_basename = $this->premium_plugin_basename(); - $premium_plugin = get_plugins( '/' . dirname( $premium_plugin_basename ) ); - return ! empty( $premium_plugin ); + if ( $this->is_theme() ) { + return $this->can_activate_theme( $this->get_premium_slug() ); + } + + return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_plugin_basename ) ); } /** @@ -23754,7 +24723,9 @@ private function get_complete_upgrade_instructions( $plan_title = '' ) { * @author Leo Fajardo (@leorw) * @since 2.2.1 */ - $premium_plugin_basename = $this->premium_plugin_basename(); + $premium_theme_slug_or_plugin_basename = $this->is_theme() ? + $this->get_premium_slug() : + $this->premium_plugin_basename(); return sprintf( /* translators: %1$s: Product title; %2$s: Plan title */ @@ -23763,7 +24734,9 @@ private function get_complete_upgrade_instructions( $plan_title = '' ) { $plan_title, sprintf( '', - wp_nonce_url( 'plugins.php?action=activate&plugin=' . $premium_plugin_basename, 'activate-plugin_' . $premium_plugin_basename ), + ( $this->is_theme() ? + wp_nonce_url( 'themes.php?action=activate&stylesheet=' . $premium_theme_slug_or_plugin_basename, 'switch-theme_' . $premium_theme_slug_or_plugin_basename ) : + wp_nonce_url( 'plugins.php?action=activate&plugin=' . $premium_theme_slug_or_plugin_basename, 'activate-plugin_' . $premium_theme_slug_or_plugin_basename ) ), esc_html( sprintf( /* translators: %s: Plan title */ $this->get_text_inline( 'Activate %s features', 'activate-x-features' ), @@ -23788,12 +24761,48 @@ private function get_complete_upgrade_instructions( $plan_title = '' ) { ) ), $deactivation_step, $this->get_text_inline( 'Upload and activate the downloaded version', 'upload-and-activate' ), - $this->apply_filters( 'upload_and_install_video_url', '//bit.ly/upload-wp-' . $this->_module_type . 's' ), + $this->apply_filters( 'upload_and_install_video_url', '//bit.ly/wp-' . $this->_module_type . '-upload' ), $this->get_text_inline( 'How to upload and activate?', 'howto-upload-activate' ) ); } } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.3 + * + * @param string $message_before_the_instructions + * @param string $message_id + * @param string $plan_title + */ + private function add_complete_upgrade_instructions_notice( + $message_before_the_instructions, + $message_id, + $plan_title = '' + ) { + $this->_admin_notices->add_sticky( + $message_before_the_instructions . + $this->get_complete_upgrade_instructions( $plan_title ), + $message_id, + $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.3 + * + * @param bool $is_upgrade + */ + private function add_after_plan_activation_or_upgrade_instructions_notice( $is_upgrade = true ) { + $this->add_complete_upgrade_instructions_notice( + $is_upgrade ? + $this->get_text_inline( 'Your plan was successfully upgraded.', 'plan-upgraded-message' ) : + $this->get_text_inline( 'Your plan was successfully activated.', 'plan-activated-message' ), + 'plan_upgraded' + ); + } + /** * @author Leo Fajardo (@leorw) * @since 2.1.0 @@ -23847,25 +24856,21 @@ static function safe_remote_post( self::enrich_request_for_debug( $url, $request ); } - $response = wp_remote_post( $url, $request ); + if ( ! isset( $request['method'] ) ) { + $request['method'] = 'POST'; + } - if ( $response instanceof WP_Error ) { - if ( 'https://' === substr( $url, 0, 8 ) && - isset( $response->errors ) && - isset( $response->errors['http_request_failed'] ) - ) { - $http_error = strtolower( $response->errors['http_request_failed'][0] ); + $response = FS_Api::remote_request( $url, $request ); - if ( false !== strpos( $http_error, 'ssl' ) || - false !== strpos( $http_error, 'curl error 35' ) - ) { - // Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare). - $url = 'http://' . substr( $url, 8 ); + if ( + 'https://' === substr( $url, 0, 8 ) && + FS_Api::is_ssl_error_response( $response ) + ) { + // Failed due to old version of cURL or Open SSL (SSLv3 is not supported by CloudFlare). + $url = 'http://' . substr( $url, 8 ); - $request['timeout'] = 15; - $response = wp_remote_post( $url, $request ); - } - } + $request['timeout'] = 15; + $response = FS_Api::remote_request( $url, $request ); } if ( false !== $cache_key ) { @@ -24241,8 +25246,10 @@ function _add_auto_installation_dialog_box() { function _tabs_capture() { $this->_logger->entrance(); - if ( ! $this->is_product_settings_page() || - ! $this->is_matching_url( $this->main_menu_url() ) + if ( + ! $this->is_product_settings_page() || + ! $this->should_page_include_tabs() || + ! $this->is_matching_url( $this->main_menu_url() ) ) { return; } @@ -24296,8 +25303,10 @@ function _store_tabs_ajax_action() { function _store_tabs_styles() { $this->_logger->entrance(); - if ( ! $this->is_product_settings_page() || - ! $this->is_matching_url( $this->main_menu_url() ) + if ( + ! $this->is_product_settings_page() || + ! $this->should_page_include_tabs() || + ! $this->is_matching_url( $this->main_menu_url() ) ) { return; } @@ -24740,24 +25749,6 @@ function fetch_remote_icon_url() { #region GDPR #-------------------------------------------------------------------------------- - /** - * @author Leo Fajardo (@leorw) - * @since 2.1.0 - * - * @return bool - */ - function fetch_and_store_current_user_gdpr_anonymously() { - $pong = $this->ping( null, true ); - - if ( ! $this->get_api_plugin_scope()->is_valid_ping( $pong ) ) { - return false; - } else { - FS_GDPR_Manager::instance()->store_is_required( $pong->is_gdpr_required ); - - return $pong->is_gdpr_required; - } - } - /** * @author Leo Fajardo (@leorw) * @since 2.1.0 @@ -24876,7 +25867,7 @@ private function get_gdpr_admin_notice_string( $user_plugins ) { '%s %s %s', $thank_you, $already_opted_in, - sprintf( $this->get_text_inline( 'Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)', 'due-to-gdpr-compliance-requirements' ), '', '' ) . + sprintf( $this->get_text_inline( 'Due to the new %sEU General Data Protection Regulation (GDPR)%s compliance requirements it is required that you provide your explicit consent, again, confirming that you are onboard :-)', 'due-to-gdpr-compliance-requirements' ), '', '' ) . '

          ' . '' . $this->get_text_inline( "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:", 'contact-for-updates' ) . '' . $actions . @@ -25118,6 +26109,14 @@ private function disable_opt_in_notice_and_lock_user() { FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + */ + static function _add_api_connectivity_notice_handler_js() { + fs_require_once_template( 'api-connectivity-message-js.php' ); + } + /** * @author Leo Fajardo (@leorw) * @since 2.1.0 @@ -25157,7 +26156,7 @@ function _fetch_is_marketing_required_flag_value_ajax_action() { $this->check_ajax_referer( 'fetch_is_marketing_required_flag_value' ); - $license_key = fs_request_get( 'license_key' ); + $license_key = fs_request_get_raw( 'license_key' ); if ( empty($license_key) ) { self::shoot_ajax_failure( $this->get_text_inline( 'License key is empty.', 'empty-license-key' ) ); @@ -25211,7 +26210,7 @@ private function fetch_installs_licenses_owners_data( $install_ids ) { '/licenses_owners.json?install_ids=' . implode( ',', $install_ids ) ); - $license_owners = null; + $license_owners = array(); if ( $this->is_api_result_object( $response, 'owners' ) ) { $license_owners = $response->owners; diff --git a/freemius/includes/class-fs-admin-notices.php b/freemius/includes/class-fs-admin-notices.php index 75047fa..c3c36d3 100644 --- a/freemius/includes/class-fs-admin-notices.php +++ b/freemius/includes/class-fs-admin-notices.php @@ -128,13 +128,10 @@ public function add( $is_sticky = false, $id = '', $store_if_sticky = true, - $network_level_or_blog_id = null + $network_level_or_blog_id = null, + $is_dimissible = null ) { - if ($this->should_use_network_notices($id, $network_level_or_blog_id)) { - $notices = $this->_network_notices; - } else { - $notices = $this->get_site_notices($network_level_or_blog_id); - } + $notices = $this->get_site_or_network_notices($id, $network_level_or_blog_id); $notices->add( $message, @@ -142,7 +139,11 @@ public function add( $type, $is_sticky, $id, - $store_if_sticky + $store_if_sticky, + null, + null, + false, + $is_dimissible ); } @@ -152,8 +153,9 @@ public function add( * * @param string|string[] $ids * @param int|null $network_level_or_blog_id + * @param bool $store */ - public function remove_sticky($ids, $network_level_or_blog_id = null) + public function remove_sticky($ids, $network_level_or_blog_id = null, $store = true) { if (! is_array($ids)) { $ids = array( $ids ); @@ -165,7 +167,7 @@ public function remove_sticky($ids, $network_level_or_blog_id = null) $notices = $this->get_site_notices($network_level_or_blog_id); } - return $notices->remove_sticky($ids); + return $notices->remove_sticky($ids, $store); } /** @@ -181,11 +183,7 @@ public function remove_sticky($ids, $network_level_or_blog_id = null) */ public function has_sticky($id, $network_level_or_blog_id = null) { - if ($this->should_use_network_notices($id, $network_level_or_blog_id)) { - $notices = $this->_network_notices; - } else { - $notices = $this->get_site_notices($network_level_or_blog_id); - } + $notices = $this->get_site_or_network_notices($id, $network_level_or_blog_id); return $notices->has_sticky($id); } @@ -205,6 +203,7 @@ public function has_sticky($id, $network_level_or_blog_id = null) * @param string|null $plugin_title * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and * blog admin pages. + * @param bool $is_dismissible */ public function add_sticky( $message, @@ -214,15 +213,31 @@ public function add_sticky( $network_level_or_blog_id = null, $wp_user_id = null, $plugin_title = null, - $is_network_and_blog_admins = false + $is_network_and_blog_admins = false, + $is_dismissible = true, + $data = array() ) { - if ($this->should_use_network_notices($id, $network_level_or_blog_id)) { - $notices = $this->_network_notices; - } else { - $notices = $this->get_site_notices($network_level_or_blog_id); - } + $notices = $this->get_site_or_network_notices($id, $network_level_or_blog_id); + + $notices->add_sticky($message, $id, $title, $type, $wp_user_id, $plugin_title, $is_network_and_blog_admins, $is_dismissible, $data); + } + + /** + * Retrieves the data of a sticky notice. + * + * @author Leo Fajardo (@leorw) + * @since 2.4.3 + * + * @param string $id + * @param int|null $network_level_or_blog_id + * + * @return array|null + */ + public function get_sticky($id, $network_level_or_blog_id) + { + $notices = $this->get_site_or_network_notices($id, $network_level_or_blog_id); - $notices->add_sticky($message, $id, $title, $type, $wp_user_id, $plugin_title, $is_network_and_blog_admins); + return $notices->get_sticky($id); } /** @@ -232,8 +247,9 @@ public function add_sticky( * @since 2.0.0 * * @param int|null $network_level_or_blog_id + * @param bool $is_temporary */ - public function clear_all_sticky($network_level_or_blog_id = null) + public function clear_all_sticky($network_level_or_blog_id = null, $is_temporary = false) { if (! $this->_is_multisite || false === $network_level_or_blog_id || @@ -241,13 +257,13 @@ public function clear_all_sticky($network_level_or_blog_id = null) is_null($network_level_or_blog_id) ) { $notices = $this->get_site_notices($network_level_or_blog_id); - $notices->clear_all_sticky(); + $notices->clear_all_sticky($is_temporary); } if ($this->_is_multisite && (true === $network_level_or_blog_id || is_null($network_level_or_blog_id)) ) { - $this->_network_notices->clear_all_sticky(); + $this->_network_notices->clear_all_sticky($is_temporary); } } @@ -326,5 +342,23 @@ private function should_use_network_notices($id = '', $network_level_or_blog_id return fs_is_network_admin(); } + /** + * Retrieves an instance of FS_Admin_Notice_Manager. + * + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @param string $id + * @param int|null $network_level_or_blog_id + * + * @return FS_Admin_Notice_Manager + */ + private function get_site_or_network_notices($id, $network_level_or_blog_id) + { + return $this->should_use_network_notices($id, $network_level_or_blog_id) ? + $this->_network_notices : + $this->get_site_notices($network_level_or_blog_id); + } + #endregion } diff --git a/freemius/includes/class-fs-api.php b/freemius/includes/class-fs-api.php index 92fb5bc..a9ed115 100644 --- a/freemius/includes/class-fs-api.php +++ b/freemius/includes/class-fs-api.php @@ -65,6 +65,14 @@ class FS_Api */ private $_sdk_version; + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.0 + * + * @var string + */ + private $_url; + /** * @param string $slug * @param string $scope 'app', 'developer', 'user' or 'install'. @@ -73,6 +81,7 @@ class FS_Api * @param bool $is_sandbox * @param bool|string $secret_key Element's secret key. * @param null|string $sdk_version + * @param null|string $url * * @return FS_Api */ @@ -83,14 +92,15 @@ public static function instance( $public_key, $is_sandbox, $secret_key = false, - $sdk_version = null + $sdk_version = null, + $url = null ) { $identifier = md5($slug . $scope . $id . $public_key . (is_string($secret_key) ? $secret_key : '') . json_encode($is_sandbox)); if (! isset(self::$_instances[ $identifier ])) { self::_init(); - self::$_instances[ $identifier ] = new FS_Api($slug, $scope, $id, $public_key, $secret_key, $is_sandbox, $sdk_version); + self::$_instances[ $identifier ] = new FS_Api($slug, $scope, $id, $public_key, $secret_key, $is_sandbox, $sdk_version, $url); } return self::$_instances[ $identifier ]; @@ -125,6 +135,7 @@ private static function _init() * @param bool|string $secret_key Element's secret key. * @param bool $is_sandbox * @param null|string $sdk_version + * @param null|string $url */ private function __construct( $slug, @@ -133,12 +144,14 @@ private function __construct( $public_key, $secret_key, $is_sandbox, - $sdk_version + $sdk_version, + $url ) { $this->_api = new Freemius_Api_WordPress($scope, $id, $public_key, $secret_key, $is_sandbox); $this->_slug = $slug; $this->_sdk_version = $sdk_version; + $this->_url = $url; $this->_logger = FS_Logger::get_logger(WP_FS__SLUG . '_' . $slug . '_api', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK); } @@ -179,14 +192,16 @@ private function _sync_clock_diff($diff = false) * @param string $path * @param string $method * @param array $params - * @param bool $retry Is in retry or first call attempt. + * @param bool $in_retry Is in retry or first call attempt. * * @return array|mixed|string|void */ - private function _call($path, $method = 'GET', $params = array(), $retry = false) + private function _call($path, $method = 'GET', $params = array(), $in_retry = false) { $this->_logger->entrance($method . ':' . $path); + $force_http = (! $in_retry && self::$_options->get_option('api_force_http', false)); + if (self::is_temporary_down()) { $result = $this->get_temporary_unavailable_error(); } else { @@ -202,14 +217,28 @@ private function _call($path, $method = 'GET', $params = array(), $retry = false } } + /** + * @since 2.5.0 Include the site's URL, if available, in all API requests that are going through the API manager. + */ + if (! empty($this->_url)) { + if (false === strpos($path, 'url=') && + ! isset($params['url']) + ) { + $path = add_query_arg('url', $this->_url, $path); + } + } + $result = $this->_api->Api($path, $method, $params); - if (null !== $result && - isset($result->error) && - isset($result->error->code) && - 'request_expired' === $result->error->code + if ( + ! $in_retry && + null !== $result && + isset($result->error) && + isset($result->error->code) ) { - if (! $retry) { + $retry = false; + + if ('request_expired' === $result->error->code) { $diff = isset($result->error->timestamp) ? (time() - strtotime($result->error->timestamp)) : false; @@ -217,15 +246,35 @@ private function _call($path, $method = 'GET', $params = array(), $retry = false // Try to sync clock diff. if (false !== $this->_sync_clock_diff($diff)) { // Retry call with new synced clock. - return $this->_call($path, $method, $params, true); + $retry = true; } + } elseif ( + Freemius_Api_WordPress::IsHttps() && + FS_Api::is_ssl_error_response($result) + ) { + $force_http = true; + $retry = true; + } + + if ($retry) { + if ($force_http) { + $this->toggle_force_http(true); + } + + $result = $this->_call($path, $method, $params, true); } } } - if ($this->_logger->is_on() && self::is_api_error($result)) { - // Log API errors. - $this->_logger->api_error($result); + if (self::is_api_error($result)) { + if ($this->_logger->is_on()) { + // Log API errors. + $this->_logger->api_error($result); + } + + if ($force_http) { + $this->toggle_force_http(false); + } } return $result; @@ -320,6 +369,44 @@ public function get($path = '/', $flush = false, $expiration = WP_FS__TIME_24_HO return $cached_result; } + /** + * @todo Remove this method after migrating Freemius::safe_remote_post() to FS_Api::call(). + * + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param string $url + * @param array $remote_args + * + * @return array|WP_Error The response array or a WP_Error on failure. + */ + public static function remote_request($url, $remote_args) + { + if (! class_exists('Freemius_Api_WordPress')) { + require_once WP_FS__DIR_SDK . '/FreemiusWordPress.php'; + } + + if (method_exists('Freemius_Api_WordPress', 'RemoteRequest')) { + return Freemius_Api_WordPress::RemoteRequest($url, $remote_args); + } + + // The following is for backward compatibility when a modified PHP SDK version is in use and the `Freemius_Api_WordPress:RemoteRequest()` method doesn't exist. + $response = wp_remote_request($url, $remote_args); + + if ( + is_array($response) && + ( + empty($response['headers']) || + empty($response['headers']['x-api-server']) + ) + ) { + // API is considered blocked if the response doesn't include the `x-api-server` header. When there's no error but this header doesn't exist, the response is usually not in the expected form (e.g., cannot be JSON-decoded). + $response = new WP_Error('api_blocked', htmlentities($response['body'])); + } + + return $response; + } + /** * Check if there's a cached version of the API request. * @@ -395,49 +482,37 @@ private function get_cache_key($path, $method = 'GET', $params = array()) } /** - * Test API connectivity. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 If fails, try to fallback to HTTP. - * @since 1.1.6 Added a 5-min caching mechanism, to prevent from overloading the server if the API if - * temporary down. + * @author Leo Fajardo (@leorw) + * @since 2.5.4 * - * @return bool True if successful connectivity to the API. + * @param bool $is_http */ - public static function test() + private function toggle_force_http($is_http) { - self::_init(); - - $cache_key = 'ping_test'; + self::$_options->set_option('api_force_http', $is_http, true); - $test = self::$_cache->get_valid($cache_key, null); - - if (is_null($test)) { - $test = Freemius_Api_WordPress::Test(); - - if (false === $test && Freemius_Api_WordPress::IsHttps()) { - // Fallback to HTTP, since HTTPS fails. - Freemius_Api_WordPress::SetHttp(); - - self::$_options->set_option('api_force_http', true, true); - - $test = Freemius_Api_WordPress::Test(); - - if (false === $test) { - /** - * API connectivity test fail also in HTTP request, therefore, - * fallback to HTTPS to keep connection secure. - * - * @since 1.1.6 - */ - self::$_options->set_option('api_force_http', false, true); - } - } - - self::$_cache->set($cache_key, $test, WP_FS__TIME_5_MIN_IN_SEC); + if ($is_http) { + Freemius_Api_WordPress::SetHttp(); + } elseif (method_exists('Freemius_Api_WordPress', 'SetHttps')) { + Freemius_Api_WordPress::SetHttps(); } + } - return $test; + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param mixed $response + * + * @return bool + */ + public static function is_blocked($response) + { + return ( + self::is_api_error_object($response, true) && + isset($response->error->code) && + 'api_blocked' === $response->error->code + ); } /** @@ -475,57 +550,6 @@ private function get_temporary_unavailable_error() ); } - /** - * Ping API for connectivity test, and return result object. - * - * @author Vova Feldman (@svovaf) - * @since 1.0.9 - * - * @param null|string $unique_anonymous_id - * @param array $params - * - * @return object - */ - public function ping($unique_anonymous_id = null, $params = array()) - { - $this->_logger->entrance(); - - if (self::is_temporary_down()) { - return $this->get_temporary_unavailable_error(); - } - - $pong = is_null($unique_anonymous_id) ? - Freemius_Api_WordPress::Ping() : - $this->_call('ping.json?' . http_build_query(array_merge( - array( 'uid' => $unique_anonymous_id ), - $params - ))); - - if ($this->is_valid_ping($pong)) { - return $pong; - } - - if (self::should_try_with_http($pong)) { - // Fallback to HTTP, since HTTPS fails. - Freemius_Api_WordPress::SetHttp(); - - self::$_options->set_option('api_force_http', true, true); - - $pong = is_null($unique_anonymous_id) ? - Freemius_Api_WordPress::Ping() : - $this->_call('ping.json?' . http_build_query(array_merge( - array( 'uid' => $unique_anonymous_id ), - $params - ))); - - if (! $this->is_valid_ping($pong)) { - self::$_options->set_option('api_force_http', false, true); - } - } - - return $pong; - } - /** * Check if based on the API result we should try * to re-run the same request with HTTP instead of HTTPS. @@ -555,21 +579,6 @@ private static function should_try_with_http($result) ))); } - /** - * Check if valid ping request result. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.1 - * - * @param mixed $pong - * - * @return bool - */ - public function is_valid_ping($pong) - { - return Freemius_Api_WordPress::Test($pong); - } - public function get_url($path = '') { return Freemius_Api_WordPress::GetUrl($path, $this->_api->IsSandbox()); @@ -589,6 +598,15 @@ public static function clear_cache() self::$_cache->clear(); } + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + */ + public static function clear_force_http_flag() + { + self::$_options->unset_option('api_force_http'); + } + #---------------------------------------------------------------------------------- #region Error Handling #---------------------------------------------------------------------------------- @@ -612,15 +630,54 @@ public static function is_api_error($result) * @since 2.0.0 * * @param mixed $result + * @param bool $ignore_message * * @return bool Is API result contains an error. */ - public static function is_api_error_object($result) + public static function is_api_error_object($result, $ignore_message = false) { return ( is_object($result) && isset($result->error) && - isset($result->error->message) + ($ignore_message || isset($result->error->message)) + ); + } + + /** + * @author Leo Fajardo (@leorw) + * @since 2.5.4 + * + * @param WP_Error|object|string $response + * + * @return bool + */ + public static function is_ssl_error_response($response) + { + $http_error = null; + + if ($response instanceof WP_Error) { + if ( + isset($response->errors) && + isset($response->errors['http_request_failed']) + ) { + $http_error = strtolower($response->errors['http_request_failed'][0]); + } + } elseif ( + self::is_api_error_object($response) && + ! empty($response->error->message) + ) { + $http_error = $response->error->message; + } + + return ( + ! empty($http_error) && + ( + false !== strpos($http_error, 'curl error 35') || + ( + false === strpos($http_error, '') && + false !== strpos($http_error, 'ssl') + ) + ) ); } diff --git a/freemius/includes/class-fs-lock.php b/freemius/includes/class-fs-lock.php new file mode 100644 index 0000000..ea6b906 --- /dev/null +++ b/freemius/includes/class-fs-lock.php @@ -0,0 +1,117 @@ +_lock_id = $lock_id; + + if (! isset(self::$_thread_id)) { + self::$_thread_id = mt_rand(0, 32000); + } + } + + /** + * Try to acquire lock. If the lock is already set or is being acquired by another locker, don't do anything. + * + * @param int $expiration + * + * @return bool TRUE if successfully acquired lock. + */ + public function try_lock($expiration = 0) + { + if ($this->is_locked()) { + // Already locked. + return false; + } + + set_site_transient($this->_lock_id, self::$_thread_id, $expiration); + + if ($this->has_lock()) { + $this->lock($expiration); + + return true; + } + + return false; + } + + /** + * Acquire lock regardless if it's already acquired by another locker or not. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @param int $expiration + */ + public function lock($expiration = 0) + { + set_site_transient($this->_lock_id, true, $expiration); + } + + /** + * Checks if lock is currently acquired. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + * + * @return bool + */ + public function is_locked() + { + return (false !== get_site_transient($this->_lock_id)); + } + + /** + * Unlock the lock. + * + * @author Vova Feldman (@svovaf) + * @since 2.1.0 + */ + public function unlock() + { + delete_site_transient($this->_lock_id); + } + + /** + * Checks if lock is currently acquired by the current locker. + * + * @return bool + */ + protected function has_lock() + { + return (self::$_thread_id == get_site_transient($this->_lock_id)); + } + } diff --git a/freemius/includes/class-fs-logger.php b/freemius/includes/class-fs-logger.php index ad3cd04..6dde8aa 100644 --- a/freemius/includes/class-fs-logger.php +++ b/freemius/includes/class-fs-logger.php @@ -1,358 +1,335 @@ _id = $id; - - $bt = debug_backtrace(); - $caller = $bt[2]; - - if (false !== strpos($caller['file'], 'plugins')) { - $this->_file_start = strpos($caller['file'], 'plugins') + strlen('plugins/'); - } else { - $this->_file_start = strpos($caller['file'], 'themes') + strlen('themes/'); - } - - if ($on) { - $this->on(); - } - if ($echo) { - $this->echo_on(); - } - } - - /** - * @param string $id - * @param bool $on - * @param bool $echo - * - * @return FS_Logger - */ - public static function get_logger($id, $on = false, $echo = false) - { - $id = strtolower($id); - - if (! isset(self::$_processID)) { - self::init(); - } - - if (! isset(self::$LOGGERS[ $id ])) { - self::$LOGGERS[ $id ] = new FS_Logger($id, $on, $echo); - } - - return self::$LOGGERS[ $id ]; - } - - /** - * Initialize logging global info. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - */ - private static function init() - { - self::$_ownerName = function_exists('get_current_user') ? - get_current_user() : - 'unknown'; - self::$_isStorageLoggingOn = (1 == get_option('fs_storage_logger', 0)); - self::$_abspathLength = strlen(ABSPATH); - self::$_processID = mt_rand(0, 32000); - - // Process ID may be `false` on errors. - if (! is_numeric(self::$_processID)) { - self::$_processID = 0; - } - } - - private static function hook_footer() - { - if (self::$_HOOKED_FOOTER) { - return; - } - - if (is_admin()) { - add_action('admin_footer', 'FS_Logger::dump', 100); - } else { - add_action('wp_footer', 'FS_Logger::dump', 100); - } - } - - public function is_on() - { - return $this->_on; - } - - public function on() - { - $this->_on = true; - - if (! function_exists('dbDelta')) { - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; - } - - self::hook_footer(); - } - - public function echo_on() - { - $this->on(); - - $this->_echo = true; - } - - public function is_echo_on() - { - return $this->_echo; - } - - public function get_id() - { - return $this->_id; - } - - public function get_file() - { - return $this->_file_start; - } - - private function _log(&$message, $type, $wrapper = false) - { - if (! $this->is_on()) { - return; - } - - $bt = debug_backtrace(); - $depth = $wrapper ? 3 : 2; - while ($depth < count($bt) - 1 && 'eval' === $bt[ $depth ]['function']) { - $depth ++; - } - - $caller = $bt[ $depth ]; - - /** - * Retrieve the correct call file & line number from backtrace - * when logging from a wrapper method. - * - * @author Vova Feldman - * @since 1.2.1.6 - */ - if (empty($caller['line'])) { - $depth --; - - while ($depth >= 0) { - if (! empty($bt[ $depth ]['line'])) { - $caller['line'] = $bt[ $depth ]['line']; - $caller['file'] = $bt[ $depth ]['file']; - break; - } - } - } - - $log = array_merge($caller, array( - 'cnt' => self::$CNT ++, - 'logger' => $this, - 'timestamp' => microtime(true), - 'log_type' => $type, - 'msg' => $message, - )); - - if (self::$_isStorageLoggingOn) { - $this->db_log($type, $message, self::$CNT, $caller); - } - - self::$LOG[] = $log; - - if ($this->is_echo_on() && ! Freemius::is_ajax()) { - echo self::format_html($log) . "\n"; - } - } - - public function log($message, $wrapper = false) - { - $this->_log($message, 'log', $wrapper); - } - - public function info($message, $wrapper = false) - { - $this->_log($message, 'info', $wrapper); - } - - public function warn($message, $wrapper = false) - { - $this->_log($message, 'warn', $wrapper); - } - - public function error($message, $wrapper = false) - { - $this->_log($message, 'error', $wrapper); - } - - /** - * Log API error. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.5 - * - * @param mixed $api_result - * @param bool $wrapper - */ - public function api_error($api_result, $wrapper = false) - { - $message = ''; - if (is_object($api_result) && - ! empty($api_result->error) && - ! empty($api_result->error->message) - ) { - $message = $api_result->error->message; - } elseif (is_object($api_result)) { - $message = var_export($api_result, true); - } elseif (is_string($api_result)) { - $message = $api_result; - } elseif (empty($api_result)) { - $message = 'Empty API result.'; - } - - $message = 'API Error: ' . $message; - - $this->_log($message, 'error', $wrapper); - } - - public function entrance($message = '', $wrapper = false) - { - $msg = 'Entrance' . (empty($message) ? '' : ' > ') . $message; - - $this->_log($msg, 'log', $wrapper); - } - - public function departure($message = '', $wrapper = false) - { - $msg = 'Departure' . (empty($message) ? '' : ' > ') . $message; - - $this->_log($msg, 'log', $wrapper); - } - - #-------------------------------------------------------------------------------- - #region Log Formatting - #-------------------------------------------------------------------------------- - - private static function format($log, $show_type = true) - { - return '[' . str_pad($log['cnt'], strlen(self::$CNT), '0', STR_PAD_LEFT) . '] [' . $log['logger']->_id . '] ' . ($show_type ? '[' . $log['log_type'] . ']' : '') . (! empty($log['class']) ? $log['class'] . $log['type'] : '') . $log['function'] . ' >> ' . $log['msg'] . (isset($log['file']) ? ' (' . substr($log['file'], $log['logger']->_file_start) . ' ' . $log['line'] . ') ' : '') . ' [' . $log['timestamp'] . ']'; - } - - private static function format_html($log) - { - return '
          [' . $log['cnt'] . '] [' . $log['logger']->_id . '] [' . $log['log_type'] . '] ' . (! empty($log['class']) ? $log['class'] . $log['type'] : '') . $log['function'] . ' >> ' . esc_html($log['msg']) . '' . (isset($log['file']) ? ' (' . substr($log['file'], $log['logger']->_file_start) . ' ' . $log['line'] . ')' : '') . ' [' . $log['timestamp'] . ']
          '; - } - - #endregion - - public static function dump() - { - ?> + /** + * @package Freemius + * @copyright Copyright (c) 2015, Freemius, Inc. + * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3 + * @since 1.0.3 + */ + + if ( ! defined( 'ABSPATH' ) ) { + exit; + } + + class FS_Logger { + private $_id; + private $_on = false; + private $_echo = false; + private $_file_start = 0; + /** + * @var int PHP Process ID. + */ + private static $_processID; + /** + * @var string PHP Script user name. + */ + private static $_ownerName; + /** + * @var bool Is storage logging turned on. + */ + private static $_isStorageLoggingOn; + /** + * @var int ABSPATH length. + */ + private static $_abspathLength; + + private static $LOGGERS = array(); + private static $LOG = array(); + private static $CNT = 0; + private static $_HOOKED_FOOTER = false; + + private function __construct( $id, $on = false, $echo = false ) { + $bt = debug_backtrace(); + + $this->_id = $id; + + $caller = $bt[2]; + + if ( false !== strpos( $caller['file'], 'plugins' ) ) { + $this->_file_start = strpos( $caller['file'], 'plugins' ) + strlen( 'plugins/' ); + } else { + $this->_file_start = strpos( $caller['file'], 'themes' ) + strlen( 'themes/' ); + } + + if ( $on ) { + $this->on(); + } + if ( $echo ) { + $this->echo_on(); + } + } + + /** + * @param string $id + * @param bool $on + * @param bool $echo + * + * @return FS_Logger + */ + public static function get_logger( $id, $on = false, $echo = false ) { + $id = strtolower( $id ); + + if ( ! isset( self::$_processID ) ) { + self::init(); + } + + if ( ! isset( self::$LOGGERS[ $id ] ) ) { + self::$LOGGERS[ $id ] = new FS_Logger( $id, $on, $echo ); + } + + return self::$LOGGERS[ $id ]; + } + + /** + * Initialize logging global info. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + */ + private static function init() { + self::$_ownerName = function_exists( 'get_current_user' ) ? + get_current_user() : + 'unknown'; + self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) ); + self::$_abspathLength = strlen( ABSPATH ); + self::$_processID = mt_rand( 0, 32000 ); + + // Process ID may be `false` on errors. + if ( ! is_numeric( self::$_processID ) ) { + self::$_processID = 0; + } + } + + private static function hook_footer() { + if ( self::$_HOOKED_FOOTER ) { + return; + } + + if ( is_admin() ) { + add_action( 'admin_footer', 'FS_Logger::dump', 100 ); + } else { + add_action( 'wp_footer', 'FS_Logger::dump', 100 ); + } + } + + function is_on() { + return $this->_on; + } + + function on() { + $this->_on = true; + + if ( ! function_exists( 'dbDelta' ) ) { + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + } + + self::hook_footer(); + } + + function echo_on() { + $this->on(); + + $this->_echo = true; + } + + function is_echo_on() { + return $this->_echo; + } + + function get_id() { + return $this->_id; + } + + function get_file() { + return $this->_file_start; + } + + private function _log( &$message, $type, $wrapper = false ) { + if ( ! $this->is_on() ) { + return; + } + + $bt = debug_backtrace(); + $depth = $wrapper ? 3 : 2; + while ( $depth < count( $bt ) - 1 && 'eval' === $bt[ $depth ]['function'] ) { + $depth ++; + } + + $caller = $bt[ $depth ]; + + /** + * Retrieve the correct call file & line number from backtrace + * when logging from a wrapper method. + * + * @author Vova Feldman + * @since 1.2.1.6 + */ + if ( empty( $caller['line'] ) ) { + $depth --; + + while ( $depth >= 0 ) { + if ( ! empty( $bt[ $depth ]['line'] ) ) { + $caller['line'] = $bt[ $depth ]['line']; + $caller['file'] = $bt[ $depth ]['file']; + break; + } + } + } + + $log = array_merge( $caller, array( + 'cnt' => self::$CNT ++, + 'logger' => $this, + 'timestamp' => microtime( true ), + 'log_type' => $type, + 'msg' => $message, + ) ); + + if ( self::$_isStorageLoggingOn ) { + $this->db_log( $type, $message, self::$CNT, $caller ); + } + + self::$LOG[] = $log; + + if ( $this->is_echo_on() && ! Freemius::is_ajax() ) { + echo self::format_html( $log ) . "\n"; + } + } + + function log( $message, $wrapper = false ) { + $this->_log( $message, 'log', $wrapper ); + } + + function info( $message, $wrapper = false ) { + $this->_log( $message, 'info', $wrapper ); + } + + function warn( $message, $wrapper = false ) { + $this->_log( $message, 'warn', $wrapper ); + } + + function error( $message, $wrapper = false ) { + $this->_log( $message, 'error', $wrapper ); + } + + /** + * Log API error. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.5 + * + * @param mixed $api_result + * @param bool $wrapper + */ + function api_error( $api_result, $wrapper = false ) { + $message = ''; + if ( is_object( $api_result ) && + ! empty( $api_result->error ) && + ! empty( $api_result->error->message ) + ) { + $message = $api_result->error->message; + } else if ( is_object( $api_result ) ) { + $message = var_export( $api_result, true ); + } else if ( is_string( $api_result ) ) { + $message = $api_result; + } else if ( empty( $api_result ) ) { + $message = 'Empty API result.'; + } + + $message = 'API Error: ' . $message; + + $this->_log( $message, 'error', $wrapper ); + } + + function entrance( $message = '', $wrapper = false ) { + $msg = 'Entrance' . ( empty( $message ) ? '' : ' > ' ) . $message; + + $this->_log( $msg, 'log', $wrapper ); + } + + function departure( $message = '', $wrapper = false ) { + $msg = 'Departure' . ( empty( $message ) ? '' : ' > ' ) . $message; + + $this->_log( $msg, 'log', $wrapper ); + } + + #-------------------------------------------------------------------------------- + #region Log Formatting + #-------------------------------------------------------------------------------- + + private static function format( $log, $show_type = true ) { + return '[' . str_pad( $log['cnt'], strlen( self::$CNT ), '0', STR_PAD_LEFT ) . '] [' . $log['logger']->_id . '] ' . ( $show_type ? '[' . $log['log_type'] . ']' : '' ) . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . $log['msg'] . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ') ' : '' ) . ' [' . $log['timestamp'] . ']'; + } + + private static function format_html( $log ) { + return '
          [' . $log['cnt'] . '] [' . $log['logger']->_id . '] [' . $log['log_type'] . '] ' . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . esc_html( $log['msg'] ) . '' . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ')' : '' ) . ' [' . $log['timestamp'] . ']
          '; + } + + #endregion + + static function dump() { + ?> prefix}fs_logger"; - - if ($is_on) { - /** - * Create logging table. - * - * NOTE: - * dbDelta must use KEY and not INDEX for indexes. - * - * @link https://core.trac.wordpress.org/ticket/2695 - */ - $result = $wpdb->query("CREATE TABLE {$table} ( + } + + static function get_log() { + return self::$LOG; + } + + #-------------------------------------------------------------------------------- + #region Database Logging + #-------------------------------------------------------------------------------- + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @return bool + */ + public static function is_storage_logging_on() { + if ( ! isset( self::$_isStorageLoggingOn ) ) { + self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) ); + } + + return self::$_isStorageLoggingOn; + } + + /** + * Turns on/off database persistent debugging to capture + * multi-session logs to debug complex flows like + * plugin auto-deactivate on premium version activation. + * + * @todo Check if Theme Check has issues with DB tables for themes. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $is_on + * + * @return bool + */ + public static function _set_storage_logging( $is_on = true ) { + global $wpdb; + + $table = "{$wpdb->prefix}fs_logger"; + + if ( $is_on ) { + /** + * Create logging table. + * + * NOTE: + * dbDelta must use KEY and not INDEX for indexes. + * + * @link https://core.trac.wordpress.org/ticket/2695 + */ + $result = $wpdb->query( "CREATE TABLE {$table} ( `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, `process_id` INT UNSIGNED NOT NULL, `user_name` VARCHAR(64) NOT NULL, @@ -370,347 +347,346 @@ public static function _set_storage_logging($is_on = true) KEY `process_id` (`process_id` ASC), KEY `process_logger` (`process_id` ASC, `logger` ASC), KEY `function` (`function` ASC), -KEY `type` (`type` ASC))"); - } else { - /** - * Drop logging table. - */ - $result = $wpdb->query("DROP TABLE IF EXISTS $table;"); - } - - if (false !== $result) { - update_option('fs_storage_logger', ($is_on ? 1 : 0)); - } - - return (false !== $result); - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param string $type - * @param string $message - * @param int $log_order - * @param array $caller - * - * @return false|int - */ - private function db_log( - &$type, - &$message, - &$log_order, - &$caller - ) { - global $wpdb; - - $request_type = 'call'; - if (defined('DOING_CRON') && DOING_CRON) { - $request_type = 'cron'; - } elseif (defined('DOING_AJAX') && DOING_AJAX) { - $request_type = 'ajax'; - } - - $request_url = WP_FS__IS_HTTP_REQUEST ? - $_SERVER['REQUEST_URI'] : - ''; - - return $wpdb->insert( - "{$wpdb->prefix}fs_logger", - array( - 'process_id' => self::$_processID, - 'user_name' => self::$_ownerName, - 'logger' => $this->_id, - 'log_order' => $log_order, - 'type' => $type, - 'request_type' => $request_type, - 'request_url' => $request_url, - 'message' => $message, - 'file' => isset($caller['file']) ? - substr($caller['file'], self::$_abspathLength) : - '', - 'line' => $caller['line'], - 'function' => (! empty($caller['class']) ? $caller['class'] . $caller['type'] : '') . $caller['function'], - 'created' => microtime(true), - ) - ); - } - - /** - * Persistent DB logger columns. - * - * @var array - */ - private static $_log_columns = array( - 'id', - 'process_id', - 'user_name', - 'logger', - 'log_order', - 'type', - 'message', - 'file', - 'line', - 'function', - 'request_type', - 'request_url', - 'created', - ); - - /** - * Create DB logs query. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param bool $filters - * @param int $limit - * @param int $offset - * @param bool $order - * @param bool $escape_eol - * - * @return string - */ - private static function build_db_logs_query( - $filters = false, - $limit = 200, - $offset = 0, - $order = false, - $escape_eol = false - ) { - global $wpdb; - - $select = '*'; - - if ($escape_eol) { - $select = ''; - for ($i = 0, $len = count(self::$_log_columns); $i < $len; $i ++) { - if ($i > 0) { - $select .= ', '; - } - - if ('message' !== self::$_log_columns[ $i ]) { - $select .= self::$_log_columns[ $i ]; - } else { - $select .= 'REPLACE(message , \'\n\', \' \') AS message'; - } - } - } - - $query = "SELECT {$select} FROM {$wpdb->prefix}fs_logger"; - if (is_array($filters)) { - $criteria = array(); - - if (! empty($filters['type']) && 'all' !== $filters['type']) { - $filters['type'] = strtolower($filters['type']); - - switch ($filters['type']) { - case 'warn_error': - $criteria[] = array( 'col' => 'type', 'val' => array( 'warn', 'error' ) ); - break; - case 'error': - case 'warn': - $criteria[] = array( 'col' => 'type', 'val' => $filters['type'] ); - break; - case 'info': - default: - $criteria[] = array( 'col' => 'type', 'val' => array( 'info', 'log' ) ); - break; - } - } - - if (! empty($filters['request_type'])) { - $filters['request_type'] = strtolower($filters['request_type']); - - if (in_array($filters['request_type'], array( 'call', 'ajax', 'cron' ))) { - $criteria[] = array( 'col' => 'request_type', 'val' => $filters['request_type'] ); - } - } - - if (! empty($filters['file'])) { - $criteria[] = array( - 'col' => 'file', - 'op' => 'LIKE', - 'val' => '%' . esc_sql($filters['file']), - ); - } - - if (! empty($filters['function'])) { - $criteria[] = array( - 'col' => 'function', - 'op' => 'LIKE', - 'val' => '%' . esc_sql($filters['function']), - ); - } - - if (! empty($filters['process_id']) && is_numeric($filters['process_id'])) { - $criteria[] = array( 'col' => 'process_id', 'val' => $filters['process_id'] ); - } - - if (! empty($filters['logger'])) { - $criteria[] = array( - 'col' => 'logger', - 'op' => 'LIKE', - 'val' => '%' . esc_sql($filters['logger']) . '%', - ); - } - - if (! empty($filters['message'])) { - $criteria[] = array( - 'col' => 'message', - 'op' => 'LIKE', - 'val' => '%' . esc_sql($filters['message']) . '%', - ); - } - - if (0 < count($criteria)) { - $query .= "\nWHERE\n"; - - $first = true; - foreach ($criteria as $c) { - if (! $first) { - $query .= "AND\n"; - } - - if (is_array($c['val'])) { - $operator = 'IN'; - - for ($i = 0, $len = count($c['val']); $i < $len; $i ++) { - $c['val'][ $i ] = "'" . esc_sql($c['val'][ $i ]) . "'"; - } - - $val = '(' . implode(',', $c['val']) . ')'; - } else { - $operator = ! empty($c['op']) ? $c['op'] : '='; - $val = "'" . esc_sql($c['val']) . "'"; - } - - $query .= "`{$c['col']}` {$operator} {$val}\n"; - - $first = false; - } - } - } - - if (! is_array($order)) { - $order = array( - 'col' => 'id', - 'order' => 'desc' - ); - } - - $query .= " ORDER BY {$order['col']} {$order['order']} LIMIT {$offset},{$limit}"; - - return $query; - } - - /** - * Load logs from DB. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param bool $filters - * @param int $limit - * @param int $offset - * @param bool $order - * - * @return object[]|null - */ - public static function load_db_logs( - $filters = false, - $limit = 200, - $offset = 0, - $order = false - ) { - global $wpdb; - - $query = self::build_db_logs_query( - $filters, - $limit, - $offset, - $order - ); - - return $wpdb->get_results($query); - } - - /** - * Load logs from DB. - * - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param bool $filters - * @param string $filename - * @param int $limit - * @param int $offset - * @param bool $order - * - * @return false|string File download URL or false on failure. - */ - public static function download_db_logs( - $filters = false, - $filename = '', - $limit = 10000, - $offset = 0, - $order = false - ) { - global $wpdb; - - $query = self::build_db_logs_query( - $filters, - $limit, - $offset, - $order, - true - ); - - $upload_dir = wp_upload_dir(); - if (empty($filename)) { - $filename = 'fs-logs-' . date('Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME) . '.csv'; - } - $filepath = rtrim($upload_dir['path'], '/') . "/{$filename}"; - - $query .= " INTO OUTFILE '{$filepath}' FIELDS TERMINATED BY '\t' ESCAPED BY '\\\\' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\\n'"; - - $columns = ''; - for ($i = 0, $len = count(self::$_log_columns); $i < $len; $i ++) { - if ($i > 0) { - $columns .= ', '; - } - - $columns .= "'" . self::$_log_columns[ $i ] . "'"; - } - - $query = "SELECT {$columns} UNION ALL " . $query; - - $result = $wpdb->query($query); - - if (false === $result) { - return false; - } - - return rtrim($upload_dir['url'], '/') . '/' . $filename; - } - - /** - * @author Vova Feldman (@svovaf) - * @since 1.2.1.6 - * - * @param string $filename - * - * @return string - */ - public static function get_logs_download_url($filename = '') - { - $upload_dir = wp_upload_dir(); - if (empty($filename)) { - $filename = 'fs-logs-' . date('Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME) . '.csv'; - } - - return rtrim($upload_dir['url'], '/') . $filename; - } - - #endregion - } +KEY `type` (`type` ASC))" ); + } else { + /** + * Drop logging table. + */ + $result = $wpdb->query( "DROP TABLE IF EXISTS $table;" ); + } + + if ( false !== $result ) { + update_option( 'fs_storage_logger', ( $is_on ? 1 : 0 ) ); + } + + return ( false !== $result ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param string $type + * @param string $message + * @param int $log_order + * @param array $caller + * + * @return false|int + */ + private function db_log( + &$type, + &$message, + &$log_order, + &$caller + ) { + global $wpdb; + + $request_type = 'call'; + if ( defined( 'DOING_CRON' ) && DOING_CRON ) { + $request_type = 'cron'; + } else if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + $request_type = 'ajax'; + } + + $request_url = WP_FS__IS_HTTP_REQUEST ? + $_SERVER['REQUEST_URI'] : + ''; + + return $wpdb->insert( + "{$wpdb->prefix}fs_logger", + array( + 'process_id' => self::$_processID, + 'user_name' => self::$_ownerName, + 'logger' => $this->_id, + 'log_order' => $log_order, + 'type' => $type, + 'request_type' => $request_type, + 'request_url' => $request_url, + 'message' => $message, + 'file' => isset( $caller['file'] ) ? + substr( $caller['file'], self::$_abspathLength ) : + '', + 'line' => $caller['line'], + 'function' => ( ! empty( $caller['class'] ) ? $caller['class'] . $caller['type'] : '' ) . $caller['function'], + 'created' => microtime( true ), + ) + ); + } + + /** + * Persistent DB logger columns. + * + * @var array + */ + private static $_log_columns = array( + 'id', + 'process_id', + 'user_name', + 'logger', + 'log_order', + 'type', + 'message', + 'file', + 'line', + 'function', + 'request_type', + 'request_url', + 'created', + ); + + /** + * Create DB logs query. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $filters + * @param int $limit + * @param int $offset + * @param bool $order + * @param bool $escape_eol + * + * @return string + */ + private static function build_db_logs_query( + $filters = false, + $limit = 200, + $offset = 0, + $order = false, + $escape_eol = false + ) { + global $wpdb; + + $select = '*'; + + if ( $escape_eol ) { + $select = ''; + for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) { + if ( $i > 0 ) { + $select .= ', '; + } + + if ( 'message' !== self::$_log_columns[ $i ] ) { + $select .= self::$_log_columns[ $i ]; + } else { + $select .= 'REPLACE(message , \'\n\', \' \') AS message'; + } + } + } + + $query = "SELECT {$select} FROM {$wpdb->prefix}fs_logger"; + if ( is_array( $filters ) ) { + $criteria = array(); + + if ( ! empty( $filters['type'] ) && 'all' !== $filters['type'] ) { + $filters['type'] = strtolower( $filters['type'] ); + + switch ( $filters['type'] ) { + case 'warn_error': + $criteria[] = array( 'col' => 'type', 'val' => array( 'warn', 'error' ) ); + break; + case 'error': + case 'warn': + $criteria[] = array( 'col' => 'type', 'val' => $filters['type'] ); + break; + case 'info': + default: + $criteria[] = array( 'col' => 'type', 'val' => array( 'info', 'log' ) ); + break; + } + } + + if ( ! empty( $filters['request_type'] ) ) { + $filters['request_type'] = strtolower( $filters['request_type'] ); + + if ( in_array( $filters['request_type'], array( 'call', 'ajax', 'cron' ) ) ) { + $criteria[] = array( 'col' => 'request_type', 'val' => $filters['request_type'] ); + } + } + + if ( ! empty( $filters['file'] ) ) { + $criteria[] = array( + 'col' => 'file', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['file'] ), + ); + } + + if ( ! empty( $filters['function'] ) ) { + $criteria[] = array( + 'col' => 'function', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['function'] ), + ); + } + + if ( ! empty( $filters['process_id'] ) && is_numeric( $filters['process_id'] ) ) { + $criteria[] = array( 'col' => 'process_id', 'val' => $filters['process_id'] ); + } + + if ( ! empty( $filters['logger'] ) ) { + $criteria[] = array( + 'col' => 'logger', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['logger'] ) . '%', + ); + } + + if ( ! empty( $filters['message'] ) ) { + $criteria[] = array( + 'col' => 'message', + 'op' => 'LIKE', + 'val' => '%' . esc_sql( $filters['message'] ) . '%', + ); + } + + if ( 0 < count( $criteria ) ) { + $query .= "\nWHERE\n"; + + $first = true; + foreach ( $criteria as $c ) { + if ( ! $first ) { + $query .= "AND\n"; + } + + if ( is_array( $c['val'] ) ) { + $operator = 'IN'; + + for ( $i = 0, $len = count( $c['val'] ); $i < $len; $i ++ ) { + $c['val'][ $i ] = "'" . esc_sql( $c['val'][ $i ] ) . "'"; + } + + $val = '(' . implode( ',', $c['val'] ) . ')'; + } else { + $operator = ! empty( $c['op'] ) ? $c['op'] : '='; + $val = "'" . esc_sql( $c['val'] ) . "'"; + } + + $query .= "`{$c['col']}` {$operator} {$val}\n"; + + $first = false; + } + } + } + + if ( ! is_array( $order ) ) { + $order = array( + 'col' => 'id', + 'order' => 'desc' + ); + } + + $query .= " ORDER BY {$order['col']} {$order['order']} LIMIT {$offset},{$limit}"; + + return $query; + } + + /** + * Load logs from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $filters + * @param int $limit + * @param int $offset + * @param bool $order + * + * @return object[]|null + */ + public static function load_db_logs( + $filters = false, + $limit = 200, + $offset = 0, + $order = false + ) { + global $wpdb; + + $query = self::build_db_logs_query( + $filters, + $limit, + $offset, + $order + ); + + return $wpdb->get_results( $query ); + } + + /** + * Load logs from DB. + * + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param bool $filters + * @param string $filename + * @param int $limit + * @param int $offset + * @param bool $order + * + * @return false|string File download URL or false on failure. + */ + public static function download_db_logs( + $filters = false, + $filename = '', + $limit = 10000, + $offset = 0, + $order = false + ) { + global $wpdb; + + $query = self::build_db_logs_query( + $filters, + $limit, + $offset, + $order, + true + ); + + $upload_dir = wp_upload_dir(); + if ( empty( $filename ) ) { + $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv'; + } + $filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}"; + + $query .= " INTO OUTFILE '{$filepath}' FIELDS TERMINATED BY '\t' ESCAPED BY '\\\\' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\\n'"; + + $columns = ''; + for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) { + if ( $i > 0 ) { + $columns .= ', '; + } + + $columns .= "'" . self::$_log_columns[ $i ] . "'"; + } + + $query = "SELECT {$columns} UNION ALL " . $query; + + $result = $wpdb->query( $query ); + + if ( false === $result ) { + return false; + } + + return rtrim( $upload_dir['url'], '/' ) . '/' . $filename; + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.2.1.6 + * + * @param string $filename + * + * @return string + */ + public static function get_logs_download_url( $filename = '' ) { + $upload_dir = wp_upload_dir(); + if ( empty( $filename ) ) { + $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv'; + } + + return rtrim( $upload_dir['url'], '/' ) . $filename; + } + + #endregion + } diff --git a/freemius/includes/class-fs-plugin-updater.php b/freemius/includes/class-fs-plugin-updater.php index c44f518..c24965f 100644 --- a/freemius/includes/class-fs-plugin-updater.php +++ b/freemius/includes/class-fs-plugin-updater.php @@ -14,7 +14,6 @@ class FS_Plugin_Updater { - /** * @var Freemius * @since 1.0.4 @@ -139,7 +138,7 @@ public function catch_plugin_information_dialog_contents() { if ( 'plugin-information' !== fs_request_get('tab', false) || - $this->_fs->get_slug() !== fs_request_get('plugin', false) + $this->_fs->get_slug() !== fs_request_get_raw('plugin', false) ) { return; } @@ -159,7 +158,7 @@ public function edit_and_echo_plugin_information_dialog_contents($hook_suffix) { if ( 'plugin-information' !== fs_request_get('tab', false) || - $this->_fs->get_slug() !== fs_request_get('plugin', false) + $this->_fs->get_slug() !== fs_request_get_raw('plugin', false) ) { return; } @@ -172,15 +171,19 @@ public function edit_and_echo_plugin_information_dialog_contents($hook_suffix) $contents = ob_get_clean(); - $update_button_id_attribute_pos = strpos($contents, 'id="plugin_update_from_iframe"'); + $install_or_update_button_id_attribute_pos = strpos($contents, 'id="plugin_install_from_iframe"'); + + if (false === $install_or_update_button_id_attribute_pos) { + $install_or_update_button_id_attribute_pos = strpos($contents, 'id="plugin_update_from_iframe"'); + } - if (false !== $update_button_id_attribute_pos) { - $update_button_start_pos = strrpos( - substr($contents, 0, $update_button_id_attribute_pos), + if (false !== $install_or_update_button_id_attribute_pos) { + $install_or_update_button_start_pos = strrpos( + substr($contents, 0, $install_or_update_button_id_attribute_pos), '', $update_button_id_attribute_pos) + strlen('')); + $install_or_update_button_end_pos = (strpos($contents, '', $install_or_update_button_id_attribute_pos) + strlen('')); /** * The part of the contents without the update button. @@ -188,20 +191,20 @@ public function edit_and_echo_plugin_information_dialog_contents($hook_suffix) * @author Leo Fajardo (@leorw) * @since 2.2.5 */ - $modified_contents = substr($contents, 0, $update_button_start_pos); + $modified_contents = substr($contents, 0, $install_or_update_button_start_pos); - $update_button = substr($contents, $update_button_start_pos, ($update_button_end_pos - $update_button_start_pos)); + $install_or_update_button = substr($contents, $install_or_update_button_start_pos, ($install_or_update_button_end_pos - $install_or_update_button_start_pos)); /** * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license, * the text will be "Renew license" and will link to the checkout page with the license's billing cycle * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page. */ - $update_button = preg_replace( - '/(\)(.+)(\<\/a>)/is', + $install_or_update_button = preg_replace( + '/(\)(.+)(\<\/a>)/is', is_object($license) ? sprintf( - '$1$3%s$5%s$7', + '$1$4%s$6%s$8', $this->_fs->checkout_url( is_object($subscription) ? (1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY) : @@ -212,11 +215,11 @@ public function edit_and_echo_plugin_information_dialog_contents($hook_suffix) fs_text_inline('Renew license', 'renew-license', $this->_fs->get_slug()) ) : sprintf( - '$1$3%s$5%s$7', + '$1$4%s$6%s$8', $this->_fs->pricing_url(), fs_text_inline('Buy license', 'buy-license', $this->_fs->get_slug()) ), - $update_button + $install_or_update_button ); /** @@ -225,7 +228,7 @@ public function edit_and_echo_plugin_information_dialog_contents($hook_suffix) * @author Leo Fajardo (@leorw) * @since 2.2.5 */ - $modified_contents .= $update_button; + $modified_contents .= $install_or_update_button; /** * Append the remaining part of the contents after the update button. @@ -233,7 +236,7 @@ public function edit_and_echo_plugin_information_dialog_contents($hook_suffix) * @author Leo Fajardo (@leorw) * @since 2.2.5 */ - $modified_contents .= substr($contents, $update_button_end_pos); + $modified_contents .= substr($contents, $install_or_update_button_end_pos); $contents = $modified_contents; } @@ -247,7 +250,11 @@ public function edit_and_echo_plugin_information_dialog_contents($hook_suffix) */ private function add_transient_filters() { - if ($this->_fs->is_premium() && ! $this->_fs->is_tracking_allowed()) { + if ( + $this->_fs->is_premium() && + $this->_fs->is_registered() && + ! FS_Permission_Manager::instance($this->_fs)->is_essentials_tracking_allowed() + ) { $this->_logger->log('Opted out sites cannot receive automatic software updates.'); return; @@ -424,7 +431,7 @@ public function change_theme_update_info_html($prepared_themes) $themes_update = get_site_transient('update_themes'); if (! isset($themes_update->response[ $theme_basename ]) || - empty($themes_update->response[ $theme_basename ]['package']) + empty($themes_update->response[ $theme_basename ]['package']) ) { return $prepared_themes; } @@ -623,11 +630,9 @@ public function pre_set_site_transient_update_plugins_filter($transient_data) if (! isset($this->_translation_updates)) { $this->_translation_updates = array(); - if (current_user_can('update_languages')) { - $translation_updates = $this->fetch_wp_org_module_translation_updates($module_type, $slug); - if (! empty($translation_updates)) { - $this->_translation_updates = $translation_updates; - } + $translation_updates = $this->fetch_wp_org_module_translation_updates($module_type, $slug); + if (! empty($translation_updates)) { + $this->_translation_updates = $translation_updates; } } @@ -647,7 +652,7 @@ public function pre_set_site_transient_update_plugins_filter($transient_data) foreach ($this->_translation_updates as $translation_update) { $lang = $translation_update['language']; if (! isset($current_plugin_translation_updates_map[ $lang ]) || - version_compare($translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>') + version_compare($translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>') ) { $current_plugin_translation_updates_map[ $lang ] = $translation_update; } @@ -661,7 +666,7 @@ public function pre_set_site_transient_update_plugins_filter($transient_data) } /** - * Get module's required data for the updates mechanism. + * Get module's required data for the updates' mechanism. * * @author Vova Feldman (@svovaf) * @since 2.0.0 @@ -672,13 +677,14 @@ public function pre_set_site_transient_update_plugins_filter($transient_data) */ public function get_update_details(FS_Plugin_Tag $new_version) { - $update = new stdClass(); - $update->slug = $this->_fs->get_slug(); - $update->new_version = $new_version->version; - $update->url = WP_FS__ADDRESS; - $update->package = $new_version->url; - $update->tested = $new_version->tested_up_to_version; - $update->requires = $new_version->requires_platform_version; + $update = new stdClass(); + $update->slug = $this->_fs->get_slug(); + $update->new_version = $new_version->version; + $update->url = WP_FS__ADDRESS; + $update->package = $new_version->url; + $update->tested = self::get_tested_wp_version($new_version->tested_up_to_version); + $update->requires = $new_version->requires_platform_version; + $update->requires_php = $new_version->requires_programming_language_version; $icon = $this->_fs->get_local_icon_url(); @@ -825,9 +831,9 @@ public function delete_update_data() $basename = $this->_fs->get_plugin_basename(); if (! is_object($transient_data) || - ! isset($transient_data->response) || + ! isset($transient_data->response) || ! is_array($transient_data->response) || - empty($transient_data->response[ $basename ]) + empty($transient_data->response[ $basename ]) ) { return; } @@ -855,28 +861,34 @@ public function delete_update_data() */ public static function _fetch_plugin_info_from_repository($action, $args) { - $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/'; - if ($ssl = wp_http_supports(array( 'ssl' ))) { - $url = set_url_scheme($url, 'https'); - } - - $args = array( - 'timeout' => 15, - 'body' => array( + $url = $http_url = 'http://api.wordpress.org/plugins/info/1.2/'; + $url = add_query_arg( + array( 'action' => $action, - 'request' => serialize($args) - ) + 'request' => $args, + ), + $url ); - $request = wp_remote_post($url, $args); + if (wp_http_supports(array( 'ssl' ))) { + $url = set_url_scheme($url, 'https'); + } + + // The new endpoint version serves only GET requests. + $request = wp_remote_get($url, array( 'timeout' => 15 )); if (is_wp_error($request)) { return false; } - $res = maybe_unserialize(wp_remote_retrieve_body($request)); + $res = json_decode(wp_remote_retrieve_body($request), true); + + if (is_array($res)) { + // Object casting is required in order to match the info/1.0 format. We are not decoding directly into an object as we need some fields to remain an array (e.g., $res->sections). + $res = (object) $res; + } - if (! is_object($res) && ! is_array($res)) { + if (! is_object($res) || isset($res->error)) { return false; } @@ -1118,6 +1130,7 @@ public function plugins_api_filter($data, $action = '', $args = null) if (! $plugin_in_repo) { $data->last_updated = ! is_null($new_version->updated) ? $new_version->updated : $new_version->created; $data->requires = $new_version->requires_platform_version; + $data->requires_php = $new_version->requires_programming_language_version; $data->tested = $new_version->tested_up_to_version; } @@ -1171,9 +1184,29 @@ public function plugins_api_filter($data, $action = '', $args = null) } } + if (! empty($data->tested)) { + $data->tested = self::get_tested_wp_version($data->tested); + } + return $data; } + /** + * @since 2.5.3 If the current WordPress version is a patch of the tested version (e.g., 6.1.2 is a patch of 6.1), then override the tested version with the patch so developers won't need to release a new version just to bump the latest supported WP version. + * + * @param string|null $tested_up_to + * + * @return string|null + */ + private static function get_tested_wp_version($tested_up_to) + { + $current_wp_version = get_bloginfo('version'); + + return (! empty($tested_up_to) && fs_starts_with($current_wp_version, $tested_up_to . '.')) ? + $current_wp_version : + $tested_up_to; + } + /** * @author Vova Feldman (@svovaf) * @since 1.2.1.7 diff --git a/freemius/includes/class-fs-storage.php b/freemius/includes/class-fs-storage.php index a1f6469..fef6f95 100644 --- a/freemius/includes/class-fs-storage.php +++ b/freemius/includes/class-fs-storage.php @@ -15,9 +15,11 @@ * * A wrapper class for handling network level and single site level storage. * - * @property bool $is_network_activation - * @property int $network_install_blog_id - * @property object $sync_cron + * @property bool $is_network_activation + * @property int $network_install_blog_id + * @property bool|null $is_extensions_tracking_allowed + * @property bool|null $is_diagnostic_tracking_allowed + * @property object $sync_cron */ class FS_Storage { @@ -73,6 +75,16 @@ class FS_Storage */ private static $_NETWORK_OPTIONS_MAP; + const OPTION_LEVEL_UNDEFINED = -1; + // The option should be stored on the network level. + const OPTION_LEVEL_NETWORK = 0; + // The option should be stored on the network level when the plugin is network-activated. + const OPTION_LEVEL_NETWORK_ACTIVATED = 1; + // The option should be stored on the network level when the plugin is network-activated and the opt-in connection was NOT delegated to the sub-site admin. + const OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED = 2; + // The option should be stored on the site level. + const OPTION_LEVEL_SITE = 3; + /** * @author Leo Fajardo (@leorw) * @@ -147,11 +159,17 @@ public function set_site_blog_context($blog_id) * @param string $key * @param mixed $value * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + * @param int $option_level Since 2.5.1 * @param bool $flush */ - public function store($key, $value, $network_level_or_blog_id = null, $flush = true) - { - if ($this->should_use_network_storage($key, $network_level_or_blog_id)) { + public function store( + $key, + $value, + $network_level_or_blog_id = null, + $option_level = self::OPTION_LEVEL_UNDEFINED, + $flush = true + ) { + if ($this->should_use_network_storage($key, $network_level_or_blog_id, $option_level)) { $this->_network_storage->store($key, $value, $flush); } else { $storage = $this->get_site_storage($network_level_or_blog_id); @@ -207,12 +225,17 @@ public function remove($key, $store = true, $network_level_or_blog_id = null) * @param string $key * @param mixed $default * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + * @param int $option_level Since 2.5.1 * * @return mixed */ - public function get($key, $default = false, $network_level_or_blog_id = null) - { - if ($this->should_use_network_storage($key, $network_level_or_blog_id)) { + public function get( + $key, + $default = false, + $network_level_or_blog_id = null, + $option_level = self::OPTION_LEVEL_UNDEFINED + ) { + if ($this->should_use_network_storage($key, $network_level_or_blog_id, $option_level)) { return $this->_network_storage->get($key, $default); } else { $storage = $this->get_site_storage($network_level_or_blog_id); @@ -302,19 +325,6 @@ public function migrate_to_network() // Migrate option to the network storage. $this->_network_storage->store($option, $this->_storage->{$option}, false); - /** - * Remove the option from site level storage. - * - * IMPORTANT: - * The line below is intentionally commented since we want to preserve the option - * on the site storage level for "downgrade compatibility". Basically, if the user - * will downgrade to an older version of the plugin with the prev storage structure, - * it will continue working. - * - * @todo After a few releases we can remove this. - */ -// $this->_storage->remove($option, false); - $updated = true; } } @@ -350,63 +360,61 @@ private static function load_network_options_map() { self::$_NETWORK_OPTIONS_MAP = array( // Network level options. - 'affiliate_application_data' => 0, - 'beta_data' => 0, - 'connectivity_test' => 0, - 'handle_gdpr_admin_notice' => 0, - 'has_trial_plan' => 0, - 'install_sync_timestamp' => 0, - 'install_sync_cron' => 0, - 'is_anonymous_ms' => 0, - 'is_network_activated' => 0, - 'is_on' => 0, - 'is_plugin_new_install' => 0, - 'network_install_blog_id' => 0, - 'pending_sites_info' => 0, - 'plugin_last_version' => 0, - 'plugin_main_file' => 0, - 'plugin_version' => 0, - 'sdk_downgrade_mode' => 0, - 'sdk_last_version' => 0, - 'sdk_upgrade_mode' => 0, - 'sdk_version' => 0, - 'sticky_optin_added_ms' => 0, - 'subscriptions' => 0, - 'sync_timestamp' => 0, - 'sync_cron' => 0, - 'was_plugin_loaded' => 0, - 'network_user_id' => 0, - 'plugin_upgrade_mode' => 0, - 'plugin_downgrade_mode' => 0, - 'is_network_connected' => 0, + 'affiliate_application_data' => self::OPTION_LEVEL_NETWORK, + 'beta_data' => self::OPTION_LEVEL_NETWORK, + 'connectivity_test' => self::OPTION_LEVEL_NETWORK, + 'handle_gdpr_admin_notice' => self::OPTION_LEVEL_NETWORK, + 'has_trial_plan' => self::OPTION_LEVEL_NETWORK, + 'install_sync_timestamp' => self::OPTION_LEVEL_NETWORK, + 'install_sync_cron' => self::OPTION_LEVEL_NETWORK, + 'is_anonymous_ms' => self::OPTION_LEVEL_NETWORK, + 'is_network_activated' => self::OPTION_LEVEL_NETWORK, + 'is_on' => self::OPTION_LEVEL_NETWORK, + 'is_plugin_new_install' => self::OPTION_LEVEL_NETWORK, + 'network_install_blog_id' => self::OPTION_LEVEL_NETWORK, + 'pending_sites_info' => self::OPTION_LEVEL_NETWORK, + 'plugin_last_version' => self::OPTION_LEVEL_NETWORK, + 'plugin_main_file' => self::OPTION_LEVEL_NETWORK, + 'plugin_version' => self::OPTION_LEVEL_NETWORK, + 'sdk_downgrade_mode' => self::OPTION_LEVEL_NETWORK, + 'sdk_last_version' => self::OPTION_LEVEL_NETWORK, + 'sdk_upgrade_mode' => self::OPTION_LEVEL_NETWORK, + 'sdk_version' => self::OPTION_LEVEL_NETWORK, + 'sticky_optin_added_ms' => self::OPTION_LEVEL_NETWORK, + 'subscriptions' => self::OPTION_LEVEL_NETWORK, + 'sync_timestamp' => self::OPTION_LEVEL_NETWORK, + 'sync_cron' => self::OPTION_LEVEL_NETWORK, + 'was_plugin_loaded' => self::OPTION_LEVEL_NETWORK, + 'network_user_id' => self::OPTION_LEVEL_NETWORK, + 'plugin_upgrade_mode' => self::OPTION_LEVEL_NETWORK, + 'plugin_downgrade_mode' => self::OPTION_LEVEL_NETWORK, + 'is_network_connected' => self::OPTION_LEVEL_NETWORK, /** - * Special flag that is used when a super-admin upgrades to the new version of the SDK that - * supports network level integration, when the connection decision wasn't made for all of the - * sites in the network. + * Special flag that is used when a super-admin upgrades to the new version of the SDK that supports network level integration, when the connection decision wasn't made for all the sites in the network. */ - 'is_network_activation' => 0, - 'license_migration' => 0, + 'is_network_activation' => self::OPTION_LEVEL_NETWORK, + 'license_migration' => self::OPTION_LEVEL_NETWORK, // When network activated, then network level. - 'install_timestamp' => 1, - 'prev_is_premium' => 1, - 'require_license_activation' => 1, + 'install_timestamp' => self::OPTION_LEVEL_NETWORK_ACTIVATED, + 'prev_is_premium' => self::OPTION_LEVEL_NETWORK_ACTIVATED, + 'require_license_activation' => self::OPTION_LEVEL_NETWORK_ACTIVATED, // If not network activated OR delegated, then site level. - 'activation_timestamp' => 2, - 'expired_license_notice_shown' => 2, - 'is_whitelabeled' => 2, - 'last_license_key' => 2, - 'last_license_user_id' => 2, - 'prev_user_id' => 2, - 'sticky_optin_added' => 2, - 'uninstall_reason' => 2, - 'is_pending_activation' => 2, - 'pending_license_key' => 2, - 'is_extensions_tracking_allowed' => 2, + 'activation_timestamp' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, + 'expired_license_notice_shown' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, + 'is_whitelabeled' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, + 'last_license_key' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, + 'last_license_user_id' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, + 'prev_user_id' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, + 'sticky_optin_added' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, + 'uninstall_reason' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, + 'is_pending_activation' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, + 'pending_license_key' => self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED, // Site level options. - 'is_anonymous' => 3, + 'is_anonymous' => self::OPTION_LEVEL_SITE, + 'clone_id' => self::OPTION_LEVEL_SITE, ); } @@ -417,26 +425,34 @@ private static function load_network_options_map() * @since 2.0.0 * * @param string $key + * @param int $option_level Since 2.5.1 * - * @return bool|mixed + * @return bool */ - private function is_multisite_option($key) + private function is_multisite_option($key, $option_level = self::OPTION_LEVEL_UNDEFINED) { if (! isset(self::$_NETWORK_OPTIONS_MAP)) { self::load_network_options_map(); } - if (! isset(self::$_NETWORK_OPTIONS_MAP[ $key ])) { + if ( + self::OPTION_LEVEL_UNDEFINED === $option_level && + isset(self::$_NETWORK_OPTIONS_MAP[ $key ]) + ) { + $option_level = self::$_NETWORK_OPTIONS_MAP[ $key ]; + } + + if (self::OPTION_LEVEL_UNDEFINED === $option_level) { // Option not found -> use site level storage. return false; } - if (0 === self::$_NETWORK_OPTIONS_MAP[ $key ]) { + if (self::OPTION_LEVEL_NETWORK === $option_level) { // Option found and set to always use the network level storage on a multisite. return true; } - if (3 === self::$_NETWORK_OPTIONS_MAP[ $key ]) { + if (self::OPTION_LEVEL_SITE === $option_level) { // Option found and set to always use the site level storage on a multisite. return false; } @@ -445,12 +461,15 @@ private function is_multisite_option($key) return false; } - if (1 === self::$_NETWORK_OPTIONS_MAP[ $key ]) { + if (self::OPTION_LEVEL_NETWORK_ACTIVATED === $option_level) { // Network activated. return true; } - if (2 === self::$_NETWORK_OPTIONS_MAP[ $key ] && ! $this->_is_delegated_connection) { + if ( + self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED === $option_level && + ! $this->_is_delegated_connection + ) { // Network activated and not delegated. return true; } @@ -463,11 +482,15 @@ private function is_multisite_option($key) * * @param string $key * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). + * @param int $option_level Since 2.5.1 * * @return bool */ - private function should_use_network_storage($key, $network_level_or_blog_id = null) - { + private function should_use_network_storage( + $key, + $network_level_or_blog_id = null, + $option_level = self::OPTION_LEVEL_UNDEFINED + ) { if (! $this->_is_multisite) { // Not a multisite environment. return false; @@ -479,12 +502,12 @@ private function should_use_network_storage($key, $network_level_or_blog_id = nu } if (is_bool($network_level_or_blog_id)) { - // Explicitly specified whether should use the network or blog level storage. + // Explicitly specified whether it should use the network or blog level storage. return $network_level_or_blog_id; } // Determine which storage to use based on the option. - return $this->is_multisite_option($key); + return $this->is_multisite_option($key, $option_level); } /** diff --git a/freemius/includes/class-fs-user-lock.php b/freemius/includes/class-fs-user-lock.php index 384be78..d855e01 100644 --- a/freemius/includes/class-fs-user-lock.php +++ b/freemius/includes/class-fs-user-lock.php @@ -10,19 +10,17 @@ exit; } + require_once WP_FS__DIR_INCLUDES . '/class-fs-lock.php'; + /** * Class FS_User_Lock */ class FS_User_Lock { /** - * @var int - */ - private $_wp_user_id; - /** - * @var int + * @var FS_Lock */ - private $_thread_id; + private $_lock; #-------------------------------------------------------------------------------- #region Singleton @@ -52,10 +50,10 @@ public static function instance() private function __construct() { - $this->_wp_user_id = Freemius::get_current_wp_user_id(); - $this->_thread_id = mt_rand(0, 32000); - } + $current_user_id = Freemius::get_current_wp_user_id(); + $this->_lock = new FS_Lock("locked_{$current_user_id}"); + } /** * Try to acquire lock. If the lock is already set or is being acquired by another locker, don't do anything. @@ -69,20 +67,7 @@ private function __construct() */ public function try_lock($expiration = 0) { - if ($this->is_locked()) { - // Already locked. - return false; - } - - set_site_transient("locked_{$this->_wp_user_id}", $this->_thread_id, $expiration); - - if ($this->has_lock()) { - set_site_transient("locked_{$this->_wp_user_id}", true, $expiration); - - return true; - } - - return false; + return $this->_lock->try_lock($expiration); } /** @@ -95,20 +80,7 @@ public function try_lock($expiration = 0) */ public function lock($expiration = 0) { - set_site_transient("locked_{$this->_wp_user_id}", true, $expiration); - } - - /** - * Checks if lock is currently acquired. - * - * @author Vova Feldman (@svovaf) - * @since 2.1.0 - * - * @return bool - */ - public function is_locked() - { - return (false !== get_site_transient("locked_{$this->_wp_user_id}")); + $this->_lock->lock($expiration); } /** @@ -119,16 +91,6 @@ public function is_locked() */ public function unlock() { - delete_site_transient("locked_{$this->_wp_user_id}"); - } - - /** - * Checks if lock is currently acquired by the current locker. - * - * @return bool - */ - private function has_lock() - { - return ($this->_thread_id == get_site_transient("locked_{$this->_wp_user_id}")); + $this->_lock->unlock(); } } diff --git a/freemius/includes/customizer/class-fs-customizer-support-section.php b/freemius/includes/customizer/class-fs-customizer-support-section.php index 8327213..3a9300d 100644 --- a/freemius/includes/customizer/class-fs-customizer-support-section.php +++ b/freemius/includes/customizer/class-fs-customizer-support-section.php @@ -1,87 +1,84 @@ register_section_type('FS_Customizer_Support_Section'); + /** + * Class Zerif_Customizer_Theme_Info_Main + * + * @since 1.0.0 + * @access public + */ + class FS_Customizer_Support_Section extends WP_Customize_Section { - parent::__construct($manager, $id, $args); - } + function __construct( $manager, $id, $args = array() ) { + $manager->register_section_type( 'FS_Customizer_Support_Section' ); - /** - * The type of customize section being rendered. - * - * @since 1.0.0 - * @access public - * @var string - */ - public $type = 'freemius-support-section'; + parent::__construct( $manager, $id, $args ); + } - /** - * @var Freemius - */ - public $fs = null; + /** + * The type of customize section being rendered. + * + * @since 1.0.0 + * @access public + * @var string + */ + public $type = 'freemius-support-section'; - /** - * Add custom parameters to pass to the JS via JSON. - * - * @since 1.0.0 - */ - public function json() - { - $json = parent::json(); + /** + * @var Freemius + */ + public $fs = null; - $is_contact_visible = $this->fs->is_page_visible('contact'); - $is_support_visible = $this->fs->is_page_visible('support'); + /** + * Add custom parameters to pass to the JS via JSON. + * + * @since 1.0.0 + */ + public function json() { + $json = parent::json(); - $json['theme_title'] = $this->fs->get_plugin_name(); + $is_contact_visible = $this->fs->is_page_visible( 'contact' ); + $is_support_visible = $this->fs->is_page_visible( 'support' ); - if ($is_contact_visible && $is_support_visible) { - $json['theme_title'] .= ' ' . $this->fs->get_text_inline('Support', 'support'); - } + $json['theme_title'] = $this->fs->get_plugin_name(); - if ($is_contact_visible) { - $json['contact'] = array( - 'label' => $this->fs->get_text_inline('Contact Us', 'contact-us'), - 'url' => $this->fs->contact_url(), - ); - } + if ( $is_contact_visible && $is_support_visible ) { + $json['theme_title'] .= ' ' . $this->fs->get_text_inline( 'Support', 'support' ); + } - if ($is_support_visible) { - $json['support'] = array( - 'label' => $this->fs->get_text_inline('Support Forum', 'support-forum'), - 'url' => $this->fs->get_support_forum_url() - ); - } + if ( $is_contact_visible ) { + $json['contact'] = array( + 'label' => $this->fs->get_text_inline( 'Contact Us', 'contact-us' ), + 'url' => $this->fs->contact_url(), + ); + } - return $json; - } + if ( $is_support_visible ) { + $json['support'] = array( + 'label' => $this->fs->get_text_inline( 'Support Forum', 'support-forum' ), + 'url' => $this->fs->get_support_forum_url() + ); + } - /** - * Outputs the Underscore.js template. - * - * @since 1.0.0 - */ - protected function render_template() - { - ?> + return $json; + } + + /** + * Outputs the Underscore.js template. + * + * @since 1.0.0 + */ + protected function render_template() { + ?>
        • @@ -101,5 +98,5 @@ class="accordion-section control-section control-section-{{ data.type }} cannot-

        • register_control_type('FS_Customizer_Upsell_Control'); - - parent::__construct($manager, $id, $args); - } - - /** - * Enqueue resources for the control. - */ - public function enqueue() - { - fs_enqueue_local_style('fs_customizer', 'customizer.css'); - } - - /** - * Json conversion - */ - public function to_json() - { - $pricing_cta = esc_html($this->fs->get_pricing_cta_label()) . '  ' . (is_rtl() ? '←' : '➤'); - - parent::to_json(); - - $this->json['button_text'] = $pricing_cta; - $this->json['button_url'] = $this->fs->is_in_trial_promotion() ? - $this->fs->get_trial_url() : - $this->fs->get_upgrade_url(); - - $api = FS_Plugin::is_valid_id($this->fs->get_bundle_id()) ? - $this->fs->get_api_bundle_scope() : - $this->fs->get_api_plugin_scope(); - - // Load features. - $pricing = $api->get($this->fs->add_show_pending("pricing.json")); - - if ($this->fs->is_api_result_object($pricing, 'plans')) { - // Add support features. - if (is_array($pricing->plans) && 0 < count($pricing->plans)) { - $support_features = array( - 'kb' => 'Help Center', - 'forum' => 'Support Forum', - 'email' => 'Priority Email Support', - 'phone' => 'Phone Support', - 'skype' => 'Skype Support', - 'is_success_manager' => 'Personal Success Manager', - ); - - for ($i = 0, $len = count($pricing->plans); $i < $len; $i ++) { - if ('free' == $pricing->plans[$i]->name) { - continue; - } - - if (! isset($pricing->plans[ $i ]->features) || - ! is_array($pricing->plans[ $i ]->features)) { - $pricing->plans[$i]->features = array(); - } - - foreach ($support_features as $key => $label) { - $key = ('is_success_manager' !== $key) ? - "support_{$key}" : - $key; - - if (! empty($pricing->plans[ $i ]->{$key})) { - $support_feature = new stdClass(); - $support_feature->title = $label; - - $pricing->plans[ $i ]->features[] = $support_feature; - } - } - } - } - } - - $this->json['plans'] = $pricing->plans; - - $this->json['strings'] = array( - 'plan' => $this->fs->get_text_x_inline('Plan', 'as product pricing plan', 'plan'), - ); - } - - /** - * Control content - */ - public function content_template() - { - ?> + /** + * @package Freemius + * @copyright Copyright (c) 2015, Freemius, Inc. + * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3 + * @since 1.2.2.7 + */ + + if ( ! defined( 'ABSPATH' ) ) { + exit; + } + + /** + * Class FS_Customizer_Upsell_Control + */ + class FS_Customizer_Upsell_Control extends WP_Customize_Control { + + /** + * Control type + * + * @var string control type + */ + public $type = 'freemius-upsell-control'; + + /** + * @var Freemius + */ + public $fs = null; + + /** + * @param WP_Customize_Manager $manager the customize manager class. + * @param string $id id. + * @param array $args customizer manager parameters. + */ + public function __construct( WP_Customize_Manager $manager, $id, array $args ) { + $manager->register_control_type( 'FS_Customizer_Upsell_Control' ); + + parent::__construct( $manager, $id, $args ); + } + + /** + * Enqueue resources for the control. + */ + public function enqueue() { + fs_enqueue_local_style( 'fs_customizer', 'customizer.css' ); + } + + /** + * Json conversion + */ + public function to_json() { + $pricing_cta = esc_html( $this->fs->get_pricing_cta_label() ) . '  ' . ( is_rtl() ? '←' : '➤' ); + + parent::to_json(); + + $this->json['button_text'] = $pricing_cta; + $this->json['button_url'] = $this->fs->is_in_trial_promotion() ? + $this->fs->get_trial_url() : + $this->fs->get_upgrade_url(); + + $api = FS_Plugin::is_valid_id( $this->fs->get_bundle_id() ) ? + $this->fs->get_api_bundle_scope() : + $this->fs->get_api_plugin_scope(); + + // Load features. + $pricing = $api->get( $this->fs->add_show_pending( "pricing.json" ) ); + + if ( $this->fs->is_api_result_object( $pricing, 'plans' ) ) { + // Add support features. + if ( is_array( $pricing->plans ) && 0 < count( $pricing->plans ) ) { + $support_features = array( + 'kb' => 'Help Center', + 'forum' => 'Support Forum', + 'email' => 'Priority Email Support', + 'phone' => 'Phone Support', + 'skype' => 'Skype Support', + 'is_success_manager' => 'Personal Success Manager', + ); + + for ( $i = 0, $len = count( $pricing->plans ); $i < $len; $i ++ ) { + if ( 'free' == $pricing->plans[$i]->name ) { + continue; + } + + if ( ! isset( $pricing->plans[ $i ]->features ) || + ! is_array( $pricing->plans[ $i ]->features ) ) { + $pricing->plans[$i]->features = array(); + } + + foreach ( $support_features as $key => $label ) { + $key = ( 'is_success_manager' !== $key ) ? + "support_{$key}" : + $key; + + if ( ! empty( $pricing->plans[ $i ]->{$key} ) ) { + + $support_feature = new stdClass(); + $support_feature->title = $label; + + $pricing->plans[ $i ]->features[] = $support_feature; + } + } + } + + $this->json['plans'] = $pricing->plans; + } + } + + $this->json['strings'] = array( + 'plan' => $this->fs->get_text_x_inline( 'Plan', 'as product pricing plan', 'plan' ), + ); + } + + /** + * Control content + */ + public function content_template() { + ?>
          <# if ( data.plans ) { #>
            @@ -161,6 +157,5 @@ public function content_template()
          <# } #>
          - title('Freemius'); - } + if ( class_exists( 'Debug_Bar_Panel' ) ) { - public static function requests_count() - { - if (class_exists('Freemius_Api_WordPress')) { - $logger = Freemius_Api_WordPress::GetLogger(); - } else { - $logger = array(); - } + /** + * Extends Debug Bar plugin by adding a panel to show all Freemius API requests. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.7.3 + * + * Class Freemius_Debug_Bar_Panel + */ + class Freemius_Debug_Bar_Panel extends Debug_Bar_Panel { - return number_format(count($logger)); - } + public function init() { + $this->title( 'Freemius' ); // @phpstan-ignore-line + } - public static function total_time() - { - if (class_exists('Freemius_Api_WordPress')) { - $logger = Freemius_Api_WordPress::GetLogger(); - } else { - $logger = array(); - } + public static function requests_count() { + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + $logger = Freemius_Api_WordPress::GetLogger(); + } else { + $logger = array(); + } - $total_time = .0; - foreach ($logger as $l) { - $total_time += $l['total']; - } + return number_format( count( $logger ) ); + } - return number_format(100 * $total_time, 2) . ' ' . fs_text_x_inline('ms', 'milliseconds'); - } + public static function total_time() { + if ( class_exists( 'Freemius_Api_WordPress' ) ) { + $logger = Freemius_Api_WordPress::GetLogger(); + } else { + $logger = array(); + } - public function render() - { - ?> -
          - -
          - -
          - -
          - -
          - +
          + +
          + +
          + +
          + +
          + is_disconnected); + return (isset($this->is_beta) && true === $this->is_beta); } /** - * @author Vova Feldman (@svovaf) - * @since 2.0.0 + * @author Leo Fajardo (@leorw) + * @since 2.5.1 * - * @return bool - */ - public function is_tracking_prohibited() - { - return ! $this->is_tracking_allowed(); - } - - /** - * @author Edgar Melkonyan + * @param string $site_url * * @return bool */ - public function is_beta() + public function is_clone($site_url) { - return (isset($this->is_beta) && true === $this->is_beta); + $clone_install_url = trailingslashit(fs_strip_url_protocol($this->url)); + + return ($clone_install_url !== $site_url); } } diff --git a/freemius/includes/entities/class-fs-subscription.php b/freemius/includes/entities/class-fs-subscription.php index ad475ac..074396f 100644 --- a/freemius/includes/entities/class-fs-subscription.php +++ b/freemius/includes/entities/class-fs-subscription.php @@ -12,7 +12,6 @@ class FS_Subscription extends FS_Entity { - #region Properties /** diff --git a/freemius/includes/entities/class-fs-user.php b/freemius/includes/entities/class-fs-user.php index 4c8af54..58f405a 100644 --- a/freemius/includes/entities/class-fs-user.php +++ b/freemius/includes/entities/class-fs-user.php @@ -12,7 +12,6 @@ class FS_User extends FS_Scope_Entity { - #region Properties /** @@ -60,6 +59,18 @@ public function is_verified() return (isset($this->is_verified) && true === $this->is_verified); } + /** + * @author Leo Fajardo (@leorw) + * @since 2.4.2 + * + * @return bool + */ + public function is_beta() + { + // Return `false` since this is just for backward compatibility. + return false; + } + public static function get_type() { return 'user'; diff --git a/freemius/includes/fs-core-functions.php b/freemius/includes/fs-core-functions.php index fe8458e..c0a9778 100644 --- a/freemius/includes/fs-core-functions.php +++ b/freemius/includes/fs-core-functions.php @@ -6,63 +6,55 @@ * @since 1.0.3 */ - if (! defined('ABSPATH')) { + if ( ! defined( 'ABSPATH' ) ) { exit; } - if (! function_exists('fs_dummy')) { - function fs_dummy() - { + if ( ! function_exists( 'fs_dummy' ) ) { + function fs_dummy() { } } /* Url. --------------------------------------------------------------------------------------------*/ - if (! function_exists('fs_get_url_daily_cache_killer')) { - function fs_get_url_daily_cache_killer() - { - return date('\YY\Mm\Dd'); + if ( ! function_exists( 'fs_get_url_daily_cache_killer' ) ) { + function fs_get_url_daily_cache_killer() { + return date( '\YY\Mm\Dd' ); } } /* Templates / Views. --------------------------------------------------------------------------------------------*/ - if (! function_exists('fs_get_template_path')) { - function fs_get_template_path($path) - { - return WP_FS__DIR_TEMPLATES . '/' . trim($path, '/'); + if ( ! function_exists( 'fs_get_template_path' ) ) { + function fs_get_template_path( $path ) { + return WP_FS__DIR_TEMPLATES . '/' . trim( $path, '/' ); } - function fs_include_template($path, &$params = null) - { + function fs_include_template( $path, &$params = null ) { $VARS = &$params; - include fs_get_template_path($path); + include fs_get_template_path( $path ); } - function fs_include_once_template($path, &$params = null) - { + function fs_include_once_template( $path, &$params = null ) { $VARS = &$params; - include_once fs_get_template_path($path); + include_once fs_get_template_path( $path ); } - function fs_require_template($path, &$params = null) - { + function fs_require_template( $path, &$params = null ) { $VARS = &$params; - require fs_get_template_path($path); + require fs_get_template_path( $path ); } - function fs_require_once_template($path, &$params = null) - { + function fs_require_once_template( $path, &$params = null ) { $VARS = &$params; - require_once fs_get_template_path($path); + require_once fs_get_template_path( $path ); } - function fs_get_template($path, &$params = null) - { + function fs_get_template( $path, &$params = null ) { ob_start(); $VARS = &$params; - require fs_get_template_path($path); + require fs_get_template_path( $path ); return ob_get_clean(); } @@ -71,7 +63,7 @@ function fs_get_template($path, &$params = null) /* Scripts and styles including. --------------------------------------------------------------------------------------------*/ - if (! function_exists('fs_asset_url')) { + if ( ! function_exists( 'fs_asset_url' ) ) { /** * Generates an absolute URL to the given path. This function ensures that the URL will be correct whether the asset * is inside a plugin's folder or a theme's folder. @@ -92,27 +84,26 @@ function fs_get_template($path, &$params = null) * * @return string Asset's URL. */ - function fs_asset_url($asset_abs_path) - { - $wp_content_dir = fs_normalize_path(WP_CONTENT_DIR); - $asset_abs_path = fs_normalize_path($asset_abs_path); + function fs_asset_url( $asset_abs_path ) { + $wp_content_dir = fs_normalize_path( WP_CONTENT_DIR ); + $asset_abs_path = fs_normalize_path( $asset_abs_path ); - if (0 === strpos($asset_abs_path, $wp_content_dir)) { + if ( 0 === strpos( $asset_abs_path, $wp_content_dir ) ) { // Handle both theme and plugin assets located in the standard directories. - $asset_rel_path = str_replace($wp_content_dir, '', $asset_abs_path); - $asset_url = content_url(fs_normalize_path($asset_rel_path)); + $asset_rel_path = str_replace( $wp_content_dir, '', $asset_abs_path ); + $asset_url = content_url( fs_normalize_path( $asset_rel_path ) ); } else { - $wp_plugins_dir = fs_normalize_path(WP_PLUGIN_DIR); - if (0 === strpos($asset_abs_path, $wp_plugins_dir)) { + $wp_plugins_dir = fs_normalize_path( WP_PLUGIN_DIR ); + if ( 0 === strpos( $asset_abs_path, $wp_plugins_dir ) ) { // Try to handle plugin assets that may be located in a non-standard plugins directory. - $asset_rel_path = str_replace($wp_plugins_dir, '', $asset_abs_path); - $asset_url = plugins_url(fs_normalize_path($asset_rel_path)); + $asset_rel_path = str_replace( $wp_plugins_dir, '', $asset_abs_path ); + $asset_url = plugins_url( fs_normalize_path( $asset_rel_path ) ); } else { // Try to handle theme assets that may be located in a non-standard themes directory. $active_theme_stylesheet = get_stylesheet(); - $wp_themes_dir = fs_normalize_path(trailingslashit(get_theme_root($active_theme_stylesheet))); - $asset_rel_path = str_replace($wp_themes_dir, '', fs_normalize_path($asset_abs_path)); - $asset_url = trailingslashit(get_theme_root_uri($active_theme_stylesheet)) . fs_normalize_path($asset_rel_path); + $wp_themes_dir = fs_normalize_path( trailingslashit( get_theme_root( $active_theme_stylesheet ) ) ); + $asset_rel_path = str_replace( $wp_themes_dir, '', fs_normalize_path( $asset_abs_path ) ); + $asset_url = trailingslashit( get_theme_root_uri( $active_theme_stylesheet ) ) . fs_normalize_path( $asset_rel_path ); } } @@ -120,24 +111,21 @@ function fs_asset_url($asset_abs_path) } } - if (! function_exists('fs_enqueue_local_style')) { - function fs_enqueue_local_style($handle, $path, $deps = array(), $ver = false, $media = 'all') - { - wp_enqueue_style($handle, fs_asset_url(WP_FS__DIR_CSS . '/' . trim($path, '/')), $deps, $ver, $media); + if ( ! function_exists( 'fs_enqueue_local_style' ) ) { + function fs_enqueue_local_style( $handle, $path, $deps = array(), $ver = false, $media = 'all' ) { + wp_enqueue_style( $handle, fs_asset_url( WP_FS__DIR_CSS . '/' . trim( $path, '/' ) ), $deps, $ver, $media ); } } - if (! function_exists('fs_enqueue_local_script')) { - function fs_enqueue_local_script($handle, $path, $deps = array(), $ver = false, $in_footer = 'all') - { - wp_enqueue_script($handle, fs_asset_url(WP_FS__DIR_JS . '/' . trim($path, '/')), $deps, $ver, $in_footer); + if ( ! function_exists( 'fs_enqueue_local_script' ) ) { + function fs_enqueue_local_script( $handle, $path, $deps = array(), $ver = false, $in_footer = 'all' ) { + wp_enqueue_script( $handle, fs_asset_url( WP_FS__DIR_JS . '/' . trim( $path, '/' ) ), $deps, $ver, $in_footer ); } } - if (! function_exists('fs_img_url')) { - function fs_img_url($path, $img_dir = WP_FS__DIR_IMG) - { - return (fs_asset_url($img_dir . '/' . trim($path, '/'))); + if ( ! function_exists( 'fs_img_url' ) ) { + function fs_img_url( $path, $img_dir = WP_FS__DIR_IMG ) { + return ( fs_asset_url( $img_dir . '/' . trim( $path, '/' ) ) ); } } @@ -145,53 +133,106 @@ function fs_img_url($path, $img_dir = WP_FS__DIR_IMG) #region Request handlers. #-------------------------------------------------------------------------------- - if (! function_exists('fs_request_get')) { + if ( ! function_exists( 'fs_request_get_raw' ) ) { /** - * A helper method to fetch GET/POST user input with an optional default value when the input is not set. - * @author Vova Feldman (@svovaf) + * A helper function to fetch GET/POST user input with an optional default value when the input is not set. + * This function does not do sanitization. It is up to the caller to properly sanitize and validate the input. + * + * The return of this function is always unslashed. + * + * @since 2.5.10 * * @param string $key * @param mixed $def - * @param string|bool $type Since 1.2.1.7 - when set to 'get' will look for the value passed via querystring, when - * set to 'post' will look for the value passed via the POST request's body, otherwise, - * will check if the parameter was passed in any of the two. + * @param string|bool $type When set to 'get', it will look for the value passed via query string. When + * set to 'post', it will look for the value passed via the POST request's body. Otherwise, + * it will check if the parameter was passed using any of the mentioned two methods. * * @return mixed */ - function fs_request_get($key, $def = false, $type = false) - { - if (is_string($type)) { - $type = strtolower($type); + function fs_request_get_raw( $key, $def = false, $type = false ) { + if ( is_string( $type ) ) { + $type = strtolower( $type ); } /** - * Note to WordPress.org Reviewers: - * This is a helper method to fetch GET/POST user input with an optional default value when the input is not set. The actual sanitization is done in the scope of the function's usage. + * Note to WordPress.org reviewers: + * This is a helper function to fetch GET/POST user input with an optional default value when the input is not set. The actual sanitization is done in the scope of the function's usage. */ - switch ($type) { + switch ( $type ) { case 'post': - $value = isset($_POST[ $key ]) ? $_POST[ $key ] : $def; + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $value = isset( $_POST[ $key ] ) ? $_POST[ $key ] : $def; break; case 'get': - $value = isset($_GET[ $key ]) ? $_GET[ $key ] : $def; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $value = isset( $_GET[ $key ] ) ? $_GET[ $key ] : $def; break; default: - $value = isset($_REQUEST[ $key ]) ? $_REQUEST[ $key ] : $def; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $value = isset( $_REQUEST[ $key ] ) ? $_REQUEST[ $key ] : $def; break; } - return $value; + // Don't unslash if the value itself is empty (empty string, null, empty array etc). + return empty( $value ) ? $value : wp_unslash( $value ); + } + } + + if ( ! function_exists( 'fs_sanitize_input' ) ) { + /** + * Sanitizes input recursively (if an array). + * + * @param mixed $input + * + * @return mixed + * @uses sanitize_text_field() + * @since 2.5.10 + */ + function fs_sanitize_input( $input ) { + if ( is_array( $input ) ) { + foreach ( $input as $key => $value ) { + $input[ $key ] = fs_sanitize_input( $value ); + } + } else { + // Allow empty values to pass through as-is, like `null`, `''`, `0`, `'0'` etc. + $input = empty( $input ) ? $input : sanitize_text_field( $input ); + } + + return $input; + } + } + + if ( ! function_exists( 'fs_request_get' ) ) { + /** + * A helper method to fetch GET/POST user input with an optional default value when the input is not set. + * + * @author Vova Feldman (@svovaf) + * + * @note The return value is always sanitized with sanitize_text_field(). + * + * @param string $key + * @param mixed $def + * @param string|bool $type Since 1.2.1.7 - when set to 'get' will look for the value passed via querystring, when + * set to 'post' will look for the value passed via the POST request's body, otherwise, + * will check if the parameter was passed in any of the two. + * + * + * @return mixed + */ + function fs_request_get( $key, $def = false, $type = false ) { + return fs_sanitize_input( fs_request_get_raw( $key, $def, $type ) ); } } - if (! function_exists('fs_request_has')) { - function fs_request_has($key) - { - return isset($_REQUEST[ $key ]); + if ( ! function_exists( 'fs_request_has' ) ) { + function fs_request_has( $key ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return isset( $_REQUEST[ $key ] ); } } - if (! function_exists('fs_request_get_bool')) { + if ( ! function_exists( 'fs_request_get_bool' ) ) { /** * A helper method to fetch GET/POST user boolean input with an optional default value when the input is not set. * @@ -202,28 +243,27 @@ function fs_request_has($key) * * @return bool|mixed */ - function fs_request_get_bool($key, $def = false) - { - $val = fs_request_get($key, null); + function fs_request_get_bool( $key, $def = false ) { + $val = fs_request_get( $key, null ); - if (is_null($val)) { + if ( is_null( $val ) ) { return $def; } - if (is_bool($val)) { + if ( is_bool( $val ) ) { return $val; - } elseif (is_numeric($val)) { - if (1 == $val) { + } else if ( is_numeric( $val ) ) { + if ( 1 == $val ) { return true; - } elseif (0 == $val) { + } else if ( 0 == $val ) { return false; } - } elseif (is_string($val)) { - $val = strtolower($val); + } else if ( is_string( $val ) ) { + $val = strtolower( $val ); - if ('true' === $val) { + if ( 'true' === $val ) { return true; - } elseif ('false' === $val) { + } else if ( 'false' === $val ) { return false; } } @@ -232,47 +272,45 @@ function fs_request_get_bool($key, $def = false) } } - if (! function_exists('fs_request_is_post')) { - function fs_request_is_post() - { - return ('post' === strtolower($_SERVER['REQUEST_METHOD'])); + if ( ! function_exists( 'fs_request_is_post' ) ) { + function fs_request_is_post() { + return ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) ); } } - if (! function_exists('fs_request_is_get')) { - function fs_request_is_get() - { - return ('get' === strtolower($_SERVER['REQUEST_METHOD'])); + if ( ! function_exists( 'fs_request_is_get' ) ) { + function fs_request_is_get() { + return ( 'get' === strtolower( $_SERVER['REQUEST_METHOD'] ) ); } } - if (! function_exists('fs_get_action')) { - function fs_get_action($action_key = 'action') - { - if (! empty($_REQUEST[ $action_key ]) && is_string($_REQUEST[ $action_key ])) { - return strtolower($_REQUEST[ $action_key ]); + if ( ! function_exists( 'fs_get_action' ) ) { + function fs_get_action( $action_key = 'action' ) { + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_REQUEST[ $action_key ] ) && is_string( $_REQUEST[ $action_key ] ) ) { + return strtolower( $_REQUEST[ $action_key ] ); } - if ('action' == $action_key) { + if ( 'action' == $action_key ) { $action_key = 'fs_action'; - if (! empty($_REQUEST[ $action_key ]) && is_string($_REQUEST[ $action_key ])) { - return strtolower($_REQUEST[ $action_key ]); + if ( ! empty( $_REQUEST[ $action_key ] ) && is_string( $_REQUEST[ $action_key ] ) ) { + return strtolower( $_REQUEST[ $action_key ] ); } } return false; + // phpcs:enable WordPress.Security.NonceVerification.Recommended } } - if (! function_exists('fs_request_is_action')) { - function fs_request_is_action($action, $action_key = 'action') - { - return (strtolower($action) === fs_get_action($action_key)); + if ( ! function_exists( 'fs_request_is_action' ) ) { + function fs_request_is_action( $action, $action_key = 'action' ) { + return ( strtolower( $action ) === fs_get_action( $action_key ) ); } } - if (! function_exists('fs_request_is_action_secure')) { + if ( ! function_exists( 'fs_request_is_action_secure' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.0.0 @@ -290,16 +328,16 @@ function fs_request_is_action_secure( $action_key = 'action', $nonce_key = 'nonce' ) { - if (strtolower($action) !== fs_get_action($action_key)) { + if ( strtolower( $action ) !== fs_get_action( $action_key ) ) { return false; } - $nonce = ! empty($_REQUEST[ $nonce_key ]) ? + $nonce = ! empty( $_REQUEST[ $nonce_key ] ) ? $_REQUEST[ $nonce_key ] : ''; - if (empty($nonce) || - (false === wp_verify_nonce($nonce, $action)) + if ( empty( $nonce ) || + ( false === wp_verify_nonce( $nonce, $action ) ) ) { return false; } @@ -310,14 +348,13 @@ function fs_request_is_action_secure( #endregion - if (! function_exists('fs_is_plugin_page')) { - function fs_is_plugin_page($page_slug) - { - return (is_admin() && $page_slug === fs_request_get('page')); + if ( ! function_exists( 'fs_is_plugin_page' ) ) { + function fs_is_plugin_page( $page_slug ) { + return ( is_admin() && $page_slug === fs_request_get( 'page' ) ); } } - if (! function_exists('fs_get_raw_referer')) { + if ( ! function_exists( 'fs_get_raw_referer' ) ) { /** * Retrieves unvalidated referer from '_wp_http_referer' or HTTP referer. * @@ -327,15 +364,14 @@ function fs_is_plugin_page($page_slug) * * @return string|false Referer URL on success, false on failure. */ - function fs_get_raw_referer() - { - if (function_exists('wp_get_raw_referer')) { + function fs_get_raw_referer() { + if ( function_exists( 'wp_get_raw_referer' ) ) { return wp_get_raw_referer(); } - if (! empty($_REQUEST['_wp_http_referer'])) { - return wp_unslash($_REQUEST['_wp_http_referer']); - } elseif (! empty($_SERVER['HTTP_REFERER'])) { - return wp_unslash($_SERVER['HTTP_REFERER']); + if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) { + return wp_unslash( $_REQUEST['_wp_http_referer'] ); + } else if ( ! empty( $_SERVER['HTTP_REFERER'] ) ) { + return wp_unslash( $_SERVER['HTTP_REFERER'] ); } return false; @@ -344,7 +380,7 @@ function fs_get_raw_referer() /* Core UI. --------------------------------------------------------------------------------------------*/ - if (! function_exists('fs_ui_action_button')) { + if ( ! function_exists( 'fs_ui_action_button' ) ) { /** * @param number $module_id * @param string $page @@ -389,7 +425,7 @@ function fs_ui_action_button( } } - if (! function_exists('fs_ui_get_action_button')) { + if ( ! function_exists( 'fs_ui_get_action_button' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.7 @@ -422,47 +458,43 @@ function fs_ui_get_action_button( $method = 'GET' ) { // Prepend icon (if set). - $title = (is_string($icon_class) ? ' ' : '') . $title; + $title = ( is_string( $icon_class ) ? ' ' : '' ) . $title; - if (is_string($confirmation)) { - return sprintf( - '
          %s%s
          ', - freemius($module_id)->_get_admin_page_url($page, $params), + if ( is_string( $confirmation ) ) { + return sprintf( '
          %s%s
          ', + freemius( $module_id )->_get_admin_page_url( $page, $params ), $method, $action, - wp_nonce_field($action, '_wpnonce', true, false), - 'button' . (! empty($button_class) ? ' ' . $button_class : '') . ($is_primary ? ' button-primary' : '') . ($is_small ? ' button-small' : ''), + wp_nonce_field( $action, '_wpnonce', true, false ), + 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), $confirmation, $title ); - } elseif ('GET' !== strtoupper($method)) { - return sprintf( - '
          %s%s
          ', - freemius($module_id)->_get_admin_page_url($page, $params), + } else if ( 'GET' !== strtoupper( $method ) ) { + return sprintf( '
          %s%s
          ', + freemius( $module_id )->_get_admin_page_url( $page, $params ), $method, $action, - wp_nonce_field($action, '_wpnonce', true, false), - 'button' . (! empty($button_class) ? ' ' . $button_class : '') . ($is_primary ? ' button-primary' : '') . ($is_small ? ' button-small' : ''), + wp_nonce_field( $action, '_wpnonce', true, false ), + 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), $title ); } else { - return sprintf( - '%s', - wp_nonce_url(freemius($module_id)->_get_admin_page_url($page, array_merge($params, array( 'fs_action' => $action ))), $action), - 'button' . (! empty($button_class) ? ' ' . $button_class : '') . ($is_primary ? ' button-primary' : '') . ($is_small ? ' button-small' : ''), + return sprintf( '%s', + wp_nonce_url( freemius( $module_id )->_get_admin_page_url( $page, array_merge( $params, array( 'fs_action' => $action ) ) ), $action ), + 'button' . ( ! empty( $button_class ) ? ' ' . $button_class : '' ) . ( $is_primary ? ' button-primary' : '' ) . ( $is_small ? ' button-small' : '' ), $title ); } } - function fs_ui_action_link($module_id, $page, $action, $title, $params = array()) - { + function fs_ui_action_link( $module_id, $page, $action, $title, $params = array() ) { ?>_get_admin_page_url( $page, array_merge( $params, array( 'fs_action' => $action ) ) ), $action ) ?>"> $entities_or_entity) { - if (is_array($entities_or_entity)) { - $entities[ $key ] = fs_get_entities($entities_or_entity, $class_name); + foreach ( $entities as $key => $entities_or_entity ) { + if ( is_array( $entities_or_entity ) ) { + $entities[ $key ] = fs_get_entities( $entities_or_entity, $class_name ); } else { - $entities[ $key ] = fs_get_entity($entities_or_entity, $class_name); + $entities[ $key ] = fs_get_entity( $entities_or_entity, $class_name ); } } @@ -536,7 +566,7 @@ function fs_get_entities($entities, $class_name) } } - if (! function_exists('fs_nonce_url')) { + if ( ! function_exists( 'fs_nonce_url' ) ) { /** * Retrieve URL with nonce added to URL query. * @@ -553,13 +583,12 @@ function fs_get_entities($entities, $class_name) * * @return string Escaped URL with nonce action added. */ - function fs_nonce_url($actionurl, $action = - 1, $name = '_wpnonce') - { - return add_query_arg($name, wp_create_nonce($action), $actionurl); + function fs_nonce_url( $actionurl, $action = - 1, $name = '_wpnonce' ) { + return add_query_arg( $name, wp_create_nonce( $action ), $actionurl ); } } - if (! function_exists('fs_starts_with')) { + if ( ! function_exists( 'fs_starts_with' ) ) { /** * Check if string starts with. * @@ -571,15 +600,14 @@ function fs_nonce_url($actionurl, $action = - 1, $name = '_wpnonce') * * @return bool */ - function fs_starts_with($haystack, $needle) - { - $length = strlen($needle); + function fs_starts_with( $haystack, $needle ) { + $length = strlen( $needle ); - return (substr($haystack, 0, $length) === $needle); + return ( substr( $haystack, 0, $length ) === $needle ); } } - if (! function_exists('fs_ends_with')) { + if ( ! function_exists( 'fs_ends_with' ) ) { /** * Check if string ends with. * @@ -591,35 +619,33 @@ function fs_starts_with($haystack, $needle) * * @return bool */ - function fs_ends_with($haystack, $needle) - { - $length = strlen($needle); + function fs_ends_with( $haystack, $needle ) { + $length = strlen( $needle ); $start = $length * - 1; // negative - return (substr($haystack, $start) === $needle); + return ( substr( $haystack, $start ) === $needle ); } } - if (! function_exists('fs_strip_url_protocol')) { - function fs_strip_url_protocol($url) - { - if (! fs_starts_with($url, 'http')) { + if ( ! function_exists( 'fs_strip_url_protocol' ) ) { + function fs_strip_url_protocol( $url ) { + if ( ! fs_starts_with( $url, 'http' ) ) { return $url; } - $protocol_pos = strpos($url, '://'); + $protocol_pos = strpos( $url, '://' ); - if ($protocol_pos > 5) { + if ( $protocol_pos > 5 ) { return $url; } - return substr($url, $protocol_pos + 3); + return substr( $url, $protocol_pos + 3 ); } } #region Url Canonization ------------------------------------------------------------------ - if (! function_exists('fs_canonize_url')) { + if ( ! function_exists( 'fs_canonize_url' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.3 @@ -630,26 +656,25 @@ function fs_strip_url_protocol($url) * * @return string */ - function fs_canonize_url($url, $omit_host = false, $ignore_params = array()) - { - $parsed_url = parse_url(strtolower($url)); + function fs_canonize_url( $url, $omit_host = false, $ignore_params = array() ) { + $parsed_url = parse_url( strtolower( $url ) ); - // if ( ! isset( $parsed_url['host'] ) ) { - // return $url; - // } +// if ( ! isset( $parsed_url['host'] ) ) { +// return $url; +// } - $canonical = (($omit_host || ! isset($parsed_url['host'])) ? '' : $parsed_url['host']) . $parsed_url['path']; + $canonical = ( ( $omit_host || ! isset( $parsed_url['host'] ) ) ? '' : $parsed_url['host'] ) . $parsed_url['path']; - if (isset($parsed_url['query'])) { - parse_str($parsed_url['query'], $queryString); - $canonical .= '?' . fs_canonize_query_string($queryString, $ignore_params); + if ( isset( $parsed_url['query'] ) ) { + parse_str( $parsed_url['query'], $queryString ); + $canonical .= '?' . fs_canonize_query_string( $queryString, $ignore_params ); } return $canonical; } } - if (! function_exists('fs_canonize_query_string')) { + if ( ! function_exists( 'fs_canonize_query_string' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.3 @@ -660,37 +685,36 @@ function fs_canonize_url($url, $omit_host = false, $ignore_params = array()) * * @return string */ - function fs_canonize_query_string(array $params, array &$ignore_params, $params_prefix = false) - { - if (! is_array($params) || 0 === count($params)) { + function fs_canonize_query_string( array $params, array &$ignore_params, $params_prefix = false ) { + if ( ! is_array( $params ) || 0 === count( $params ) ) { return ''; } // Url encode both keys and values - $keys = fs_urlencode_rfc3986(array_keys($params)); - $values = fs_urlencode_rfc3986(array_values($params)); - $params = array_combine($keys, $values); + $keys = fs_urlencode_rfc3986( array_keys( $params ) ); + $values = fs_urlencode_rfc3986( array_values( $params ) ); + $params = array_combine( $keys, $values ); // Parameters are sorted by name, using lexicographical byte value ordering. // Ref: Spec: 9.1.1 (1) - uksort($params, 'strcmp'); + uksort( $params, 'strcmp' ); $pairs = array(); - foreach ($params as $parameter => $value) { - $lower_param = strtolower($parameter); + foreach ( $params as $parameter => $value ) { + $lower_param = strtolower( $parameter ); // Skip ignore params. - if (in_array($lower_param, $ignore_params) || - (false !== $params_prefix && fs_starts_with($lower_param, $params_prefix)) + if ( in_array( $lower_param, $ignore_params ) || + ( false !== $params_prefix && fs_starts_with( $lower_param, $params_prefix ) ) ) { continue; } - if (is_array($value)) { + if ( is_array( $value ) ) { // If two or more parameters share the same name, they are sorted by their value // Ref: Spec: 9.1.1 (1) - natsort($value); - foreach ($value as $duplicate_value) { + natsort( $value ); + foreach ( $value as $duplicate_value ) { $pairs[] = $lower_param . '=' . $duplicate_value; } } else { @@ -698,15 +722,15 @@ function fs_canonize_query_string(array $params, array &$ignore_params, $params_ } } - if (0 === count($pairs)) { + if ( 0 === count( $pairs ) ) { return ''; } - return implode("&", $pairs); + return implode( "&", $pairs ); } } - if (! function_exists('fs_urlencode_rfc3986')) { + if ( ! function_exists( 'fs_urlencode_rfc3986' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.3 @@ -715,12 +739,11 @@ function fs_canonize_query_string(array $params, array &$ignore_params, $params_ * * @return array|mixed|string */ - function fs_urlencode_rfc3986($input) - { - if (is_array($input)) { - return array_map('fs_urlencode_rfc3986', $input); - } elseif (is_scalar($input)) { - return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode($input))); + function fs_urlencode_rfc3986( $input ) { + if ( is_array( $input ) ) { + return array_map( 'fs_urlencode_rfc3986', $input ); + } else if ( is_scalar( $input ) ) { + return str_replace( '+', ' ', str_replace( '%7E', '~', rawurlencode( $input ) ) ); } return ''; @@ -729,7 +752,7 @@ function fs_urlencode_rfc3986($input) #endregion Url Canonization ------------------------------------------------------------------ - if (! function_exists('fs_download_image')) { + if ( ! function_exists( 'fs_download_image' ) ) { /** * @author Vova Feldman (@svovaf) * @@ -740,29 +763,28 @@ function fs_urlencode_rfc3986($input) * * @return bool Is successfully downloaded. */ - function fs_download_image($from, $to) - { - $dir = dirname($to); + function fs_download_image( $from, $to ) { + $dir = dirname( $to ); - if ('direct' !== get_filesystem_method(array(), $dir)) { + if ( 'direct' !== get_filesystem_method( array(), $dir ) ) { return false; } - if (! class_exists('WP_Filesystem_Direct')) { + if ( ! class_exists( 'WP_Filesystem_Direct' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; } - $fs = new WP_Filesystem_Direct(''); - $tmpfile = download_url($from); + $fs = new WP_Filesystem_Direct( '' ); + $tmpfile = download_url( $from ); - if ($tmpfile instanceof WP_Error) { + if ( $tmpfile instanceof WP_Error ) { // Issue downloading the file. return false; } - $fs->copy($tmpfile, $to); - $fs->delete($tmpfile); + $fs->copy( $tmpfile, $to ); + $fs->delete( $tmpfile ); return true; } @@ -771,7 +793,7 @@ function fs_download_image($from, $to) /* General Utilities --------------------------------------------------------------------------------------------*/ - if (! function_exists('fs_sort_by_priority')) { + if ( ! function_exists( 'fs_sort_by_priority' ) ) { /** * Sorts an array by the value of the priority key. * @@ -783,22 +805,21 @@ function fs_download_image($from, $to) * * @return int */ - function fs_sort_by_priority($a, $b) - { + function fs_sort_by_priority( $a, $b ) { // If b has a priority and a does not, b wins. - if (! isset($a['priority']) && isset($b['priority'])) { + if ( ! isset( $a['priority'] ) && isset( $b['priority'] ) ) { return 1; } // If b has a priority and a does not, b wins. - elseif (isset($a['priority']) && ! isset($b['priority'])) { + elseif ( isset( $a['priority'] ) && ! isset( $b['priority'] ) ) { return - 1; - } // If neither has a priority or both priorities are equal its a tie. - elseif ((! isset($a['priority']) && ! isset($b['priority'])) || $a['priority'] === $b['priority']) { + } // If neither has a priority or both priorities are equal it's a tie. + elseif ( ( ! isset( $a['priority'] ) && ! isset( $b['priority'] ) ) || $a['priority'] === $b['priority'] ) { return 0; } // If both have priority return the winner. - return ($a['priority'] < $b['priority']) ? - 1 : 1; + return ( $a['priority'] < $b['priority'] ) ? - 1 : 1; } } @@ -806,7 +827,13 @@ function fs_sort_by_priority($a, $b) #region Localization #-------------------------------------------------------------------------------- - if (! function_exists('fs_text')) { + global $fs_text_overrides; + + if ( ! isset( $fs_text_overrides ) ) { + $fs_text_overrides = array(); + } + + if ( ! function_exists( 'fs_text' ) ) { /** * Retrieve a translated text by key. * @@ -818,41 +845,22 @@ function fs_sort_by_priority($a, $b) * * @return string * - * @global $fs_text , $fs_text_overrides + * @global $fs_text_overrides */ - function fs_text($key, $slug = 'freemius') - { - global $fs_text, - $fs_module_info_text, - $fs_text_overrides; - - if (isset($fs_text_overrides[ $slug ])) { - if (isset($fs_text_overrides[ $slug ][ $key ])) { + function fs_text( $key, $slug = 'freemius' ) { + global $fs_text_overrides; + + if ( isset( $fs_text_overrides[ $slug ] ) ) { + if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { return $fs_text_overrides[ $slug ][ $key ]; } - $lower_key = strtolower($key); - if (isset($fs_text_overrides[ $slug ][ $lower_key ])) { + $lower_key = strtolower( $key ); + if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { return $fs_text_overrides[ $slug ][ $lower_key ]; } } - if (! isset($fs_text)) { - $dir = defined('WP_FS__DIR_INCLUDES') ? - WP_FS__DIR_INCLUDES : - dirname(__FILE__); - - require_once $dir . '/i18n.php'; - } - - if (isset($fs_text[ $key ])) { - return $fs_text[ $key ]; - } - - if (isset($fs_module_info_text[ $key ])) { - return $fs_module_info_text[ $key ]; - } - return $key; } @@ -873,14 +881,13 @@ function fs_text($key, $slug = 'freemius') * * @global $fs_text_overrides */ - function _fs_text_x_inline($text, $context, $key = '', $slug = 'freemius') - { - list($text, $text_domain) = fs_text_and_domain($text, $key, $slug); + function _fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug ); // Avoid misleading Theme Check warning. $fn = 'translate_with_gettext_context'; - return $fn($text, $context, $text_domain); + return $fn( $text, $context, $text_domain ); } #endregion @@ -900,9 +907,8 @@ function _fs_text_x_inline($text, $context, $key = '', $slug = 'freemius') * * @global $fs_text_overrides */ - function fs_text_x_inline($text, $context, $key = '', $slug = 'freemius') - { - return _fs_text_x_inline($text, $context, $key, $slug); + function fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return _fs_text_x_inline( $text, $context, $key, $slug ); } /** @@ -914,9 +920,8 @@ function fs_text_x_inline($text, $context, $key = '', $slug = 'freemius') * @param string $key * @param string $slug */ - function fs_echo($key, $slug = 'freemius') - { - echo fs_text($key, $slug); + function fs_echo( $key, $slug = 'freemius' ) { + echo fs_text( $key, $slug ); } /** @@ -929,9 +934,8 @@ function fs_echo($key, $slug = 'freemius') * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ - function fs_echo_inline($text, $key = '', $slug = 'freemius') - { - echo _fs_text_inline($text, $key, $slug); + function fs_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo _fs_text_inline( $text, $key, $slug ); } /** @@ -945,13 +949,12 @@ function fs_echo_inline($text, $key = '', $slug = 'freemius') * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ - function fs_echo_x_inline($text, $context, $key = '', $slug = 'freemius') - { - echo _fs_text_x_inline($text, $context, $key, $slug); + function fs_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + echo _fs_text_x_inline( $text, $context, $key, $slug ); } } - if (! function_exists('fs_text_override')) { + if ( ! function_exists( 'fs_text_override' ) ) { /** * Get a translatable text override if exists, or `false`. * @@ -964,27 +967,26 @@ function fs_echo_x_inline($text, $context, $key = '', $slug = 'freemius') * * @return string|false */ - function fs_text_override($text, $key, $slug) - { + function fs_text_override( $text, $key, $slug ) { global $fs_text_overrides; /** * Check if string is overridden. */ - if (! isset($fs_text_overrides[ $slug ])) { + if ( ! isset( $fs_text_overrides[ $slug ] ) ) { return false; } - if (empty($key)) { - $key = strtolower(str_replace(' ', '-', $text)); + if ( empty( $key ) ) { + $key = strtolower( str_replace( ' ', '-', $text ) ); } - if (isset($fs_text_overrides[ $slug ][ $key ])) { + if ( isset( $fs_text_overrides[ $slug ][ $key ] ) ) { return $fs_text_overrides[ $slug ][ $key ]; } - $lower_key = strtolower($key); - if (isset($fs_text_overrides[ $slug ][ $lower_key ])) { + $lower_key = strtolower( $key ); + if ( isset( $fs_text_overrides[ $slug ][ $lower_key ] ) ) { return $fs_text_overrides[ $slug ][ $lower_key ]; } @@ -992,7 +994,7 @@ function fs_text_override($text, $key, $slug) } } - if (! function_exists('fs_text_and_domain')) { + if ( ! function_exists( 'fs_text_and_domain' ) ) { /** * Get a translatable text and its text domain. * @@ -1007,11 +1009,10 @@ function fs_text_override($text, $key, $slug) * * @return string[] */ - function fs_text_and_domain($text, $key, $slug) - { - $override = fs_text_override($text, $key, $slug); + function fs_text_and_domain( $text, $key, $slug ) { + $override = fs_text_override( $text, $key, $slug ); - if (false === $override) { + if ( false === $override ) { // No override, use FS text domain. $text_domain = 'freemius'; } else { @@ -1025,7 +1026,7 @@ function fs_text_and_domain($text, $key, $slug) } } - if (! function_exists('_fs_text_inline')) { + if ( ! function_exists( '_fs_text_inline' ) ) { /** * Retrieve an inline translated text by key. * @@ -1040,18 +1041,17 @@ function fs_text_and_domain($text, $key, $slug) * * @global $fs_text_overrides */ - function _fs_text_inline($text, $key = '', $slug = 'freemius') - { - list($text, $text_domain) = fs_text_and_domain($text, $key, $slug); + function _fs_text_inline( $text, $key = '', $slug = 'freemius' ) { + list( $text, $text_domain ) = fs_text_and_domain( $text, $key, $slug ); // Avoid misleading Theme Check warning. $fn = 'translate'; - return $fn($text, $text_domain); + return $fn( $text, $text_domain ); } } - if (! function_exists('fs_text_inline')) { + if ( ! function_exists( 'fs_text_inline' ) ) { /** * Retrieve an inline translated text by key. * @@ -1066,13 +1066,12 @@ function _fs_text_inline($text, $key = '', $slug = 'freemius') * * @global $fs_text_overrides */ - function fs_text_inline($text, $key = '', $slug = 'freemius') - { - return _fs_text_inline($text, $key, $slug); + function fs_text_inline( $text, $key = '', $slug = 'freemius' ) { + return _fs_text_inline( $text, $key, $slug ); } } - if (! function_exists('fs_esc_attr')) { + if ( ! function_exists( 'fs_esc_attr' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 @@ -1082,13 +1081,12 @@ function fs_text_inline($text, $key = '', $slug = 'freemius') * * @return string */ - function fs_esc_attr($key, $slug) - { - return esc_attr(fs_text($key, $slug)); + function fs_esc_attr( $key, $slug ) { + return esc_attr( fs_text( $key, $slug ) ); } } - if (! function_exists('fs_esc_attr_inline')) { + if ( ! function_exists( 'fs_esc_attr_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1099,13 +1097,12 @@ function fs_esc_attr($key, $slug) * * @return string */ - function fs_esc_attr_inline($text, $key = '', $slug = 'freemius') - { - return esc_attr(_fs_text_inline($text, $key, $slug)); + function fs_esc_attr_inline( $text, $key = '', $slug = 'freemius' ) { + return esc_attr( _fs_text_inline( $text, $key, $slug ) ); } } - if (! function_exists('fs_esc_attr_x_inline')) { + if ( ! function_exists( 'fs_esc_attr_x_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1117,13 +1114,12 @@ function fs_esc_attr_inline($text, $key = '', $slug = 'freemius') * * @return string */ - function fs_esc_attr_x_inline($text, $context, $key = '', $slug = 'freemius') - { - return esc_attr(_fs_text_x_inline($text, $context, $key, $slug)); + function fs_esc_attr_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return esc_attr( _fs_text_x_inline( $text, $context, $key, $slug ) ); } } - if (! function_exists('fs_esc_attr_echo')) { + if ( ! function_exists( 'fs_esc_attr_echo' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 @@ -1131,13 +1127,12 @@ function fs_esc_attr_x_inline($text, $context, $key = '', $slug = 'freemius') * @param string $key * @param string $slug */ - function fs_esc_attr_echo($key, $slug) - { - echo esc_attr(fs_text($key, $slug)); + function fs_esc_attr_echo( $key, $slug ) { + echo esc_attr( fs_text( $key, $slug ) ); } } - if (! function_exists('fs_esc_attr_echo_inline')) { + if ( ! function_exists( 'fs_esc_attr_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1146,13 +1141,12 @@ function fs_esc_attr_echo($key, $slug) * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ - function fs_esc_attr_echo_inline($text, $key = '', $slug = 'freemius') - { - echo esc_attr(_fs_text_inline($text, $key, $slug)); + function fs_esc_attr_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo esc_attr( _fs_text_inline( $text, $key, $slug ) ); } } - if (! function_exists('fs_esc_js')) { + if ( ! function_exists( 'fs_esc_js' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 @@ -1162,13 +1156,12 @@ function fs_esc_attr_echo_inline($text, $key = '', $slug = 'freemius') * * @return string */ - function fs_esc_js($key, $slug) - { - return esc_js(fs_text($key, $slug)); + function fs_esc_js( $key, $slug ) { + return esc_js( fs_text( $key, $slug ) ); } } - if (! function_exists('fs_esc_js_inline')) { + if ( ! function_exists( 'fs_esc_js_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1179,13 +1172,12 @@ function fs_esc_js($key, $slug) * * @return string */ - function fs_esc_js_inline($text, $key = '', $slug = 'freemius') - { - return esc_js(_fs_text_inline($text, $key, $slug)); + function fs_esc_js_inline( $text, $key = '', $slug = 'freemius' ) { + return esc_js( _fs_text_inline( $text, $key, $slug ) ); } } - if (! function_exists('fs_esc_js_x_inline')) { + if ( ! function_exists( 'fs_esc_js_x_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1197,13 +1189,12 @@ function fs_esc_js_inline($text, $key = '', $slug = 'freemius') * * @return string */ - function fs_esc_js_x_inline($text, $context, $key = '', $slug = 'freemius') - { - return esc_js(_fs_text_x_inline($text, $context, $key, $slug)); + function fs_esc_js_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) ); } } - if (! function_exists('fs_esc_js_echo_x_inline')) { + if ( ! function_exists( 'fs_esc_js_echo_x_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1213,15 +1204,14 @@ function fs_esc_js_x_inline($text, $context, $key = '', $slug = 'freemius') * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * - * @return string + * @return void */ - function fs_esc_js_echo_x_inline($text, $context, $key = '', $slug = 'freemius') - { - echo esc_js(_fs_text_x_inline($text, $context, $key, $slug)); + function fs_esc_js_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + echo esc_js( _fs_text_x_inline( $text, $context, $key, $slug ) ); } } - if (! function_exists('fs_esc_js_echo')) { + if ( ! function_exists( 'fs_esc_js_echo' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 @@ -1229,13 +1219,12 @@ function fs_esc_js_echo_x_inline($text, $context, $key = '', $slug = 'freemius') * @param string $key * @param string $slug */ - function fs_esc_js_echo($key, $slug) - { - echo esc_js(fs_text($key, $slug)); + function fs_esc_js_echo( $key, $slug ) { + echo esc_js( fs_text( $key, $slug ) ); } } - if (! function_exists('fs_esc_js_echo_inline')) { + if ( ! function_exists( 'fs_esc_js_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1244,13 +1233,12 @@ function fs_esc_js_echo($key, $slug) * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ - function fs_esc_js_echo_inline($text, $key = '', $slug = 'freemius') - { - echo esc_js(_fs_text_inline($text, $key, $slug)); + function fs_esc_js_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo esc_js( _fs_text_inline( $text, $key, $slug ) ); } } - if (! function_exists('fs_json_encode_echo')) { + if ( ! function_exists( 'fs_json_encode_echo' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 @@ -1258,13 +1246,12 @@ function fs_esc_js_echo_inline($text, $key = '', $slug = 'freemius') * @param string $key * @param string $slug */ - function fs_json_encode_echo($key, $slug) - { - echo json_encode(fs_text($key, $slug)); + function fs_json_encode_echo( $key, $slug ) { + echo json_encode( fs_text( $key, $slug ) ); } } - if (! function_exists('fs_json_encode_echo_inline')) { + if ( ! function_exists( 'fs_json_encode_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1273,13 +1260,12 @@ function fs_json_encode_echo($key, $slug) * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ - function fs_json_encode_echo_inline($text, $key = '', $slug = 'freemius') - { - echo json_encode(_fs_text_inline($text, $key, $slug)); + function fs_json_encode_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo json_encode( _fs_text_inline( $text, $key, $slug ) ); } } - if (! function_exists('fs_esc_html')) { + if ( ! function_exists( 'fs_esc_html' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 @@ -1289,13 +1275,12 @@ function fs_json_encode_echo_inline($text, $key = '', $slug = 'freemius') * * @return string */ - function fs_esc_html($key, $slug) - { - return esc_html(fs_text($key, $slug)); + function fs_esc_html( $key, $slug ) { + return esc_html( fs_text( $key, $slug ) ); } } - if (! function_exists('fs_esc_html_inline')) { + if ( ! function_exists( 'fs_esc_html_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1306,13 +1291,12 @@ function fs_esc_html($key, $slug) * * @return string */ - function fs_esc_html_inline($text, $key = '', $slug = 'freemius') - { - return esc_html(_fs_text_inline($text, $key, $slug)); + function fs_esc_html_inline( $text, $key = '', $slug = 'freemius' ) { + return esc_html( _fs_text_inline( $text, $key, $slug ) ); } } - if (! function_exists('fs_esc_html_x_inline')) { + if ( ! function_exists( 'fs_esc_html_x_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1324,13 +1308,12 @@ function fs_esc_html_inline($text, $key = '', $slug = 'freemius') * * @return string */ - function fs_esc_html_x_inline($text, $context, $key = '', $slug = 'freemius') - { - return esc_html(_fs_text_x_inline($text, $context, $key, $slug)); + function fs_esc_html_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + return esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) ); } } - if (! function_exists('fs_esc_html_echo_x_inline')) { + if ( ! function_exists( 'fs_esc_html_echo_x_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1340,13 +1323,12 @@ function fs_esc_html_x_inline($text, $context, $key = '', $slug = 'freemius') * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ - function fs_esc_html_echo_x_inline($text, $context, $key = '', $slug = 'freemius') - { - echo esc_html(_fs_text_x_inline($text, $context, $key, $slug)); + function fs_esc_html_echo_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { + echo esc_html( _fs_text_x_inline( $text, $context, $key, $slug ) ); } } - if (! function_exists('fs_esc_html_echo')) { + if ( ! function_exists( 'fs_esc_html_echo' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 @@ -1354,13 +1336,12 @@ function fs_esc_html_echo_x_inline($text, $context, $key = '', $slug = 'freemius * @param string $key * @param string $slug */ - function fs_esc_html_echo($key, $slug) - { - echo esc_html(fs_text($key, $slug)); + function fs_esc_html_echo( $key, $slug ) { + echo esc_html( fs_text( $key, $slug ) ); } } - if (! function_exists('fs_esc_html_echo_inline')) { + if ( ! function_exists( 'fs_esc_html_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 @@ -1369,13 +1350,12 @@ function fs_esc_html_echo($key, $slug) * @param string $key String key for overrides. * @param string $slug Module slug for overrides. */ - function fs_esc_html_echo_inline($text, $key = '', $slug = 'freemius') - { - echo esc_html(_fs_text_inline($text, $key, $slug)); + function fs_esc_html_echo_inline( $text, $key = '', $slug = 'freemius' ) { + echo esc_html( _fs_text_inline( $text, $key, $slug ) ); } } - if (! function_exists('fs_override_i18n')) { + if ( ! function_exists( 'fs_override_i18n' ) ) { /** * Override default i18n text phrases. * @@ -1387,15 +1367,14 @@ function fs_esc_html_echo_inline($text, $key = '', $slug = 'freemius') * * @global $fs_text_overrides */ - function fs_override_i18n(array $key_value, $slug = 'freemius') - { + function fs_override_i18n( array $key_value, $slug = 'freemius' ) { global $fs_text_overrides; - if (! isset($fs_text_overrides[ $slug ])) { + if ( ! isset( $fs_text_overrides[ $slug ] ) ) { $fs_text_overrides[ $slug ] = array(); } - foreach ($key_value as $key => $value) { + foreach ( $key_value as $key => $value ) { $fs_text_overrides[ $slug ][ $key ] = $value; } } @@ -1407,21 +1386,20 @@ function fs_override_i18n(array $key_value, $slug = 'freemius') #region Multisite Network #-------------------------------------------------------------------------------- - if (! function_exists('fs_is_plugin_uninstall')) { + if ( ! function_exists( 'fs_is_plugin_uninstall' ) ) { /** * @author Vova Feldman (@svovaf) * @since 2.0.0 */ - function fs_is_plugin_uninstall() - { + function fs_is_plugin_uninstall() { return ( - defined('WP_UNINSTALL_PLUGIN') || - (0 < did_action('update_option_uninstall_plugins')) + defined( 'WP_UNINSTALL_PLUGIN' ) || + ( 0 < did_action( 'pre_uninstall_plugin' ) ) ); } } - if (! function_exists('fs_is_network_admin')) { + if ( ! function_exists( 'fs_is_network_admin' ) ) { /** * Unlike is_network_admin(), this one will also work properly when * the context execution is WP AJAX handler, and during plugin @@ -1430,16 +1408,15 @@ function fs_is_plugin_uninstall() * @author Vova Feldman (@svovaf) * @since 2.0.0 */ - function fs_is_network_admin() - { + function fs_is_network_admin() { return ( WP_FS__IS_NETWORK_ADMIN || - (is_multisite() && fs_is_plugin_uninstall()) + ( is_multisite() && fs_is_plugin_uninstall() ) ); } } - if (! function_exists('fs_is_blog_admin')) { + if ( ! function_exists( 'fs_is_blog_admin' ) ) { /** * Unlike is_blog_admin(), this one will also work properly when * the context execution is WP AJAX handler, and during plugin @@ -1448,18 +1425,17 @@ function fs_is_network_admin() * @author Vova Feldman (@svovaf) * @since 2.0.0 */ - function fs_is_blog_admin() - { + function fs_is_blog_admin() { return ( WP_FS__IS_BLOG_ADMIN || - (! is_multisite() && fs_is_plugin_uninstall()) + ( ! is_multisite() && fs_is_plugin_uninstall() ) ); } } #endregion - if (! function_exists('fs_apply_filter')) { + if ( ! function_exists( 'fs_apply_filter' ) ) { /** * Apply filter for specific plugin. * @@ -1474,16 +1450,12 @@ function fs_is_blog_admin() * * @uses apply_filters() */ - function fs_apply_filter($module_unique_affix, $tag, $value) - { + function fs_apply_filter( $module_unique_affix, $tag, $value ) { $args = func_get_args(); - return call_user_func_array( - 'apply_filters', - array_merge( + return call_user_func_array( 'apply_filters', array_merge( array( "fs_{$tag}_{$module_unique_affix}" ), - array_slice($args, 2) - ) + array_slice( $args, 2 ) ) ); } - } + } \ No newline at end of file diff --git a/freemius/includes/fs-essential-functions.php b/freemius/includes/fs-essential-functions.php index 37a901a..545689c 100644 --- a/freemius/includes/fs-essential-functions.php +++ b/freemius/includes/fs-essential-functions.php @@ -153,89 +153,11 @@ function fs_kses_no_null($string) #endregion Core Redirect (copied from BuddyPress) ----------------------------------------- - if (! function_exists('__fs')) { - global $fs_text_overrides; - - if (! isset($fs_text_overrides)) { - $fs_text_overrides = array(); - } - - /** - * Retrieve a translated text by key. - * - * @deprecated Use `fs_text()` instead since methods starting with `__` trigger warnings in Php 7. - * @todo Remove this method in the future. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - * - * @param string $key - * @param string $slug - * - * @return string - * - * @global $fs_text, $fs_text_overrides - */ - function __fs($key, $slug = 'freemius') - { - _deprecated_function(__FUNCTION__, '2.0.0', 'fs_text()'); - - global $fs_text, - $fs_module_info_text, - $fs_text_overrides; - - if (isset($fs_text_overrides[ $slug ])) { - if (isset($fs_text_overrides[ $slug ][ $key ])) { - return $fs_text_overrides[ $slug ][ $key ]; - } - - $lower_key = strtolower($key); - if (isset($fs_text_overrides[ $slug ][ $lower_key ])) { - return $fs_text_overrides[ $slug ][ $lower_key ]; - } - } - - if (! isset($fs_text)) { - $dir = defined('WP_FS__DIR_INCLUDES') ? - WP_FS__DIR_INCLUDES : - dirname(__FILE__); - - require_once $dir . '/i18n.php'; - } - - if (isset($fs_text[ $key ])) { - return $fs_text[ $key ]; - } - - if (isset($fs_module_info_text[ $key ])) { - return $fs_module_info_text[ $key ]; - } - - return $key; - } - - /** - * Output a translated text by key. - * - * @deprecated Use `fs_echo()` instead for consistency with `fs_text()`. - * - * @todo Remove this method in the future. - * - * @author Vova Feldman (@svovaf) - * @since 1.1.4 - * - * @param string $key - * @param string $slug - */ - function _efs($key, $slug = 'freemius') - { - fs_echo($key, $slug); - } - } - if (! function_exists('fs_get_ip')) { /** - * Get client IP. + * Get server IP. + * + * @since 2.5.1 This method returns the server IP. * * @author Vova Feldman (@svovaf) * @since 1.1.2 @@ -244,23 +166,9 @@ function _efs($key, $slug = 'freemius') */ function fs_get_ip() { - $fields = array( - 'HTTP_CF_CONNECTING_IP', - 'HTTP_CLIENT_IP', - 'HTTP_X_FORWARDED_FOR', - 'HTTP_X_FORWARDED', - 'HTTP_FORWARDED_FOR', - 'HTTP_FORWARDED', - 'REMOTE_ADDR', - ); - - foreach ($fields as $ip_field) { - if (! empty($_SERVER[ $ip_field ])) { - return $_SERVER[ $ip_field ]; - } - } - - return null; + return empty($_SERVER[ 'SERVER_ADDR' ]) ? + null : + $_SERVER[ 'SERVER_ADDR' ]; } } @@ -492,7 +400,7 @@ function fs_fallback_to_newest_active_sdk() if (! $is_module_active || ! $is_sdk_exists) { unset($fs_active_plugins->plugins[ $sdk_relative_path ]); - // No need to store the data since it will be stored in fs_update_sdk_newest_version() + // No need to store the data since it will be stored in fs_update_sdk_newest_version() // or explicitly with update_option(). } else { $newest_sdk_data = $data; diff --git a/freemius/includes/fs-html-escaping-functions.php b/freemius/includes/fs-html-escaping-functions.php new file mode 100644 index 0000000..7d284c4 --- /dev/null +++ b/freemius/includes/fs-html-escaping-functions.php @@ -0,0 +1,128 @@ + true, + 'style' => true, + 'data-*' => true, + ); + + return array( + 'a' => array_merge( + $common_attributes, + array( + 'href' => true, + 'title' => true, + 'target' => true, + 'rel' => true, + ) + ), + 'img' => array_merge( + $common_attributes, + array( + 'src' => true, + 'alt' => true, + 'title' => true, + 'width' => true, + 'height' => true, + ) + ), + 'br' => $common_attributes, + 'em' => $common_attributes, + 'small' => $common_attributes, + 'strong' => $common_attributes, + 'u' => $common_attributes, + 'b' => $common_attributes, + 'hr' => $common_attributes, + 'span' => $common_attributes, + 'p' => $common_attributes, + 'div' => $common_attributes, + 'ul' => $common_attributes, + 'li' => $common_attributes, + 'ol' => $common_attributes, + 'h1' => $common_attributes, + 'h2' => $common_attributes, + 'h3' => $common_attributes, + 'h4' => $common_attributes, + 'h5' => $common_attributes, + 'h6' => $common_attributes, + 'button' => $common_attributes, + 'sup' => $common_attributes, + 'sub' => $common_attributes, + 'nobr' => $common_attributes, + ); + } + } + + if (! function_exists('fs_html_get_classname')) { + /** + * Gets an HTML class attribute value. + * + * @param string|string[] $classes + * + * @return string + */ + function fs_html_get_classname($classes) + { + if (is_array($classes)) { + $classes = implode(' ', $classes); + } + + return esc_attr($classes); + } + } + + if (! function_exists('fs_html_get_attributes')) { + /** + * Gets a properly escaped HTML attributes string from an associative array. + * + * @param array $attributes A key/value pair array of attributes. + * + * @return string + */ + function fs_html_get_attributes($attributes) + { + $attribute_string = ''; + + foreach ($attributes as $key => $value) { + $attribute_string .= sprintf( + ' %1$s="%2$s"', + esc_attr($key), + esc_attr($value) + ); + } + + return $attribute_string; + } + } + + if (! function_exists('fs_html_get_sanitized_html')) { + /** + * Get sanitized HTML for template files. + * + * @param string $raw_html + * + * @return string + * @since 2.5.10 + */ + function fs_html_get_sanitized_html($raw_html) + { + return wp_kses($raw_html, fs_html_get_allowed_kses_list()); + } + } diff --git a/freemius/includes/fs-plugin-info-dialog.php b/freemius/includes/fs-plugin-info-dialog.php index 29f328b..3fbd113 100644 --- a/freemius/includes/fs-plugin-info-dialog.php +++ b/freemius/includes/fs-plugin-info-dialog.php @@ -6,7 +6,7 @@ * @since 1.0.6 */ - if (! defined('ABSPATH')) { + if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -16,8 +16,7 @@ * @author Vova Feldman (@svovaf) * @since 1.1.7 */ - class FS_Plugin_Info_Dialog - { + class FS_Plugin_Info_Dialog { /** * @since 1.1.7 * @@ -55,25 +54,22 @@ class FS_Plugin_Info_Dialog */ private $status; - public function __construct(Freemius $fs) - { + function __construct( Freemius $fs ) { $this->_fs = $fs; - $this->_logger = FS_Logger::get_logger(WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK); + $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); // Remove default plugin information action. - remove_all_actions('install_plugins_pre_plugin-information'); + remove_all_actions( 'install_plugins_pre_plugin-information' ); // Override action with custom plugins function for add-ons. - add_action('install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' )); + add_action( 'install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' ) ); // Override request for plugin information for Add-ons. add_filter( 'fs_plugins_api', array( &$this, '_get_addon_info_filter' ), - WP_FS__DEFAULT_PRIORITY, - 3 - ); + WP_FS__DEFAULT_PRIORITY, 3 ); } /** @@ -88,27 +84,26 @@ public function __construct(Freemius $fs) * * @return array|null */ - public function _get_addon_info_filter($data, $action = '', $args = null) - { + function _get_addon_info_filter( $data, $action = '', $args = null ) { $this->_logger->entrance(); - $parent_plugin_id = fs_request_get('parent_plugin_id', $this->_fs->get_id()); + $parent_plugin_id = fs_request_get( 'parent_plugin_id', $this->_fs->get_id() ); - if ($this->_fs->get_id() != $parent_plugin_id || - ('plugin_information' !== $action) || - ! isset($args->slug) + if ( $this->_fs->get_id() != $parent_plugin_id || + ( 'plugin_information' !== $action ) || + ! isset( $args->slug ) ) { return $data; } // Find add-on by slug. - $selected_addon = $this->_fs->get_addon_by_slug($args->slug, WP_FS__DEV_MODE); + $selected_addon = $this->_fs->get_addon_by_slug( $args->slug, WP_FS__DEV_MODE ); - if (false === $selected_addon) { + if ( false === $selected_addon ) { return $data; } - if (! isset($selected_addon->info)) { + if ( ! isset( $selected_addon->info ) ) { // Setup some default info. $selected_addon->info = new stdClass(); $selected_addon->info->selling_point_0 = 'Selling Point 1'; @@ -117,7 +112,7 @@ public function _get_addon_info_filter($data, $action = '', $args = null) $selected_addon->info->description = '

          Tell your users all about your add-on

          '; } - fs_enqueue_local_style('fs_addons', '/admin/add-ons.css'); + fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' ); $data = $args; @@ -129,33 +124,33 @@ public function _get_addon_info_filter($data, $action = '', $args = null) $has_features = false; $plans = false; - $result = $this->_fs->get_api_plugin_scope()->get($this->_fs->add_show_pending("/addons/{$selected_addon->id}/pricing.json?type=visible")); + $result = $this->_fs->get_api_plugin_scope()->get( $this->_fs->add_show_pending( "/addons/{$selected_addon->id}/pricing.json?type=visible" ) ); - if (! isset($result->error)) { + if ( ! isset( $result->error ) ) { $plans = $result->plans; - if (is_array($plans)) { - for ($i = 0, $len = count($plans); $i < $len; $i ++) { - $pricing = isset($plans[ $i ]->pricing) ? $plans[ $i ]->pricing : null; - $features = isset($plans[ $i ]->features) ? $plans[ $i ]->features : null; + if ( is_array( $plans ) ) { + for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { + $pricing = isset( $plans[ $i ]->pricing ) ? $plans[ $i ]->pricing : null; + $features = isset( $plans[ $i ]->features ) ? $plans[ $i ]->features : null; - $plans[ $i ] = new FS_Plugin_Plan($plans[ $i ]); + $plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] ); $plan = $plans[ $i ]; - if ('free' == $plans[ $i ]->name || - ! is_array($pricing) || - 0 == count($pricing) + if ( 'free' == $plans[ $i ]->name || + ! is_array( $pricing ) || + 0 == count( $pricing ) ) { $has_free_plan = true; } - if (is_array($pricing) && 0 < count($pricing)) { + if ( is_array( $pricing ) && 0 < count( $pricing ) ) { $filtered_pricing = array(); - foreach ($pricing as $prices) { - $prices = new FS_Pricing($prices); + foreach ( $pricing as $prices ) { + $prices = new FS_Pricing( $prices ); - if (! $prices->is_usd()) { + if ( ! $prices->is_usd() ) { /** * Skip non-USD pricing. * @@ -165,15 +160,15 @@ public function _get_addon_info_filter($data, $action = '', $args = null) continue; } - if (($prices->has_monthly() && $prices->monthly_price > 1.0) || - ($prices->has_annual() && $prices->annual_price > 1.0) || - ($prices->has_lifetime() && $prices->lifetime_price > 1.0) + if ( ( $prices->has_monthly() && $prices->monthly_price > 1.0 ) || + ( $prices->has_annual() && $prices->annual_price > 1.0 ) || + ( $prices->has_lifetime() && $prices->lifetime_price > 1.0 ) ) { $filtered_pricing[] = $prices; } } - if (! empty($filtered_pricing)) { + if ( ! empty( $filtered_pricing ) ) { $has_paid_plan = true; $plan->pricing = $filtered_pricing; @@ -182,7 +177,7 @@ public function _get_addon_info_filter($data, $action = '', $args = null) } } - if (is_array($features) && 0 < count($features)) { + if ( is_array( $features ) && 0 < count( $features ) ) { $plan->features = $features; $has_features = true; @@ -193,10 +188,9 @@ public function _get_addon_info_filter($data, $action = '', $args = null) $latest = null; - if (! $has_paid_plan && $selected_addon->is_wp_org_compliant) { + if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) { $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository( - 'plugin_information', - (object) array( + 'plugin_information', (object) array( 'slug' => $selected_addon->slug, 'is_ssl' => is_ssl(), 'fields' => array( @@ -205,10 +199,9 @@ public function _get_addon_info_filter($data, $action = '', $args = null) 'downloaded' => false, 'active_installs' => true ) - ) - ); + ) ); - if (! empty($repo_data)) { + if ( ! empty( $repo_data ) ) { $data = $repo_data; $data->wp_org_missing = false; } else { @@ -219,24 +212,24 @@ public function _get_addon_info_filter($data, $action = '', $args = null) $data->wp_org_missing = true; } - $data->fs_missing = (! $has_free_plan || $data->wp_org_missing); + $data->fs_missing = ( ! $has_free_plan || $data->wp_org_missing ); } else { $data->has_purchased_license = false; $data->wp_org_missing = false; $fs_addon = null; $current_addon_version = false; - if ($this->_fs->is_addon_activated($selected_addon->id)) { - $fs_addon = $this->_fs->get_addon_instance($selected_addon->id); + if ( $this->_fs->is_addon_activated( $selected_addon->id ) ) { + $fs_addon = $this->_fs->get_addon_instance( $selected_addon->id ); $current_addon_version = $fs_addon->get_plugin_version(); - } elseif ($this->_fs->is_addon_installed($selected_addon->id)) { + } else if ( $this->_fs->is_addon_installed( $selected_addon->id ) ) { $addon_plugin_data = get_plugin_data( - (WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename($selected_addon->id)), + ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $selected_addon->id ) ), false, false ); - if (! empty($addon_plugin_data)) { + if ( ! empty( $addon_plugin_data ) ) { $current_addon_version = $addon_plugin_data['Version']; } } @@ -249,22 +242,22 @@ public function _get_addon_info_filter($data, $action = '', $args = null) $current_addon_version ); - if ($has_paid_plan) { - $blog_id = fs_request_get('fs_blog_id'); - $has_valid_blog_id = is_numeric($blog_id); + if ( $has_paid_plan ) { + $blog_id = fs_request_get( 'fs_blog_id' ); + $has_valid_blog_id = is_numeric( $blog_id ); - if ($has_valid_blog_id) { - switch_to_blog($blog_id); + if ( $has_valid_blog_id ) { + switch_to_blog( $blog_id ); } $data->checkout_link = $this->_fs->checkout_url( WP_FS__PERIOD_ANNUALLY, false, array(), - ($has_valid_blog_id ? false : null) + ( $has_valid_blog_id ? false : null ) ); - if ($has_valid_blog_id) { + if ( $has_valid_blog_id ) { restore_current_blog(); } } @@ -275,24 +268,24 @@ public function _get_addon_info_filter($data, $action = '', $args = null) * @author Leo Fajardo (@leorw) * @since 2.4.1 */ - if (is_object($fs_addon)) { + if ( is_object( $fs_addon ) ) { $data->has_purchased_license = $fs_addon->has_active_valid_license(); } else { $account_addons = $this->_fs->get_account_addons(); - if (! empty($account_addons) && in_array($selected_addon->id, $account_addons)) { + if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) { $data->has_purchased_license = true; } } - if ($has_free_plan || $data->has_purchased_license) { - $data->download_link = $this->_fs->_get_latest_download_local_url($selected_addon->id); + if ( $has_free_plan || $data->has_purchased_license ) { + $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id ); } $data->fs_missing = ( false === $latest && ( - empty($selected_addon->premium_releases_count) || - ! ($selected_addon->premium_releases_count > 0) + empty( $selected_addon->premium_releases_count ) || + ! ( $selected_addon->premium_releases_count > 0 ) ) ); @@ -300,26 +293,27 @@ public function _get_addon_info_filter($data, $action = '', $args = null) $plugin_local_data = $this->_fs->get_plugin_data(); $data->author = $plugin_local_data['Author']; - if (! empty($selected_addon->info->banner_url)) { + if ( ! empty( $selected_addon->info->banner_url ) ) { $data->banners = array( 'low' => $selected_addon->info->banner_url, ); } - if (! empty($selected_addon->info->screenshots)) { + if ( ! empty( $selected_addon->info->screenshots ) ) { $view_vars = array( 'screenshots' => $selected_addon->info->screenshots, 'plugin' => $selected_addon, ); - $data->sections['screenshots'] = fs_get_template('/plugin-info/screenshots.php', $view_vars); + $data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars ); } - if (is_object($latest)) { + if ( is_object( $latest ) ) { $data->version = $latest->version; $data->last_updated = $latest->created; $data->requires = $latest->requires_platform_version; + $data->requires_php = $latest->requires_programming_language_version; $data->tested = $latest->tested_up_to_version; - } elseif (! empty($current_addon_version)) { + } else if ( ! empty( $current_addon_version ) ) { $data->version = $current_addon_version; } else { // Add dummy version. @@ -332,27 +326,27 @@ public function _get_addon_info_filter($data, $action = '', $args = null) $data->name = $selected_addon->title; $view_vars = array( 'plugin' => $selected_addon ); - if (is_object($latest) && isset($latest->readme) && is_object($latest->readme)) { + if ( is_object( $latest ) && isset( $latest->readme ) && is_object( $latest->readme ) ) { $latest_version_readme_data = $latest->readme; - if (isset($latest_version_readme_data->sections)) { + if ( isset( $latest_version_readme_data->sections ) ) { $data->sections = (array) $latest_version_readme_data->sections; } else { $data->sections = array(); } } - $data->sections['description'] = fs_get_template('/plugin-info/description.php', $view_vars); + $data->sections['description'] = fs_get_template( '/plugin-info/description.php', $view_vars ); - if ($has_pricing) { + if ( $has_pricing ) { // Add plans to data. $data->plans = $plans; - if ($has_features) { + if ( $has_features ) { $view_vars = array( 'plans' => $plans, 'plugin' => $selected_addon, ); - $data->sections['features'] = fs_get_template('/plugin-info/features.php', $view_vars); + $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars ); } } @@ -363,7 +357,7 @@ public function _get_addon_info_filter($data, $action = '', $args = null) $data->premium_slug = $selected_addon->premium_slug; $data->addon_id = $selected_addon->id; - if (! isset($data->has_purchased_license)) { + if ( ! isset( $data->has_purchased_license ) ) { $data->has_purchased_license = false; } @@ -378,30 +372,29 @@ public function _get_addon_info_filter($data, $action = '', $args = null) * * @return string */ - private function get_billing_cycle(FS_Plugin_Plan $plan) - { + private function get_billing_cycle( FS_Plugin_Plan $plan ) { $billing_cycle = null; - if (1 === count($plan->pricing) && 1 == $plan->pricing[0]->licenses) { + if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) { $pricing = $plan->pricing[0]; - if (isset($pricing->annual_price)) { + if ( isset( $pricing->annual_price ) ) { $billing_cycle = 'annual'; - } elseif (isset($pricing->monthly_price)) { + } else if ( isset( $pricing->monthly_price ) ) { $billing_cycle = 'monthly'; - } elseif (isset($pricing->lifetime_price)) { + } else if ( isset( $pricing->lifetime_price ) ) { $billing_cycle = 'lifetime'; } } else { - foreach ($plan->pricing as $pricing) { - if (isset($pricing->annual_price)) { + foreach ( $plan->pricing as $pricing ) { + if ( isset( $pricing->annual_price ) ) { $billing_cycle = 'annual'; - } elseif (isset($pricing->monthly_price)) { + } else if ( isset( $pricing->monthly_price ) ) { $billing_cycle = 'monthly'; - } elseif (isset($pricing->lifetime_price)) { + } else if ( isset( $pricing->lifetime_price ) ) { $billing_cycle = 'lifetime'; } - if (! is_null($billing_cycle)) { + if ( ! is_null( $billing_cycle ) ) { break; } } @@ -419,14 +412,13 @@ private function get_billing_cycle(FS_Plugin_Plan $plan) * * @return float|null|string */ - private function get_price_tag(FS_Plugin_Plan $plan, FS_Pricing $pricing) - { + private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) { $price_tag = ''; - if (isset($pricing->annual_price)) { - $price_tag = $pricing->annual_price . ($plan->is_block_features ? ' / year' : ''); - } elseif (isset($pricing->monthly_price)) { + if ( isset( $pricing->annual_price ) ) { + $price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' ); + } else if ( isset( $pricing->monthly_price ) ) { $price_tag = $pricing->monthly_price . ' / mo'; - } elseif (isset($pricing->lifetime_price)) { + } else if ( isset( $pricing->lifetime_price ) ) { $price_tag = $pricing->lifetime_price; } @@ -442,16 +434,15 @@ private function get_price_tag(FS_Plugin_Plan $plan, FS_Pricing $pricing) * * @return string */ - private function get_actions_dropdown($api, $plan = null) - { - $this->actions = isset($this->actions) ? + private function get_actions_dropdown( $api, $plan = null ) { + $this->actions = isset( $this->actions ) ? $this->actions : - $this->get_plugin_actions($api); + $this->get_plugin_actions( $api ); $actions = $this->actions; - $checkout_cta = $this->get_checkout_cta($api, $plan); - if (! empty($checkout_cta)) { + $checkout_cta = $this->get_checkout_cta( $api, $plan ); + if ( ! empty( $checkout_cta ) ) { /** * If there's no license yet, make the checkout button the main CTA. Otherwise, make it the last item in * the actions dropdown. @@ -459,23 +450,25 @@ private function get_actions_dropdown($api, $plan = null) * @author Leo Fajardo (@leorw) * @since 2.3.0 */ - if (! $api->has_purchased_license) { - array_unshift($actions, $checkout_cta); + if ( ! $api->has_purchased_license ) { + array_unshift( $actions, $checkout_cta ); } else { $actions[] = $checkout_cta; } } - if (empty($actions)) { + if ( empty( $actions ) ) { return ''; } - $total_actions = count($actions); - if (1 === $total_actions) { + $total_actions = count( $actions ); + if ( 1 === $total_actions ) { return $actions[0]; } - ob_start(); ?> + ob_start(); + + ?>
          @@ -505,56 +498,54 @@ private function get_actions_dropdown($api, $plan = null) * * @return string */ - private function get_checkout_cta($api, $plan = null) - { - if (empty($api->checkout_link) || - ! isset($api->plans) || - ! is_array($api->plans) || - 0 == count($api->plans) + private function get_checkout_cta( $api, $plan = null ) { + if ( empty( $api->checkout_link ) || + ! isset( $api->plans ) || + ! is_array( $api->plans ) || + 0 == count( $api->plans ) ) { return ''; } - if (is_null($plan)) { - foreach ($api->plans as $p) { - if (! empty($p->pricing)) { + if ( is_null( $plan ) ) { + foreach ( $api->plans as $p ) { + if ( ! empty( $p->pricing ) ) { $plan = $p; break; } } } - $blog_id = fs_request_get('fs_blog_id'); - $has_valid_blog_id = is_numeric($blog_id); + $blog_id = fs_request_get( 'fs_blog_id' ); + $has_valid_blog_id = is_numeric( $blog_id ); - if ($has_valid_blog_id) { - switch_to_blog($blog_id); + if ( $has_valid_blog_id ) { + switch_to_blog( $blog_id ); } $addon_checkout_url = $this->_fs->addon_checkout_url( $plan->plugin_id, $plan->pricing[0]->id, - $this->get_billing_cycle($plan), + $this->get_billing_cycle( $plan ), $plan->has_trial(), - ($has_valid_blog_id ? false : null) + ( $has_valid_blog_id ? false : null ) ); - if ($has_valid_blog_id) { + if ( $has_valid_blog_id ) { restore_current_blog(); } return '' . - esc_html( - ! $plan->has_trial() ? + esc_html( ! $plan->has_trial() ? ( $api->has_purchased_license ? - fs_text_inline('Purchase More', 'purchase-more', $api->slug) : - fs_text_x_inline('Purchase', 'verb', 'purchase', $api->slug) + fs_text_inline( 'Purchase More', 'purchase-more', $api->slug ) : + fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug ) ) : sprintf( /* translators: %s: N-days trial */ - fs_text_inline('Start my free %s', 'start-free-x', $api->slug), - $this->get_trial_period($plan) + fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ), + $this->get_trial_period( $plan ) ) ) . ''; @@ -568,33 +559,32 @@ private function get_checkout_cta($api, $plan = null) * * @return string[] */ - private function get_plugin_actions($api) - { - $this->status = isset($this->status) ? + private function get_plugin_actions( $api ) { + $this->status = isset( $this->status ) ? $this->status : - install_plugin_install_status($api); + install_plugin_install_status( $api ); - $is_update_available = ('update_available' === $this->status['status']); + $is_update_available = ( 'update_available' === $this->status['status'] ); - if ($is_update_available && empty($this->status['url'])) { + if ( $is_update_available && empty( $this->status['url'] ) ) { return array(); } - $blog_id = fs_request_get('fs_blog_id'); + $blog_id = fs_request_get( 'fs_blog_id' ); - $active_plugins_directories_map = Freemius::get_active_plugins_directories_map($blog_id); + $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $blog_id ); $actions = array(); - $is_addon_activated = $this->_fs->is_addon_activated($api->slug); + $is_addon_activated = $this->_fs->is_addon_activated( $api->slug ); $fs_addon = null; $is_free_installed = null; $is_premium_installed = null; - $has_installed_version = ('install' !== $this->status['status']); + $has_installed_version = ( 'install' !== $this->status['status'] ); - if (! $api->has_paid_plan && ! $api->has_purchased_license) { + if ( ! $api->has_paid_plan && ! $api->has_purchased_license ) { /** * Free-only add-on. * @@ -603,7 +593,7 @@ private function get_plugin_actions($api) */ $is_free_installed = $has_installed_version; $is_premium_installed = false; - } elseif (! $api->has_free_plan) { + } else if ( ! $api->has_free_plan ) { /** * Premium-only add-on. * @@ -619,52 +609,52 @@ private function get_plugin_actions($api) * @author Leo Fajardo (@leorw) * @since 2.3.0 */ - if (! $has_installed_version) { + if ( ! $has_installed_version ) { $is_free_installed = false; $is_premium_installed = false; } else { $fs_addon = $is_addon_activated ? - $this->_fs->get_addon_instance($api->slug) : + $this->_fs->get_addon_instance( $api->slug ) : null; - if (is_object($fs_addon)) { - if ($fs_addon->is_premium()) { + if ( is_object( $fs_addon ) ) { + if ( $fs_addon->is_premium() ) { $is_premium_installed = true; } else { $is_free_installed = true; } } - if (is_null($is_free_installed)) { - $is_free_installed = file_exists(fs_normalize_path(WP_PLUGIN_DIR . "/{$api->slug}/{$api->slug}.php")); - if (! $is_free_installed) { + if ( is_null( $is_free_installed ) ) { + $is_free_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->slug}/{$api->slug}.php" ) ); + if ( ! $is_free_installed ) { /** * Check if there's a plugin installed in a directory named `$api->slug`. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ - $installed_plugins = get_plugins('/' . $api->slug); - $is_free_installed = (! empty($installed_plugins)); + $installed_plugins = get_plugins( '/' . $api->slug ); + $is_free_installed = ( ! empty( $installed_plugins ) ); } } - if (is_null($is_premium_installed)) { - $is_premium_installed = file_exists(fs_normalize_path(WP_PLUGIN_DIR . "/{$api->premium_slug}/{$api->slug}.php")); - if (! $is_premium_installed) { + if ( is_null( $is_premium_installed ) ) { + $is_premium_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->premium_slug}/{$api->slug}.php" ) ); + if ( ! $is_premium_installed ) { /** * Check if there's a plugin installed in a directory named `$api->premium_slug`. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ - $installed_plugins = get_plugins('/' . $api->premium_slug); - $is_premium_installed = (! empty($installed_plugins)); + $installed_plugins = get_plugins( '/' . $api->premium_slug ); + $is_premium_installed = ( ! empty( $installed_plugins ) ); } } } - $has_installed_version = ($is_free_installed || $is_premium_installed); + $has_installed_version = ( $is_free_installed || $is_premium_installed ); } $this->status['is_free_installed'] = $is_free_installed; @@ -679,12 +669,12 @@ private function get_plugin_actions($api) $can_download_premium_version = false; $can_activate_premium_version = false; - if (! $api->has_purchased_license) { - if ($api->has_free_plan) { - if ($has_installed_version) { - if ($is_update_available) { + if ( ! $api->has_purchased_license ) { + if ( $api->has_free_plan ) { + if ( $has_installed_version ) { + if ( $is_update_available ) { $can_install_free_version_update = true; - } elseif (! $is_premium_installed && ! isset($active_plugins_directories_map[ dirname($this->status['file']) ])) { + } else if ( ! $is_premium_installed && ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { $can_activate_free_version = true; } } else { @@ -700,24 +690,24 @@ private function get_plugin_actions($api) } } } else { - if (! is_object($fs_addon) && $is_addon_activated) { - $fs_addon = $this->_fs->get_addon_instance($api->slug); + if ( ! is_object( $fs_addon ) && $is_addon_activated ) { + $fs_addon = $this->_fs->get_addon_instance( $api->slug ); } $can_download_premium_version = true; - if (! isset($active_plugins_directories_map[ dirname($this->status['file']) ])) { - if ($is_premium_installed) { - $can_activate_premium_version = (! $is_addon_activated || ! $fs_addon->is_premium()); - } elseif ($is_free_installed) { - $can_activate_free_version = (! $is_addon_activated); + if ( ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { + if ( $is_premium_installed ) { + $can_activate_premium_version = ( ! $is_addon_activated || ! $fs_addon->is_premium() ); + } else if ( $is_free_installed ) { + $can_activate_free_version = ( ! $is_addon_activated ); } } - if ($this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant()) { - if ($is_update_available) { + if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() ) { + if ( $is_update_available ) { $can_install_premium_version_update = true; - } elseif (! $is_premium_installed) { + } else if ( ! $is_premium_installed ) { $can_install_premium_version = true; } } @@ -727,7 +717,7 @@ private function get_plugin_actions($api) $can_install_premium_version || $can_install_premium_version_update ) { - if (is_numeric($blog_id)) { + if ( is_numeric( $blog_id ) ) { /** * Replace the network status URL with a blog admin–based status URL if the `Add-Ons` page is loaded * from a specific blog admin page (when `fs_blog_id` is valid) in order for plugin installation/update @@ -736,7 +726,7 @@ private function get_plugin_actions($api) * @author Leo Fajardo (@leorw) * @since 2.3.0 */ - $this->status['url'] = self::get_blog_status_url($blog_id, $this->status['url'], $this->status['status']); + $this->status['url'] = self::get_blog_status_url( $blog_id, $this->status['url'], $this->status['status'] ); } /** @@ -746,24 +736,24 @@ private function get_plugin_actions($api) * @author Leo Fajardo (@leorw) * @since 2.3.0 */ - $this->status['url'] = str_replace('?', '?fs_allow_updater_and_dialog=true&', $this->status['url']); + $this->status['url'] = str_replace( '?', '?fs_allow_updater_and_dialog=true&', $this->status['url'] ); } - if ($can_install_free_version_update || $can_install_premium_version_update) { + if ( $can_install_free_version_update || $can_install_premium_version_update ) { $actions[] = $this->get_cta( - ($can_install_free_version_update ? - fs_esc_html_inline('Install Free Version Update Now', 'install-free-version-update-now', $api->slug) : - fs_esc_html_inline('Install Update Now', 'install-update-now', $api->slug)), + ( $can_install_free_version_update ? + fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) : + fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ), true, false, $this->status['url'], '_parent' ); - } elseif ($can_install_free_version || $can_install_premium_version) { + } else if ( $can_install_free_version || $can_install_premium_version ) { $actions[] = $this->get_cta( - ($can_install_free_version ? - fs_esc_html_inline('Install Free Version Now', 'install-free-version-now', $api->slug) : - fs_esc_html_inline('Install Now', 'install-now', $api->slug)), + ( $can_install_free_version ? + fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) : + fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ), true, false, $this->status['url'], @@ -774,41 +764,41 @@ private function get_plugin_actions($api) $download_latest_action = ''; if ( - ! empty($api->download_link) && - ($can_download_free_version || $can_download_premium_version) + ! empty( $api->download_link ) && + ( $can_download_free_version || $can_download_premium_version ) ) { $download_latest_action = $this->get_cta( - ($can_download_free_version ? - fs_esc_html_x_inline('Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug) : - fs_esc_html_x_inline('Download Latest', 'as download latest version', 'download-latest', $api->slug)), + ( $can_download_free_version ? + fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) : + fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ), true, false, - esc_url($api->download_link) + esc_url( $api->download_link ) ); } - if (! $can_activate_free_version && ! $can_activate_premium_version) { - if (! empty($download_latest_action)) { + if ( ! $can_activate_free_version && ! $can_activate_premium_version ) { + if ( ! empty( $download_latest_action ) ) { $actions[] = $download_latest_action; } } else { $activate_action = sprintf( '%s', - wp_nonce_url((is_numeric($blog_id) ? trailingslashit(get_admin_url($blog_id)) : '') . 'plugins.php?action=activate&plugin=' . $this->status['file'], 'activate-plugin_' . $this->status['file']), - fs_esc_attr_inline('Activate this add-on', 'activate-this-addon', $api->slug), + wp_nonce_url( ( is_numeric( $blog_id ) ? trailingslashit( get_admin_url( $blog_id ) ) : '' ) . 'plugins.php?action=activate&plugin=' . $this->status['file'], 'activate-plugin_' . $this->status['file'] ), + fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $api->slug ), $can_activate_free_version ? - fs_text_inline('Activate Free Version', 'activate-free', $api->slug) : - fs_text_inline('Activate', 'activate', $api->slug) + fs_text_inline( 'Activate Free Version', 'activate-free', $api->slug ) : + fs_text_inline( 'Activate', 'activate', $api->slug ) ); - if (! $can_download_premium_version && ! empty($download_latest_action)) { + if ( ! $can_download_premium_version && ! empty( $download_latest_action ) ) { $actions[] = $download_latest_action; $download_latest_action = ''; } - if ($can_install_premium_version || $can_install_premium_version_update) { - if ($can_download_premium_version && ! empty($download_latest_action)) { + if ( $can_install_premium_version || $can_install_premium_version_update ) { + if ( $can_download_premium_version && ! empty( $download_latest_action ) ) { $actions[] = $download_latest_action; $download_latest_action = ''; @@ -816,10 +806,10 @@ private function get_plugin_actions($api) $actions[] = $activate_action; } else { - array_unshift($actions, $activate_action); + array_unshift( $actions, $activate_action ); } - if (! empty($download_latest_action)) { + if ( ! empty ($download_latest_action ) ) { $actions[] = $download_latest_action; } } @@ -839,29 +829,28 @@ private function get_plugin_actions($api) * * @return string */ - private static function get_blog_status_url($blog_id, $network_status_url, $status) - { - if (! in_array($status, array( 'install', 'update_available' ))) { + private static function get_blog_status_url( $blog_id, $network_status_url, $status ) { + if ( ! in_array( $status, array( 'install', 'update_available' ) ) ) { return $network_status_url; } - $action = ('install' === $status) ? + $action = ( 'install' === $status ) ? 'install-plugin' : 'upgrade-plugin'; - $query = parse_url($network_status_url, PHP_URL_QUERY); - if (empty($query)) { + $query = parse_url( $network_status_url, PHP_URL_QUERY ); + if ( empty( $query ) ) { return $network_status_url; } - parse_str(html_entity_decode($query), $url_params); - if (empty($url_params) || ! isset($url_params['plugin'])) { + parse_str( html_entity_decode( $query ), $url_params ); + if ( empty( $url_params ) || ! isset( $url_params['plugin'] ) ) { return $network_status_url; } $plugin = $url_params['plugin']; - return wp_nonce_url(get_admin_url($blog_id, "update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}"); + return wp_nonce_url( get_admin_url( $blog_id,"update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}"); } /** @@ -887,23 +876,23 @@ private function get_cta( ) { $classes = array(); - if (! $is_primary) { + if ( ! $is_primary ) { $classes[] = 'left'; } else { $classes[] = 'button-primary'; $classes[] = 'right'; } - if ($is_disabled) { + if ( $is_disabled ) { $classes[] = 'disabled'; } - $rel = ('_blank' === $target) ? ' rel="noopener noreferrer"' : ''; + $rel = ( '_blank' === $target ) ? ' rel="noopener noreferrer"' : ''; return sprintf( '%s', - empty($href) ? '' : 'href="' . $href . '" target="' . $target . '"' . $rel, - implode(' ', $classes), + empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"' . $rel, + implode( ' ', $classes ), $label ); } @@ -916,11 +905,10 @@ private function get_cta( * * @return string */ - private function get_trial_period($plan) - { + private function get_trial_period( $plan ) { $trial_period = (int) $plan->trial_period; - switch ($trial_period) { + switch ( $trial_period ) { case 30: return 'month'; case 60: @@ -938,16 +926,15 @@ private function get_trial_period($plan) * @author Vova Feldman (@svovaf) * @since 1.0.6 */ - public function install_plugin_information() - { + function install_plugin_information() { global $tab; - if (empty($_REQUEST['plugin'])) { + if ( empty( $_REQUEST['plugin'] ) ) { return; } $args = array( - 'slug' => wp_unslash($_REQUEST['plugin']), + 'slug' => wp_unslash( $_REQUEST['plugin'] ), 'is_ssl' => is_ssl(), 'fields' => array( 'banners' => true, @@ -957,22 +944,22 @@ public function install_plugin_information() ) ); - if (is_array($args)) { + if ( is_array( $args ) ) { $args = (object) $args; } - if (! isset($args->per_page)) { + if ( ! isset( $args->per_page ) ) { $args->per_page = 24; } - if (! isset($args->locale)) { + if ( ! isset( $args->locale ) ) { $args->locale = get_locale(); } - $api = apply_filters('fs_plugins_api', false, 'plugin_information', $args); + $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args ); - if (is_wp_error($api)) { - wp_die($api); + if ( is_wp_error( $api ) ) { + wp_die( $api ); } $plugins_allowedtags = array( @@ -1013,57 +1000,58 @@ public function install_plugin_information() ); $plugins_section_titles = array( - 'description' => fs_text_x_inline('Description', 'Plugin installer section title', 'description', $api->slug), - 'installation' => fs_text_x_inline('Installation', 'Plugin installer section title', 'installation', $api->slug), - 'faq' => fs_text_x_inline('FAQ', 'Plugin installer section title', 'faq', $api->slug), - 'screenshots' => fs_text_inline('Screenshots', 'screenshots', $api->slug), - 'changelog' => fs_text_x_inline('Changelog', 'Plugin installer section title', 'changelog', $api->slug), - 'reviews' => fs_text_x_inline('Reviews', 'Plugin installer section title', 'reviews', $api->slug), - 'other_notes' => fs_text_x_inline('Other Notes', 'Plugin installer section title', 'other-notes', $api->slug), + 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ), + 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ), + 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ), + 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ), + 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ), + 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ), + 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ), ); // Sanitize HTML - // foreach ( (array) $api->sections as $section_name => $content ) { - // $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags ); - // } +// foreach ( (array) $api->sections as $section_name => $content ) { +// $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags ); +// } - foreach (array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key) { - if (isset($api->$key)) { - $api->$key = wp_kses($api->$key, $plugins_allowedtags); + foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) { + if ( isset( $api->$key ) ) { + $api->$key = wp_kses( $api->$key, $plugins_allowedtags ); } } // Add after $api->slug is ready. - $plugins_section_titles['features'] = fs_text_x_inline('Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug); + $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug ); - $_tab = esc_attr($tab); + $_tab = esc_attr( $tab ); - $section = isset($_REQUEST['section']) ? wp_unslash($_REQUEST['section']) : 'description'; // Default to the Description tab, Do not translate, API returns English. - if (empty($section) || ! isset($api->sections[ $section ])) { - $section_titles = array_keys((array) $api->sections); - $section = array_shift($section_titles); + $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English. + if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) { + $section_titles = array_keys( (array) $api->sections ); + $section = array_shift( $section_titles ); } - iframe_header(fs_text_inline('Plugin Install', 'plugin-install', $api->slug)); + iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) ); $_with_banner = ''; - // var_dump($api->banners); - if (! empty($api->banners) && (! empty($api->banners['low']) || ! empty($api->banners['high']))) { +// var_dump($api->banners); + if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) { $_with_banner = 'with-banner'; - $low = empty($api->banners['low']) ? $api->banners['high'] : $api->banners['low']; - $high = empty($api->banners['high']) ? $api->banners['low'] : $api->banners['high']; ?> + $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low']; + $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high']; + ?> @@ -1074,110 +1062,114 @@ public function install_plugin_information() echo "

          {$api->name}

          "; echo "
          \n"; - foreach ((array) $api->sections as $section_name => $content) { - if ('reviews' === $section_name && (empty($api->ratings) || 0 === array_sum((array) $api->ratings))) { + foreach ( (array) $api->sections as $section_name => $content ) { + if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) { continue; } - if (isset($plugins_section_titles[ $section_name ])) { + if ( isset( $plugins_section_titles[ $section_name ] ) ) { $title = $plugins_section_titles[ $section_name ]; } else { - $title = ucwords(str_replace('_', ' ', $section_name)); + $title = ucwords( str_replace( '_', ' ', $section_name ) ); } - $class = ($section_name === $section) ? ' class="current"' : ''; - $href = add_query_arg(array( 'tab' => $tab, 'section' => $section_name )); - $href = esc_url($href); - $san_section = esc_attr($section_name); - echo "\t" . esc_html($title) . "\n"; + $class = ( $section_name === $section ) ? ' class="current"' : ''; + $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) ); + $href = esc_url( $href ); + $san_section = esc_attr( $section_name ); + echo "\t" . esc_html( $title ) . "\n"; } - echo "
          \n"; ?> + echo "
          \n"; + + ?>
          - is_paid) : ?> - plans)) : ?> + is_paid ) : ?> + plans ) ) : ?>
          - plans as $plan) : ?> + plans as $plan ) : ?> pricing)) { + if ( empty( $plan->pricing ) ) { continue; } - /** - * @var FS_Plugin_Plan $plan - */ ?> + /** + * @var FS_Plugin_Plan $plan + */ + ?> pricing[0] ?> is_multi_cycle() ?> -
          -

          slug), $plan->title)) ?>

          +
          +

          slug ), $plan->title ) ) ?>

          has_annual() ?> has_monthly() ?>