PK!"a// readme.txtnu[=== User Menus - Nav Menu Visibility === Contributors: codeatlantic, danieliser Author URI: https://code-atlantic.com/ Plugin URI: https://wordpress.org/plugins/user-menus/ Donate link: https://code-atlantic.com/donate/ Tags: menu, menus, user-menu, logout, nav-menu, nav-menus, user, user-role, user-roles Requires at least: 4.6 Tested up to: 6.2.2 Stable tag: 1.3.2 Requires PHP: 5.6 Freemius: 2.5.10 License: GPLv3 or Any Later Version Show/hide menu items to logged in users, logged out users or specific user roles. Display logged in user details in menu. Add a logout link to menu. == Description == User Menus is the perfect plugin for websites that have logged in users. The plugin gives you more control over your nav menu by allowing you to apply visibility controls to menu items e.g., who can see each menu item (everyone, logged out users, logged in users, specific user roles). It also enables you to display logged in user information in the navigation menu e.g., “Hello, John Doe”. Lastly, the plugin allows you to add login, register, and logout links to your menu. = Full Feature List = User Menus allows you to do the following: * Display menu items to everyone * Display menu items to only logged out users * Display menu items to only logged in users * Display menu items to users with or without a specific user role. * Show a logged in user’s {avatar} in a menu item with a custom size option. * Show a logged in user’s {username} in a menu item * Show a logged in user’s {first_name} in a menu item * Show a logged in user’s {last_name} in a menu item * Show a logged in user’s {display_name} in a menu item * Show a logged in user’s nickname} in a menu item * Show a logged in user’s {email} in a menu item * Add a logout link to the menu (optional redirect settings) * Add a register link to the menu (optional redirect settings) * Add a login link to the menu (optional redirect settings) ** Includes a custom Menu Importer that will allow migrating User Menus data with the normal menu export/import. = Created by Code Atlantic = User Menus is built by the [Code Atlantic][codeatlantic] team. We create high-quality WordPress plugins that help you grow your WordPress sites. Check out some of our most popular plugins: * [Popup Maker][popupmaker] - #1 Popup & Marketing Plugin for WordPress * [Content Control][contentcontrol] - Restrict Access to Pages and Posts **Requires WordPress 4.6 and PHP 5.6** [codeatlantic]: https://code-atlantic.com "Code Atlantic - High Quality WordPress Plugins" [popupmaker]: https://wppopupmaker.com "#1 Popup & Marketing Plugin for WordPress" [contentcontrol]: https://wordpress.org/plugins/content-control/ "Control Who Can Access Content" == Installation == = Minimum Requirements = * WordPress 4.6 or greater * PHP version 5.6 or greater = Installation = * Install User Menus either via the WordPress.org plugin repository or by uploading the files to your server. * Activate User Menus. * Go to wp-admin > Appearance > Menus and edit your menu. If you need help getting started with User Menus, please see the [FAQs][faq page] that explain how to use the plugin. [faq page]: https://wordpress.org/plugins/user-menus/faq/ "User Menus FAQ" == Frequently Asked Questions == = How do I set up this plugin? = * To setup the plugin, go to /wp-admin/ > **Appearance** > **Menus**. * Add a **menu item** or choose an existing one to edit the User Menus settings. * To see the User Menus settings, _expand_ the **menu item** that you chose in the **Menu structure** panel. * Select **Everyone**, **Logged Out Users**, or **Logged In Users** from the **Who can see this link?** dropdown. * **Logged In Users**: The **Choose which roles can see this link** radio button is selected by default. If no roles are selected, all roles can see the menu item by default. Once a role is checked, then only checked roles can see the menu item. * **Logged In Users**: The **Choose which roles won't see this link** radio button is **not** selected by default. If no roles are selected, all roles still have visibility to the menu item by default. Once a role is checked, then only checked roles won't see the menu item. * To show a logged in user’s information in a **menu item**, make a **menu item** only visible to logged in users. Click the grey arrow button to add a user tag (username, first_name, last_name, nickname, display_name, email) to the **menu item** label. * To add a login or logout link to your menu, expand the **User Links** under the **Add menu items** panel, check **Login** or **Logout**, then click **Add to Menu**. = Where can I get support? = If you get stuck, you can ask for help in the [User Menu Plugin Forum](https://wordpress.org/support/plugin/user-menus). = Where can I report bugs or contribute to the project? = Bugs can be reported either in our support forum or preferably on the [User Menu GitHub repository](https://github.com/jungleplugins/user-menus/issues). == Screenshots == 1. Limit menu item visibility based on logged in status, user role etc. 2. Display user information such as username, first name etc in your menu text. 3. Quickly insert login/logout links & choose where users will be taken afterwards. == Changelog == = v1.3.2 - 07/19/2023 = * Security: Fixes from the freemius library, notice can be seen [here](https://freemius.com/blog/freemius-wordpress-sdk-security-vulnerability/) = v1.3.1 - 11/04/2022 = * Tweak: Patch file mismatches in freemius/core/svn in last version. = v1.3.0 - 09/13/2022 = * Tweak: Upgrade freemius sdk to v2.4.5 for PHP 8.1 compatibility. = v1.2.9 - 03/02/2022 = * Tweak: Downgrade freemius sdk to the latest stable (previously version was Release Candidate). = v1.2.8 - 03/02/2022 = * Tweak: Update freemius sdk to the latest version. = v1.2.7 - 07/21/2021 = * Fix: Bug due to variable type mismatch which caused children of protected items to be rendered. = v1.2.6 - 07/20/2021 = * Improvement: Update Freemius to 2.4.2 * Improvement: Code styling clean up. * Improvement: Compatibility with jQuery v3. = v1.2.5 - 12/31/2020 = * Improvement:Update Freemius to 2.4.1 = v1.2.4 - 08/20/2020 = * Improvement: Removed class that could cause links to be disabled with some themes. * Tweak: Update Freemius sdk to v2.4.0.1. * Fix: Compatibility issue with some sites where duplicate fields were shown in the menu editor. = v1.2.3 - 3/23/2020 = * Tweak: Add compatibility fix for WP 5.4 menu walker = v1.2.2 - 12/17/2019 = * Improvement: Login, Register & Logout menu links now hint at who they will be visible for. * Fix: Deprecation notice for sites using PHP 7.4 = v1.2.1 - 10/20/2019 = * Fix: Bug in some sites where Menu Editor Description field was not shown. = v1.2.0 - 10/10/2019 = * Feature: Added option to *show* or *hide* the menu item for chosen roles. * Feature: Added Register user link navigation menu type with optional redirect. * Improvement: Added Freemius integration to allow for future premium offerings * Tweak: Updates brand from Jungle Plugins to Code Atlantic (nothing has changed, just the name). * Tweak: Minor text and design changes. * Fix: Bug where missing data in menu items caused an error to be thrown in edge cases. = v1.1.3 = * Improvement: Corrected usage of get_avatar to ensure compatibility with 3rd party avatar plugins. = v1.1.2 = * Improvement: Made changes to the nav menu editor to make it more compatible with other plugins. = v1.1.1 = * Fix: Forgot to add new files during commit. Correcting this issue. = v1.1.0 = * Feature: Added ability to insert user avatar in menu items with size option to match your needs. * Improvement: Added accessibility enhancements to menu editor. Includes keyboard support, proper focus, tabbing & titles. * Improvement: Added proper labeling to the user code dropdown. * Tweak: Restyled user code insert elements to better resemble default WP admin. = v1.0.0 = * Initial Release PK!iYe9e9freemius/config.phpnu[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}} PK!p//%freemius/assets/css/admin/add-ons.cssnu[.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}} PK!ղ.freemius/assets/css/admin/clone-resolution.cssnu[.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} PK!X 00%freemius/assets/css/admin/connect.cssnu[.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} PK!a$freemius/assets/css/admin/common.cssnu[.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} PK!}Ǿ++#freemius/assets/css/admin/debug.cssnu[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} PK!J%freemius/assets/css/admin/plugins.cssnu[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}.wp-list-table.plugins .plugin-title span.fs-tag{display:inline-block;margin-left:5px;line-height:10px} PK!',,$freemius/assets/css/admin/optout.cssnu[.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} PK!p2WW#freemius/assets/css/admin/index.phpnu[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} PK!뀅;;/freemius/assets/css/admin/gdpr-optin-notice.cssnu[.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} PK!_?SS&freemius/assets/css/admin/checkout.cssnu[@media screen and (max-width: 782px){#wpbody-content{padding-bottom:0 !important}} PK!:0$$#freemius/assets/img/plugin-icon.pngnu[PNG  IHDR?1tEXtSoftwareAdobe ImageReadyqe<$FIDATx}_[G !$ F`/=qǓޟ"$XH$E,=.ܐ ' P#KKK+˰~Hv#kYYmmkZZ[RJDbQ_lɢN.$HRs ͢Ș;Z.$Ojfff"p|e.bH.ϟ3rP@^;99 zAd̒EÅѱ1q4hoo/++(BӇޖN/?`#% JKCCź(}Ncc$޿_:;Ν;n$13888;3#mJzzznܼiZ%SSCCC{{{jQQQqֶ6IH$~{&JKe?xPYY) FGGFFR.2XVd]]]Hz*K>4z(06&''?~ ?/l6ȡVI]]]V]rI_|Nn:0ٜ1~-ꫯxV`<L1߱ z?ֲ*t***v{xʉPZuV*|Lo;cl[[[dr+\Y,}eKK$+ ]'Nv=h4#sq* C_~Y&]Xyc1|uuoRh$5 8 (3)AX,/ گMM0}@+Sq O SI pZZ[}2_&8~ A( 3o߾74tuu!362酅0zXxZNK 06::88ˁuftLFf"1555;;e%L}nKY̌OLlHܹsW ''V;0}|SJrqqnEʹ7o߸! ޾E}>ۥ39piiihppSN`_$UDZ1=ϭfS`~~4sVB$44422[ QRcp`b||tlLxw޽~$J|A@ _|!Nx~aaA{_}!Q0;;k~qvPٗUp@_} esBf}ri6KY\''&X^__/ `1MMM͗t -01P`ٞ>}}SgӍ`0xb:b o߾e]yrߺQ#p=܏fg߽{wr+ 74֭[>d|pp .7q }}iu:;;\5ĥ7o?mA?}T{>}'#}򄼭Qȇ3kkkY^^c<~L~渾>;;g>lwwǏYVV#}vvv3{vL&Bt:̠njj twWh_ͬkE;ymllk!`7Cnrr7I@h4:>>GC <|h` ɏ6:`0~|laanrs։gT %/"d2/~uiq3yf 333XxWw|^9e+Hy|5< >|!򞻏$0$:i 97R RVMOM]Y+JM;PXC!k||ښJ>oQYA?r=V,obEYڕ ZS彔HP"ӀR󛛉c0mbmmH1!?퀷wzVC$;`x).PM։㕸}m#dK$eee_>z$6aB÷V?|o_~I=Ngz?' 7YE<\ |(LPs~\7D+~ NBV:B;w!UQOOqZևۮ~fE2?>!w!fb|< Muj {iϑHW=iqB~?ի!$%&8JRVUUHdqxf"۷oMOMSa3eyu,gD{䱈3HC,u{zLFƅj>&9OMnoV{$(_%ZN<[IOqG*lKT3׉>3FXˠy tz; _<ZJP6469Y_`lOyDH̆?}BNUQQ y8FPSXٌ7q:_gss>_+++(nyT` e?;Jy3 {^@"SP*3:px4*+ &@֡>2[~_ȖApRb2/.p%eH`(deOdCa岵LMMOKwH^g+:]c*4D PgV_?ȱ*F,i)@9!x*:YM}}=$,:X8e:  W'8EFa:5Px{ m|,Tg>~7=MH `hҤs@ 5B AA+0x7݄&zJ_ggb9tBeeefH#]yTU HkN2p8!tTg6|ܻLfˏ55>B rdnR>j߻Nr!G/ L:US A^V/fZl3ongD9Nh@2_RP .0AY,J\~0g)+e|6\b::!;Urk_%ZHs=PEȝ^3i&'D5˛rhhH1rਨL tNLhr}pgr}nGAPP-j @ T|$7Xs/;s#U"Q Pj"D=٬Kh̒ !gt,kB7,? !}kk뤛[ S(X"|ׁI.p G_S:'Nz&ͬv` \FB ˂3EpfyXј~J>L Y%@>,(}΃ #߰L+㙊%)Bt0 __B$#j#I"IRH!qcS܎lgP=_b/HL{8A =X#Ka EB@x^2˲j8(Hv &z]]iB4 ]  f]$C9ZI$L !I) ' 4HBޢ(:8 @$,LL*LB!u5V> DSj 8KRjŽ$ȶ56%$NfuD{Q _!', PAlkDʪ*ؼP_sD  ʛ%jAB V%b{yMk_[K 7T !PA 0 ʁjBaBf1)nGdlwt2|>n,sS$1p*hFaʢd Pq>Ld:d3tm}k׮c݌oj-)`BWn] AYYYgWWi C)<^8j)w4v`cr !d$U@. ҬzzzJ,m&!KWVVH^K֒tT-/-` ΒrߟBDPW8,V+ #  I2{C]b:Q%tk&)Y^^&{zJMyl|BKKK$5pw,.,b~ x 8B 4 3YMՑ";7kk? !ƢWG 'z;VBSSS$e`5[oYALfAuvu[,#PZU)%bB?*>j4ZkJbϓ؊bDȨN {z:D` s =Ѱ(q{:@*%EWWW^UqxxHRAȍfZ\.7X0kv+|>!xbĄ=b @kű~,Q 7ɱ=͆@O:TJC1XBc "! b7ncollP]+VGtKDQq Gf+Enr:n-_Z\z)!#Q=# A4VT񯗄uV cJQP֓T08I}al~d^>O'n S[{;UW!0pA hݻb{F"[_l0"#2aCЧO"\}"s޽wY{j<>%j#xϜ2PvwwS,Ix޽{+].W"B!;婊XD=DaFa*A:pG˼ 6`k[LP|>;IGL>|(N***~kkk$Q &NCcQ7B3Kb  ۠w tvv r4VyD*ï!CL lc 4"L Ͳ`0Hj{'N6P~X[Z[}E__^ i.\ 791A ”pf<dEVk^lhp{mmm{qq0œgKOGGFH&hIgx<{I˛z-.dr^AyZ!_ ոWS.ޱnn '|*FLl+O$?~'[h ~-fgfrQ dZi#Zc"?߯f6 ׂh4Jxfptۛ7B4z/*yow]~:;;iu˗/E˂ bccc 䫫czCl^ZDX3eŝ痢 nliss3ct/GR«ֶ61l  \X&DZODf‚gv};? +>W"wS\ntI]]}swwPb#GI_t:=22Bimc" p1 O4rSoe,-hJt Z![A3vv}:;;YT*ϟL@ ěn_D#zHn6W?_)3ѻJZ?I8%/ l90LZ#*dtUOO΅i9kMMf(`abmH3& Rshpѵ-!֕BNF ݙL919ʤ5 RsǑ8G.]p8UTS{(.eZ]f$ y]^-28>R/^0U9r12!"PS AЋdo~Lj*@;>6byq)N%X LQ{h^xn;hLͺ*|y~2;[*bTVjxt+뽼Fww7#p4i`#t&gŻ/@SWmhnnF.{{{޿CxiolYIEx]@F_]a}8`|k-J4^ H盶wt\z`ʎWHR'\gRx޾&{ooo_q6RB^]0(h'dEJf̌Q_uUWvttlQ+e#5W" E0}. Gd",:\_'*fso0(" ̨ڛn.JɁ,޽} U(q)溺ׁf"D6R_߸g) `ʶ!>c۷o^8Uag뇜mnjŸ4^yx EXQQog#V9|۟''_&,/-)I!q{A)S"st HY(x^.g^i<mJ6񮛛]WFu;! BeA`rbBKSೝNgUU]fWT`TH9 ND8 D뽽Lu +ފr:00 ZUIɢ_\sW̚˻-E <@wqWA;ܿS޹{Wb{^~n 턌~!)ެqCY^얕A- ]]Es/@'Ƚ%zhtfzWWqR?ȩo ,..pd1ۖәGStuu5ߓt:=7;Btz `ʎj: DbvfFDC~Н$<$ U1&8*++۳[ TS@^{g"iڌ_Rva4S[[RbϪqBDbiy9^DWW, {& ! E?Vkk EsU9BEAM:Uι\% PAH+B#5N AA",E R:EEL&C X4M|x<LWLY.O$E=WVj+Xa1箮mbxPȐJ *UUKv\nGuoww;FX^Bn4xná̛06$)3 O}o;o2 2"#!@ 2d!C& 2dȐ CL2dȐ!@ 2d!C& 2dȐ CL2dȐ!@ 2dX#qtxx~\v_z ΋DIII %?[lr Yݵ{?Bε!%%ENHMM)!@lY[[[;^@C8 /j(y, wX_ܴZ ~BIVLt͆Gd6vwwC蓃y $ h_]Y1L!}/A\D˕ kkGGGavk :$33S~2˵7VKJK@ G:B" TVUeddD/v@!//O&@DtiiivfTTHNN}F ΢0m0_K IIIkzzLpsx SS_G+J,n߹# T;<}6p o6e  Qd2}/bcc/9n+*UI mmmڜtttta~gRjuWҞq2AO+?p8F}VuuuSssFJĻ_z{wvvxi  ζ6mww*Ά)HJJ 666u{ YY& 4"4)ommI8֖P`fff|lL*go:777///'78Lb4pB#VVVP!]YYG#l-,,iZ]XՕ5IoܼB!Av#υ/+/r( ٕe$ܿ?TΌC?}s0R,-tΪT*E(nq1]#uJǏC",@?RSSkjku:]8 AS43=-?y\`ggGqFQ/F`I<~$--M&O=Ay  -aX&E8E{aVvLGhOIIilj*((PDVWV&zr111YYY2j Z2Gg"6ONh=~fK @[P_VV/FptdQxd5sp]]]Ej,><4d#*&f]] /S]] ''|񱱹9T*w" 3Vv^tƆ6Dmnl 8N!. 3xD2G7oJ燆4M}@688*š>z9#+\XX~Yx;n*.)ֲu|+M1$oa! ޻'oăy%:>>j/_҂L&'𩧇zbNNN5{{o޼RRRZZ[?~&:\GKISJx,T䎌}0Ov1>zz'P<~)VĚ,LzX`QJKIt4BOKoR`|lljY*Uk_9;3@yqaaUՈ\-18>p# t#SU,.**z/tOa*#zp~Jsttt495=uP1C^^W*Dkxb_~e<~sVϔRWz=Nal)÷o"p?~Lȍ䫎U)`\ GF|eyyyFFJKK{ykOz-\pOBl6̦3??!rANEcc#o{ w[ ӹbJ%||P /rͶg/ Yxr~~z ^x-dB0@Q&8Jd M?;3e5ϢAzo$)5o#9\X!hcskao7$)@`@z{{)0tRF6^RZ.8`7nd4~jl]#`X91,?鶗qqqqM,)))?_I v7^.&uG?Ve y0--$]^\A3PI><ų@ɎՕ./KEErxx/تH^uu5|{{k?$RFew,GlZ SLP~?aoc_R.'t8!œ~&Klr+~sxsXk}>$FK E@ ?0z)A?wwsX}BGG\Tdfe\ [Ha॓`hBO.Fu< (twumfccc@?;5J.q.FJKs<O'KK1QЧR?etpOwvvl)H!opih.51>sPҎB'[_$a8tb2'--nWk?8p8`FCK_III~e OąuywJJ} vᡁ[ǣ?M&@i>UF6:(1,@:l+.( s\B 5! tY"vUKG UG%G.I[+6tU16F_g0g u o Pቹ\n N;%`cA2 e|䠘ľSlPTI6 ڳ)Q"2<;hPkQx]]߿~%؜Ĥ^ŏ$ςo?W:1{UBxз޸yMq$0:89&rC aq E3))b0˥ +Y6 x ǏF( y^qN'w rɔt:!Q՞#v~z D2 2-va-6!roX68\IÏH!@jgk4oݾ-\ZrIA8U0@/]BD³⩬ $ϘJ]]KM!!,)QDs8__NNN>յx&--iojǥ ߾}8%+++E> ~Pud|X+Bx 4&_| jׇwGGF[}w%fggONNjHl 9.IUX)TDK'f ްZoޔWT444h ܜIM>Aa HÜJAPC"  b~ny*g/UUU/ _@.$$n "Vh!X!![7os&_DQq$;m#!zfZ#--{9;⁏? QO0EeDLX/Z7zKklvFp]bi, f a+!" $$PxKKں:lhd aZ=VAyLდ6002ګՇRy޽~] `<$ M4Ȫ wk(0]]_0Y~pݻRW%9IA9N@I6|Z^ի5-66ŋc AQѣ_> i{C*%')ɣ#2:~YSqN˃)hni!|Chri{5@cb+I HB,Pznvmuu52))-e0p{Af ؠ~[o9t/l`, Nuڶ얺CT}˗/N O=k{*٦p~KT Y9uQzWǏW^M[ӵxŔggaXEG H ro sv!u 3-O9"l&X,E۷o](`!#": luEȸ!@͔fcq|6[WW׷_7AKRv-.TJRt:2[8'#2LMQw 'О ްȋ hz1MBN8'&&^z{'gmR- *""ST8Qy$(a(N˗ MWfs/hqaASVV&|948EA?O/??_m?vЅX,⏊,0!,`+h0kbq7EqE4E'%^-)/##oX8F슋j%Ly/--I +S$O==?}_W\oԤFeJ=ē'q$F!'DyPjB0o޼A2䯧V0nF&`ۧ i_3ҮTq ?{GGGS~>eegK] D4?9=tq''W.`"l6qxmȤ`GZ#kbi}}__/W/g ՞ku+(֦_UV^ 0R QF?&SuuFxI\J> 3G~X aczTjUҪ$.8==ﯕTaoW 7 @oNHn1yl6"j5;Ys G Q-D~q\.W}!RRxTj0. 1|O}}WPtd2aGT`1G² WJ(g"o 111E>aTlbDv=YYY icr,/-w(Jt"g?nu5u KeTEeVKPDp{4 Beee>1bij!) v(&e>LRRS⮬c9⯳qNNP4 {~߳Oqwd}`@{h\*cn/INOccb32iF*JI^F_:wxr_>>>q_^u V5]ڂ;0T p7MM!!B`fz(O' b-%7nue|jpQ5"Ꚛ(jŁd5d-CPxi |N#VTiX!LE, ƩC9A )//B|\8?TKh" 3+VƯKK`{s9x\wqhu❞ .uVoX'&~T!EZ;ϓj%l@TRj\engti/:†1?ZzM=ccc{|LZ~t:k:. y40޽rDbbb?;;;!j^uٗޟc\L1B(]HPBX[[KqOl6ߌ;%0)ْKSC{]t?;'@$YIbNޣE@RR.Hd &&&I̝o0VRUKw|.YEII %II* og?lmmMQ|fΔTH64vW@`L&tSSSun/1,>YH}7\ʪ*ič!ONN SSGv&H$^PPPptHRvpЫrҲT\Vw8FBX8|Ņ!Zr\τB(+*:>>7XE)D_בb&G099ڧTU+7vnK2 <隚%hh$H X!@LL T) PuB wltd4 9P_Z֛U")))1TJq z,]ffxHR_^ hlj :f(°Y.2)x>; \$Btfx& 8ݏWev D}A4̲gKdϦsp>EχYW0W !7OĄh{bA'JƜYχ]()Qh&_.|&WP jakOjHM(sONajJw`Zx"7NwhTSSuVqNw> º$@βk{vvG}}>?:##2&D p/ \EEER&t߲Z9!t8.MU*|}7$4~`j 6`.oLFgl63ᎎ,Hw4m0H*:(xBxI* NAL@¾2W =0?q1* ^ʋ$!|Z@r3w2 %ޮ$7mXhRx\Aw\\q.L!Ĉ_DAAA=#n(aj͓+'D*HM!q/p{SC!@@ Uo$Y^^BBn^C/fdXҲ2]ep"kn QNG)+}ӱQe2|e @W IK* ECcg|__LȨ°B#$;]Txa6^732JJJx}Th%O # [A0oEkS8H eeeEKv~.^'FkYeggn-"5`0JSPP9/;,~kHMME ܤR4L0j6~r|k!iֶ-$oBJێ ' Sd:tO!V( TM"Q&6.HOO ZDE%xrON`( xrrz7~6蘘X0q>/xL"@JJ38>¤ؓ_%{8߱'CF$@qaǞ HŅ{2dD"x .'MUNʈ\J u=2dP\,# S2"%]rJTF@!DeD8䔨&[%i.eprJTF@᝞"De\`=ܛ IENDB`PK!p2WWfreemius/assets/img/index.phpnu[ 0) { $window.on('scroll', function () { for (var i = 0; i < iframes.length; i++) { FS.PostMessage.postScroll(iframes[i]); } }); } }, init_child : function () { this.init(_parent_subdomain); _is_child = true; // Post height of a child right after window is loaded. $(window).bind('load', function () { FS.PostMessage.postHeight(); // Post message that window was loaded. FS.PostMessage.post('loaded'); }); }, hasParent : function () { return _hasParent; }, postHeight : function (diff, wrapper) { diff = diff || 0; wrapper = wrapper || '#wrap_section'; this.post('height', { height: diff + $(wrapper).outerHeight(true) }); }, postScroll : function (iframe) { this.post('scroll', { top: $window.scrollTop(), height: ($window.height() - parseFloat($html.css('paddingTop')) - parseFloat($html.css('marginTop'))) }, iframe); }, post : function (type, data, iframe) { console.debug('PostMessage.post', type); if (iframe) { // Post to iframe. _postman.postMessage(JSON.stringify({ type: type, data: data }), iframe.src, iframe.contentWindow); } else { // Post to parent. _postman.postMessage(JSON.stringify({ type: type, data: data }), _parent_url, window.parent); } }, receive: function (type, callback) { console.debug('PostMessage.receive', type); if (undef === _callbacks[type]) _callbacks[type] = []; _callbacks[type].push(callback); }, receiveOnce: function (type, callback) { if (this.is_set(type)) return; this.receive(type, callback); }, // Check if any callbacks assigned to a specified message type. is_set: function (type) { return (undef != _callbacks[type]); }, parent_url: function () { return _parent_url; }, parent_subdomain: function () { return _parent_subdomain; } }; }(); })(jQuery);PK! DDfreemius/LICENSE.txtnu[ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read .PK!% freemius/require.phpnu[add( "Freemius failed to redirect the page because the headers have been already sent from line {$line} in file {$file}. If it's unexpected, it usually happens due to invalid space and/or EOL character(s).", 'Oops...', 'error' ); } return false; } if ( defined( 'DOING_AJAX' ) ) { // Don't redirect on AJAX calls. return false; } if ( ! $location ) // allows the wp_redirect filter to cancel a redirect { return false; } $location = fs_sanitize_redirect( $location ); if ( $is_IIS ) { header( "Refresh: 0;url=$location" ); } else { if ( php_sapi_name() != 'cgi-fcgi' ) { status_header( $status ); } // This causes problems on IIS and some FastCGI setups header( "Location: $location" ); } if ( $exit ) { exit(); } return true; } if ( ! function_exists( 'fs_sanitize_redirect' ) ) { /** * Sanitizes a URL for use in a redirect. * * @since 2.3 * * @param string $location * * @return string redirect-sanitized URL */ function fs_sanitize_redirect( $location ) { $location = preg_replace( '|[^a-z0-9-~+_.?#=&;,/:%!]|i', '', $location ); $location = fs_kses_no_null( $location ); // remove %0d and %0a from location $strip = array( '%0d', '%0a' ); $found = true; while ( $found ) { $found = false; foreach ( (array) $strip as $val ) { while ( strpos( $location, $val ) !== false ) { $found = true; $location = str_replace( $val, '', $location ); } } } return $location; } } if ( ! function_exists( 'fs_kses_no_null' ) ) { /** * Removes any NULL characters in $string. * * @since 1.0.0 * * @param string $string * * @return string */ function fs_kses_no_null( $string ) { $string = preg_replace( '/\0+/', '', $string ); $string = preg_replace( '/(\\\\0)+/', '', $string ); return $string; } } } #endregion Core Redirect (copied from BuddyPress) ----------------------------------------- if ( ! function_exists( 'fs_get_ip' ) ) { /** * Get server IP. * * @since 2.5.1 This method returns the server IP. * * @author Vova Feldman (@svovaf) * @since 1.1.2 * * @return string|null */ function fs_get_ip() { return empty( $_SERVER[ 'SERVER_ADDR' ] ) ? null : $_SERVER[ 'SERVER_ADDR' ]; } } /** * Leverage backtrace to find caller plugin main file path. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return string */ function fs_find_caller_plugin_file() { /** * All the code below will be executed once on activation. * If the user changes the main plugin's file name, the file_exists() * will catch it. */ if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $all_plugins = fs_get_plugins( true ); $all_plugins_paths = array(); // Get active plugin's main files real full names (might be symlinks). foreach ( $all_plugins as $relative_path => $data ) { $all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ); } $plugin_file = null; for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) { if ( empty( $bt[ $i ]['file'] ) ) { continue; } if ( in_array( fs_normalize_path( $bt[ $i ]['file'] ), $all_plugins_paths ) ) { $plugin_file = $bt[ $i ]['file']; break; } } if ( is_null( $plugin_file ) ) { // Throw an error to the developer in case of some edge case dev environment. wp_die( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.', 'Error', array( 'back_link' => true ) ); } return $plugin_file; } require_once dirname( __FILE__ ) . '/supplements/fs-essential-functions-1.1.7.1.php'; /** * Update SDK newest version reference. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $sdk_relative_path * @param string|bool $plugin_file * * @global $fs_active_plugins */ function fs_update_sdk_newest_version( $sdk_relative_path, $plugin_file = false ) { /** * If there is a plugin running an older version of FS (1.2.1 or below), the `fs_update_sdk_newest_version()` * function in the older version will be used instead of this one. But since the older version is using * the `is_plugin_active` function to check if a plugin is active, passing the theme's `plugin_path` to the * `is_plugin_active` function will return false since the path is not a plugin path, so `in_activation` will be * `true` for theme modules and the upgrading of the SDK version to 1.2.2 or newer version will work fine. * * Future versions that will call this function will use the proper logic here instead of just relying on the * `is_plugin_active` function to fail for themes. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ global $fs_active_plugins; $newest_sdk = $fs_active_plugins->plugins[ $sdk_relative_path ]; if ( ! is_string( $plugin_file ) ) { $plugin_file = plugin_basename( fs_find_caller_plugin_file() ); } if ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) { if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $in_activation = ( ! is_plugin_active( $plugin_file ) ); } else { $theme = wp_get_theme(); $in_activation = ( $newest_sdk->plugin_path == $theme->stylesheet ); } $fs_active_plugins->newest = (object) array( 'plugin_path' => $plugin_file, 'sdk_path' => $sdk_relative_path, 'version' => $newest_sdk->version, 'in_activation' => $in_activation, 'timestamp' => time(), ); // Update DB with latest SDK version and path. update_option( 'fs_active_plugins', $fs_active_plugins ); } /** * Reorder the plugins load order so the plugin with the newest Freemius SDK is loaded first. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @return bool Was plugin order changed. Return false if plugin was loaded first anyways. * * @global $fs_active_plugins */ function fs_newest_sdk_plugin_first() { global $fs_active_plugins; /** * @todo Multi-site network activated plugin are always loaded prior to site plugins so if there's a plugin activated in the network mode that has an older version of the SDK of another plugin which is site activated that has new SDK version, the fs-essential-functions.php will be loaded from the older SDK. Same thing about MU plugins (loaded even before network activated plugins). * * @link https://github.com/Freemius/wordpress-sdk/issues/26 */ $newest_sdk_plugin_path = $fs_active_plugins->newest->plugin_path; $active_plugins = get_option( 'active_plugins', array() ); $updated_active_plugins = array( $newest_sdk_plugin_path ); $plugin_found = false; $is_first_path = true; foreach ( $active_plugins as $key => $plugin_path ) { if ( $plugin_path === $newest_sdk_plugin_path ) { if ( $is_first_path ) { // if it's the first plugin already, no need to continue return false; } $plugin_found = true; // Skip the plugin (it is already added as the 1st item of $updated_active_plugins). continue; } $updated_active_plugins[] = $plugin_path; if ( $is_first_path ) { $is_first_path = false; } } if ( $plugin_found ) { update_option( 'active_plugins', $updated_active_plugins ); return true; } if ( is_multisite() ) { // Plugin is network active. $network_active_plugins = get_site_option( 'active_sitewide_plugins', array() ); if ( isset( $network_active_plugins[ $newest_sdk_plugin_path ] ) ) { reset( $network_active_plugins ); if ( $newest_sdk_plugin_path === key( $network_active_plugins ) ) { // Plugin is already activated first on the network level. return false; } else { $time = $network_active_plugins[ $newest_sdk_plugin_path ]; // Remove plugin from its current position. unset( $network_active_plugins[ $newest_sdk_plugin_path ] ); // Set it to be included first. $network_active_plugins = array( $newest_sdk_plugin_path => $time ) + $network_active_plugins; update_site_option( 'active_sitewide_plugins', $network_active_plugins ); return true; } } } return false; } /** * Go over all Freemius SDKs in the system and find and "remember" * the newest SDK which is associated with an active plugin. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @global $fs_active_plugins */ function fs_fallback_to_newest_active_sdk() { global $fs_active_plugins; /** * @var object $newest_sdk_data */ $newest_sdk_data = null; $newest_sdk_path = null; foreach ( $fs_active_plugins->plugins as $sdk_relative_path => $data ) { if ( is_null( $newest_sdk_data ) || version_compare( $data->version, $newest_sdk_data->version, '>' ) ) { // If plugin inactive or SDK starter file doesn't exist, remove SDK reference. if ( 'plugin' === $data->type ) { $is_module_active = is_plugin_active( $data->plugin_path ); } else { $active_theme = wp_get_theme(); $is_module_active = ( $data->plugin_path === $active_theme->get_template() ); } $is_sdk_exists = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $sdk_relative_path . '/start.php' ) ); 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() // or explicitly with update_option(). } else { $newest_sdk_data = $data; $newest_sdk_path = $sdk_relative_path; } } } if ( is_null( $newest_sdk_data ) ) { // Couldn't find any SDK reference. $fs_active_plugins = new stdClass(); update_option( 'fs_active_plugins', $fs_active_plugins ); } else { fs_update_sdk_newest_version( $newest_sdk_path, $newest_sdk_data->plugin_path ); } }PK!10freemius/includes/fs-html-escaping-functions.phpnu[ 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() ); } } PK!Cq'freemius/includes/class-fs-security.phpnu[id . $entity->secret_key . $entity->public_key . $action ); } /** * @param \FS_Scope_Entity $entity * @param int|bool $timestamp * @param string $action * * @return array */ function get_context_params( FS_Scope_Entity $entity, $timestamp = false, $action = '' ) { if ( false === $timestamp ) { $timestamp = time(); } return array( 's_ctx_type' => $entity->get_type(), 's_ctx_id' => $entity->id, 's_ctx_ts' => $timestamp, 's_ctx_secure' => $this->get_secure_token( $entity, $timestamp, $action ), ); } } PK!6$freemius/includes/class-freemius.phpnu[store_id_slug_type_path_map( $module_id, $slug ); } $this->_module_id = $module_id; $this->_slug = $this->get_slug(); $this->_module_type = $this->get_module_type(); $this->_blog_id = is_multisite() ? get_current_blog_id() : null; $this->_storage = FS_Storage::instance( $this->_module_type, $this->_slug ); $this->_cache = FS_Cache_Manager::get_manager( WP_FS___OPTION_PREFIX . "cache_{$module_id}" ); $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, $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 ); $this->_is_multisite_integrated = ( defined( "WP_FS__PRODUCT_{$module_id}_MULTISITE" ) && ( true === constant( "WP_FS__PRODUCT_{$module_id}_MULTISITE" ) ) ); $this->_is_network_active = ( is_multisite() && $this->_is_multisite_integrated && // Themes are always network activated, but the ACTUAL activation is per site. $this->is_plugin() && ( is_plugin_active_for_network( $this->_plugin_basename ) || // Plugin network level activation or uninstall. ( fs_is_network_admin() && is_plugin_inactive( $this->_plugin_basename ) ) ) ); $this->_storage->set_network_active( $this->_is_network_active, $this->is_delegated_connection() ); if ( ! isset( $this->_storage->is_network_activated ) ) { $this->_storage->is_network_activated = $this->_is_network_active; } if ( $this->_storage->is_network_activated != $this->_is_network_active ) { // Update last activation level. $this->_storage->is_network_activated = $this->_is_network_active; $this->maybe_adjust_storage(); } #region Migration if ( is_multisite() ) { /** * If the install_timestamp exists on the site level but doesn't exist on the * network level storage, it means that we need to process the storage with migration. * * The code in this `if` scope will only be executed once and only for the first site that will execute it because once we migrate the storage data, install_timestamp will be already set in the network level storage. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ if ( false === $this->_storage->get( 'install_timestamp', false, true ) && false !== $this->_storage->get( 'install_timestamp', false, false ) ) { // Initiate storage migration. $this->_storage->migrate_to_network(); // Migrate module cache to network level storage. $this->_cache->migrate_to_network(); } } #endregion $base_name_split = explode( '/', $this->_plugin_basename ); $this->_plugin_dir_name = $base_name_split[0]; if ( $this->_logger->is_on() ) { $this->_logger->info( 'plugin_main_file_path = ' . $this->_plugin_main_file_path ); $this->_logger->info( 'plugin_dir_path = ' . $this->_plugin_dir_path ); $this->_logger->info( 'plugin_basename = ' . $this->_plugin_basename ); $this->_logger->info( 'free_plugin_basename = ' . $this->_free_plugin_basename ); $this->_logger->info( 'plugin_dir_name = ' . $this->_plugin_dir_name ); } // Remember link between file to slug. $this->store_file_slug_map(); // Store plugin's initial install timestamp. if ( ! isset( $this->_storage->install_timestamp ) ) { $this->_storage->install_timestamp = WP_FS__SCRIPT_START_TIME; } if ( ! is_object( $this->_plugin ) ) { $this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->get(); } $this->_admin_notices = FS_Admin_Notices::instance( $this->_slug . ( $this->is_theme() ? ':theme' : '' ), /** * Ensure that the admin notice will always have a title by using the stored plugin title if available and * retrieving the title via the "get_plugin_name" method if there is no stored plugin title available. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ ( is_object( $this->_plugin ) && isset( $this->_plugin->title ) ? $this->_plugin->title : $this->get_plugin_name() ), $this->get_unique_affix() ); if ( 'true' === fs_request_get( 'fs_clear_api_cache' ) || fs_request_is_action( 'restart_freemius' ) ) { FS_Api::clear_cache(); $this->_cache->clear(); } $this->register_constructor_hooks(); /** * Starting from version 2.0.0, `FS_Site` entities no longer have the `plan` property and have `plan_id` * instead. This should be called before calling `_load_account()`, otherwise, `$this->_site` will not be * loaded in `_load_account` for versions of SDK starting from 2.0.0. * * @author Leo Fajardo (@leorw) */ self::migrate_install_plan_to_plan_id( $this->_storage ); $this->_load_account(); $this->_version_updates_handler(); } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 */ private function maybe_adjust_storage() { $install_timestamp = null; $prev_is_premium = null; $options_to_update = array(); $is_network_admin = fs_is_network_admin(); $network_install_timestamp = $this->_storage->get( 'install_timestamp', null, true ); if ( ! $is_network_admin ) { if ( is_null( $network_install_timestamp ) ) { // Plugin was not network-activated before. return; } if ( is_null( $this->_storage->get( 'install_timestamp', null, false ) ) ) { // Set the `install_timestamp` only if it's not yet set. $install_timestamp = $network_install_timestamp; } $prev_is_premium = $this->_storage->get( 'prev_is_premium', null, true ); } else { $current_wp_user = self::_get_current_wp_user(); $current_fs_user = self::_get_user_by_email( $current_wp_user->user_email ); $network_user_info = array(); $skips_count = 0; $sites = self::get_sites(); $sites_count = count( $sites ); $blog_id_2_install_map = array(); $is_first_non_ignored_blog = true; foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); $blog_install_timestamp = $this->_storage->get( 'install_timestamp', null, $blog_id ); if ( is_null( $blog_install_timestamp ) ) { // Plugin has not been installed on this blog. continue; } $is_earlier_install = ( ! is_null( $install_timestamp ) && $blog_install_timestamp < $install_timestamp ); $install = $this->get_install_by_blog_id( $blog_id ); $update_network_user_info = false; if ( ! is_object( $install ) ) { if ( ! $this->_storage->get( 'is_anonymous', false, $blog_id ) ) { // The opt-in decision (whether to skip or opt in) is yet to be made. continue; } $skips_count ++; } else { $blog_id_2_install_map[ $blog_id ] = $install; if ( empty( $network_user_info ) ) { // Set the network user info for the 1st time. Choose any user information whether or not it is for the current WP user. $update_network_user_info = true; } if ( ! $update_network_user_info && is_object( $current_fs_user ) && $network_user_info['user_id'] != $current_fs_user->id && $install->user_id == $current_fs_user->id ) { // If an install that is owned by the current WP user is found, use its user information instead. $update_network_user_info = true; } if ( ! $update_network_user_info && $is_earlier_install && ( ! is_object( $current_fs_user ) || $current_fs_user->id == $install->user_id ) ) { // Update to the earliest install info if there's no install found so far that is owned by the current WP user; OR only if the found install is owned by the current WP user. $update_network_user_info = true; } } if ( $update_network_user_info ) { $network_user_info = array( 'user_id' => $install->user_id, 'blog_id' => $blog_id ); } $site_prev_is_premium = $this->_storage->get( 'prev_is_premium', null, $blog_id ); if ( $is_first_non_ignored_blog ) { $prev_is_premium = $site_prev_is_premium; if ( is_null( $network_install_timestamp ) ) { $install_timestamp = $blog_install_timestamp; } $is_first_non_ignored_blog = false; continue; } if ( ! is_null( $prev_is_premium ) && $prev_is_premium !== $site_prev_is_premium ) { // If a different `$site_prev_is_premium` value is found, do not include the option in the collection of options to update. $prev_is_premium = null; } if ( $is_earlier_install ) { // If an earlier install timestamp is found. $install_timestamp = $blog_install_timestamp; } } $installs_count = count( $blog_id_2_install_map ); if ( $sites_count === ( $installs_count + $skips_count ) ) { if ( ! empty( $network_user_info ) ) { $options_to_update['network_user_id'] = $network_user_info['user_id']; $options_to_update['network_install_blog_id'] = $network_user_info['blog_id']; foreach ( $blog_id_2_install_map as $blog_id => $install ) { if ( $install->user_id == $network_user_info['user_id'] ) { continue; } $this->_storage->store( 'is_delegated_connection', true, $blog_id ); } } if ( $sites_count === $skips_count ) { /** * Assume network-level skipping as the intended action if all actions identified were only * skipping of the connection (i.e., no opt-ins and delegated connections so far). */ $options_to_update['is_anonymous_ms'] = true; } else if ( $sites_count === $installs_count ) { /** * Assume network-level opt-in as the intended action if all actions identified were only opt-ins * (i.e., no delegation and skipping of the connections so far). */ $options_to_update['is_network_connected'] = true; } } } if ( ! is_null( $install_timestamp ) ) { $options_to_update['install_timestamp'] = $install_timestamp; } if ( ! is_null( $prev_is_premium ) ) { $options_to_update['prev_is_premium'] = $prev_is_premium; } if ( ! empty( $options_to_update ) ) { $this->adjust_storage( $options_to_update, $is_network_admin ); } } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param array $options * @param bool $is_network_admin */ private function adjust_storage( $options, $is_network_admin ) { foreach ( $options as $name => $value ) { $this->_storage->store( $name, $value, $is_network_admin ? true : null ); } } /** * Checks whether this module has a settings menu. * * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool */ function has_settings_menu() { return ( $this->_is_network_active && fs_is_network_admin() ) ? $this->_menu->has_network_menu() : $this->_menu->has_menu(); } /** * If `true` the opt-in should be shown as a modal dialog box on the themes.php page. WordPress.org themes guidelines prohibit from redirecting the user from the themes.php page after activating a theme. * * @author Vova Feldman (@svovaf) * @since 2.4.5 * * @return bool */ function show_opt_in_on_themes_page() { if ( ! $this->is_free_wp_org_theme() ) { return false; } if ( ! $this->has_settings_menu() ) { return true; } return $this->show_settings_with_tabs(); } /** * If `true` the opt-in should be shown on the product's main setting page. * * @author Vova Feldman (@svovaf) * @since 2.4.5 * * @return bool * * @uses show_opt_in_on_themes_page(); */ function show_opt_in_on_setting_page() { return ! $this->show_opt_in_on_themes_page(); } /** * If `true` the settings should be shown using tabs. * * @author Vova Feldman (@svovaf) * @since 2.4.5 * * @return bool */ function show_settings_with_tabs() { return ( self::NAVIGATION_TABS === $this->_navigation ); } /** * Check if the context module is free wp.org theme. * * This method is helpful because: * 1. wp.org themes are limited to a single submenu item, * and sub-submenu items are most likely not allowed (never verified). * 2. wp.org themes are not allowed to redirect the user * after the theme activation, therefore, the agreed UX * is showing the opt-in as a modal dialog box after * activation (approved by @otto42, @emiluzelac, @greenshady, @grapplerulrich). * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return bool */ function is_free_wp_org_theme() { return ( $this->is_theme() && $this->is_org_repo_compliant() && ! $this->is_premium() ); } /** * Checks whether this a submenu item is visible. * * @author Vova Feldman (@svovaf) * @since 1.2.2.6 * @since 1.2.2.7 Even if the menu item was specified to be hidden, when it is the context page, then show the submenu item so the user will have the right context page. * * @param string $slug * @param bool $is_tabs_visibility_check This is used to decide if the associated tab should be shown or hidden. * * @return bool */ function is_submenu_item_visible( $slug, $is_tabs_visibility_check = false ) { if ( $this->is_admin_page( $slug ) ) { /** * It is the current context page, so show the submenu item * so the user will have the right context page, even if it * was set to hidden. */ return true; } if ( ! $this->has_settings_menu() ) { // No menu settings at all. return false; } if ( ! $is_tabs_visibility_check && $this->is_org_repo_compliant() && $this->show_settings_with_tabs() ) { /** * wp.org themes are limited to a single submenu item, and * sub-submenu items are most likely not allowed (never verified). */ return false; } return $this->_menu->is_submenu_item_visible( $slug ); } /** * Check if a Freemius page should be accessible via the UI. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @param string $slug * * @return bool */ function is_page_visible( $slug ) { if ( $this->is_admin_page( $slug ) ) { return true; } return $this->_menu->is_submenu_item_visible( $slug, true, true ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 */ private function _version_updates_handler() { if ( ! isset( $this->_storage->sdk_version ) || $this->_storage->sdk_version != $this->version ) { // Freemius version upgrade mode. $this->_storage->sdk_last_version = $this->_storage->sdk_version; $this->_storage->sdk_version = $this->version; if ( empty( $this->_storage->sdk_last_version ) || version_compare( $this->_storage->sdk_last_version, $this->version, '<' ) ) { $this->_storage->sdk_upgrade_mode = true; $this->_storage->sdk_downgrade_mode = false; } else { $this->_storage->sdk_downgrade_mode = true; $this->_storage->sdk_upgrade_mode = false; } $this->do_action( 'sdk_version_update', $this->_storage->sdk_last_version, $this->version ); } $plugin_version = $this->get_plugin_version(); if ( ! isset( $this->_storage->plugin_version ) || $this->_storage->plugin_version != $plugin_version ) { // Plugin version upgrade mode. $this->_storage->plugin_last_version = $this->_storage->plugin_version; $this->_storage->plugin_version = $plugin_version; if ( empty( $this->_storage->plugin_last_version ) || version_compare( $this->_storage->plugin_last_version, $plugin_version, '<' ) ) { $this->_storage->plugin_upgrade_mode = true; $this->_storage->plugin_downgrade_mode = false; } else { $this->_storage->plugin_downgrade_mode = true; $this->_storage->plugin_upgrade_mode = false; } if ( ! empty( $this->_storage->plugin_last_version ) ) { // Different version of the plugin was installed before, therefore it's an update. $this->_storage->is_plugin_new_install = false; } $this->do_action( 'plugin_version_update', $this->_storage->plugin_last_version, $plugin_version ); } } #-------------------------------------------------------------------------------- #region Data Migration on SDK Update #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 1.1.5 * * @param string $sdk_prev_version * @param string $sdk_version */ function _sdk_version_update( $sdk_prev_version, $sdk_version ) { if ( empty( $sdk_prev_version ) ) { return; } if ( version_compare( $sdk_prev_version, '2.5.1', '<' ) && version_compare( $sdk_version, '2.5.1', '>=' ) ) { if ( $this->is_registered( true ) ) { /** * Migrate to new permissions layer. */ require_once WP_FS__DIR_INCLUDES . '/supplements/fs-migration-2.5.1.php'; $install_by_blog_id = is_multisite() ? $this->get_blog_install_map() : array( 0 => $this->_site ); fs_migrate_251( $this, $install_by_blog_id ); } } } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param \FS_Storage $storage * @param bool|int|null $blog_id */ private static function migrate_install_plan_to_plan_id( FS_Storage $storage, $blog_id = null ) { if ( empty( $storage->sdk_version ) ) { // New installation of the plugin, no need to upgrade. return; } if ( ! version_compare( $storage->sdk_version, '2.0.0', '<' ) ) { // Previous version is >= 2.0.0, so no need to migrate. return; } // Alias. $module_type = $storage->get_module_type(); $module_slug = $storage->get_module_slug(); $installs = self::get_all_sites( $module_type, $blog_id ); $install = isset( $installs[ $module_slug ] ) ? $installs[ $module_slug ] : null; if ( ! is_object( $install ) ) { return; } if ( isset( $install->plan ) && is_object( $install->plan ) ) { if ( isset( $install->plan->id ) && ! empty( $install->plan->id ) ) { $install->plan_id = self::_decrypt( $install->plan->id ); } unset( $install->plan ); $installs[ $module_slug ] = clone $install; self::set_account_option_by_module( $module_type, 'sites', $installs, true, $blog_id ); } } /** * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @param string $plugin_prev_version * @param string $plugin_version */ function _after_version_update( $plugin_prev_version, $plugin_version ) { if ( $this->is_theme() ) { // Expire the cache of the previous tabs since the theme may // have setting updates. $this->_cache->expire( 'tabs' ); $this->_cache->expire( 'tabs_stylesheets' ); } } /** * A special migration logic for the $_accounts, executed for all the plugins in the system: * - Moves some data to the network level storage. * - If the plugin's connection was skipped for all sites, set the plugin as if it was network skipped. * - If the plugin's connection was ignored for all sites, don't do anything in terms of the network connection. * - If the plugin was connected to all sites by the same super-admin, set the plugin as if was network opted-in for all sites. * - If there's at least one site that was connected by a super-admin, find the "main super-admin" (the one that installed the majority of the plugin installs) and set the plugin as if was network activated with the main super-admin, set all the sites that were skipped or opted-in with a different user to delegated mode. Then, prompt the currently logged super-admin to choose what to do with the ignored sites. * - If there are any sites in the network which the connection decision was not yet taken for, set this plugin into network activation mode so a super-admin can choose what to do with the rest of the sites. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ private static function migrate_accounts_to_network() { $sites = self::get_sites(); $sites_count = count( $sites ); $connection_status = array(); $plugin_slugs = array(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); self::$_accounts->migrate_to_network( $blog_id ); /** * Build a list of all Freemius powered plugins slugs. */ $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array(), $blog_id ); foreach ( $id_slug_type_path_map as $module_id => $data ) { if ( WP_FS__MODULE_TYPE_PLUGIN === $data['type'] ) { $plugin_slugs[ $data['slug'] ] = true; } } $installs = self::get_account_option( 'sites', WP_FS__MODULE_TYPE_PLUGIN, $blog_id ); if ( is_array( $installs ) ) { foreach ( $installs as $slug => $install ) { if ( ! isset( $connection_status[ $slug ] ) ) { $connection_status[ $slug ] = array(); } if ( is_object( $install ) && FS_Site::is_valid_id( $install->id ) && FS_User::is_valid_id( $install->user_id ) ) { $connection_status[ $slug ][ $blog_id ] = $install->user_id; } } } } foreach ( $plugin_slugs as $slug => $true ) { if ( ! isset( $connection_status[ $slug ] ) ) { $connection_status[ $slug ] = array(); } foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); if ( isset( $connection_status[ $slug ][ $blog_id ] ) ) { continue; } $storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); $is_anonymous = $storage->get( 'is_anonymous', null, $blog_id ); if ( ! is_null( $is_anonymous ) ) { // Since 1.1.3 is_anonymous is an array. if ( is_array( $is_anonymous ) && isset( $is_anonymous['is'] ) ) { $is_anonymous = $is_anonymous['is']; } if ( is_bool( $is_anonymous ) && true === $is_anonymous ) { $connection_status[ $slug ][ $blog_id ] = 'skipped'; } } if ( ! isset( $connection_status[ $slug ][ $blog_id ] ) ) { $connection_status[ $slug ][ $blog_id ] = 'ignored'; } } } $super_admins = array(); foreach ( $connection_status as $slug => $blogs_status ) { $skips = 0; $ignores = 0; $connections = 0; $opted_in_users = array(); $opted_in_super_admins = array(); $storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); foreach ( $blogs_status as $blog_id => $status_or_user_id ) { if ( 'skipped' === $status_or_user_id ) { $skips ++; } else if ( 'ignored' === $status_or_user_id ) { $ignores ++; } else if ( FS_User::is_valid_id( $status_or_user_id ) ) { $connections ++; if ( ! isset( $opted_in_users[ $status_or_user_id ] ) ) { $opted_in_users[ $status_or_user_id ] = array(); } $opted_in_users[ $status_or_user_id ][] = $blog_id; if ( isset( $super_admins[ $status_or_user_id ] ) || self::is_super_admin( $status_or_user_id ) ) { // Cache super-admin data. $super_admins[ $status_or_user_id ] = true; // Remember opted-in super-admins for the plugin. $opted_in_super_admins[ $status_or_user_id ] = true; } } } $main_super_admin_user_id = null; $all_migrated = false; if ( $sites_count == $skips ) { // All sites were skipped -> network skip by copying the anonymous mode from any of the sites. $storage->is_anonymous_ms = $storage->is_anonymous; $all_migrated = true; } else if ( $sites_count == $ignores ) { // Don't do anything, still in activation mode. $all_migrated = true; } else if ( 0 < count( $opted_in_super_admins ) ) { // Find the super-admin with the majority of installs. $max_installs_by_super_admin = 0; foreach ( $opted_in_super_admins as $user_id => $true ) { $installs_count = count( $opted_in_users[ $user_id ] ); if ( $installs_count > $max_installs_by_super_admin ) { $max_installs_by_super_admin = $installs_count; $main_super_admin_user_id = $user_id; } } if ( $sites_count == $connections && 1 == count( $opted_in_super_admins ) ) { // Super-admin opted-in for all sites in the network. $storage->is_network_connected = true; $all_migrated = true; } // Store network user. $storage->network_user_id = $main_super_admin_user_id; $storage->network_install_blog_id = ( $sites_count == $connections ) ? // Since all sites are opted-in, associating with the main site. get_current_blog_id() : // Associating with the 1st found opted-in site. $opted_in_users[ $main_super_admin_user_id ][0]; /** * Make sure we migrate the plan ID of the network install, otherwise, if after the migration * the 1st page that will be loaded is the network level WP Admin and $storage->network_install_blog_id * is different than the main site of the network, the $this->_site will not be set since the plan_id * will be empty. */ $storage->migrate_to_network(); self::migrate_install_plan_to_plan_id( $storage, $storage->network_install_blog_id ); } else { // At least one opt-in. All the opt-in were created by a non-super-admin. if ( 0 == $ignores ) { // All sites were opted-in or skipped, all by non-super-admin. So delegate all. $storage->store( 'is_delegated_connection', true, true ); $all_migrated = true; } } if ( ! $all_migrated ) { /** * Delegate all sites that were: * 1) Opted-in by a user that is NOT the main-super-admin. * 2) Skipped and non of the sites was opted-in by a super-admin. If any site was opted-in by a super-admin, there will be a main-super-admin, and we consider the skip as if it was done by that user. */ foreach ( $blogs_status as $blog_id => $status_or_user_id ) { if ( $status_or_user_id == $main_super_admin_user_id ) { continue; } if ( FS_User::is_valid_id( $status_or_user_id ) || ( 'skipped' === $status_or_user_id && is_null( $main_super_admin_user_id ) ) ) { $storage->store( 'is_delegated_connection', true, $blog_id ); } } } if ( ( $connections + $skips > 0 ) ) { if ( $ignores > 0 ) { /** * If admin already opted-in or skipped in any of the network sites, and also * have sites which the connection decision was not yet taken, set this plugin * into network activation mode so the super-admin can choose what to do with * the rest of the sites. */ self::set_network_upgrade_mode( $storage ); } } } } /** * Set a module into network upgrade mode. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_Storage $storage * * @return bool */ private static function set_network_upgrade_mode( FS_Storage $storage ) { return $storage->is_network_activation = true; } /** * Will return true after upgrading to the SDK with the network level integration, * when the super-admin involvement is required regarding the rest of the sites. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return bool */ function is_network_upgrade_mode() { return $this->_storage->get( 'is_network_activation' ); } /** * Clear flag after the upgrade mode completion. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return bool True if network activation was on and now completed. */ private function network_upgrade_mode_completed() { if ( fs_is_network_admin() && $this->is_network_upgrade_mode() ) { $this->_storage->remove( 'is_network_activation' ); return true; } return false; } #endregion /** * This action is connected to the 'plugins_loaded' hook and helps to determine * if this is a new plugin installation or a plugin update. * * There are 3 different use-cases: * 1) New plugin installation right with Freemius: * 1.1 _activate_plugin_event_hook() will be executed first * 1.2 Since $this->_storage->is_plugin_new_install is not set, * and $this->_storage->plugin_last_version is not set, * $this->_storage->is_plugin_new_install will be set to TRUE. * 1.3 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will * be already set to TRUE. * * 2) Plugin update, didn't have Freemius before, and now have the SDK: * 2.1 _activate_plugin_event_hook() will not be executed, because * the activation hook do NOT fires on updates since WP 3.1. * 2.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will * be empty, therefore, it will be set to FALSE. * * 3) Plugin update, had Freemius in prev version as well: * 3.1 _version_updates_handler() will be executed 1st, since FS was installed * before, $this->_storage->plugin_last_version will NOT be empty, * therefore, $this->_storage->is_plugin_new_install will be set to FALSE. * 3.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install is * already set, therefore, it will not be modified. * * Use-case #3 is backward compatible, #3.1 will be executed since 1.0.9. * * NOTE: * The only fallback of this mechanism is if an admin updates a plugin based on use-case #2, * and then, the next immediate PageView is the plugin's main settings page, it will not * show the opt-in right away. The reason it will happen is because Freemius execution * will be turned off till the plugin is fully loaded at least once * (till $this->_storage->was_plugin_loaded is TRUE). * * @author Vova Feldman (@svovaf) * @since 1.1.9 * */ function _plugins_loaded() { // Update flag that plugin was loaded with Freemius at least once. $this->_storage->was_plugin_loaded = true; /** * Bug fix - only set to false when it's a plugin, due to the * execution sequence of the theme hooks and our methods, if * this will be set for themes, Freemius will always assume * it's a theme update. * * @author Vova Feldman (@svovaf) * @since 1.2.2.2 */ if ( $this->is_plugin() && ! isset( $this->_storage->is_plugin_new_install ) ) { $this->_storage->is_plugin_new_install = ( ! is_plugin_active( $this->_plugin_basename ) && empty( $this->_storage->plugin_last_version ) ); } } /** * Opens the support forum subemenu item in a new browser page. * * @author Vova Feldman (@svovaf) * @since 2.1.4 */ static function _open_support_forum_in_new_page() { ?> _logger->entrance(); if ( is_admin() ) { add_action( 'admin_init', array( &$this, '_hook_action_links_and_register_account_hooks' ) ); if ( $this->is_plugin() ) { if ( self::is_plugin_install_page() && true !== fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) { /** * Unless the `fs_allow_updater_and_dialog` URL param exists and its value is `true`, make * Freemius-related updates unavailable on the "Add Plugins" admin page (/plugin-install.php) * so that they won't interfere with the .org plugins' functionalities on that page (e.g. * updating of a .org plugin). */ add_filter( 'site_transient_update_plugins', array( 'Freemius', '_remove_fs_updates_from_plugin_install_page' ), 10, 2 ); } else if ( self::is_plugins_page() || self::is_updates_page() ) { /** * On the "Plugins" and "Updates" admin pages, if there are premium or non–org-compliant plugins, modify their details dialog URLs (add a Freemius-specific param) so that the SDK can determine if the plugin information dialog should show information from Freemius. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ add_action( 'admin_footer', array( 'Freemius', '_prepend_fs_allow_updater_and_dialog_flag_url_param' ) ); } $plugin_dir = dirname( $this->_plugin_dir_path ) . '/'; /** * @since 1.2.2 * * Hook to both free and premium version activations to support * auto deactivation on the other version activation. */ register_activation_hook( $plugin_dir . $this->_free_plugin_basename, array( &$this, '_activate_plugin_event_hook' ) ); register_activation_hook( $plugin_dir . $this->premium_plugin_basename(), array( &$this, '_activate_plugin_event_hook' ) ); } else { add_action( 'after_switch_theme', array( &$this, '_activate_theme_event_hook' ), 10, 2 ); add_action( 'admin_footer', array( &$this, '_style_premium_theme' ) ); } /** * Part of the mechanism to identify new plugin install vs. plugin update. * * @author Vova Feldman (@svovaf) * @since 1.1.9 */ if ( empty( $this->_storage->was_plugin_loaded ) ) { /** * During the plugin activation (not theme), 'plugins_loaded' will be already executed * when the logic gets here since the activation logic first add the activate plugins, * then triggers 'plugins_loaded', and only then include the code of the plugin that * is activated. Which means that _plugins_loaded() will NOT be executed during the * plugin activation, and that IS intentional. * * @author Vova Feldman (@svovaf) */ if ( $this->is_plugin() && $this->is_activation_mode( false ) && 0 == did_action( 'plugins_loaded' ) ) { add_action( 'plugins_loaded', array( &$this, '_plugins_loaded' ) ); } else { // If was activated before, then it was already loaded before. $this->_plugins_loaded(); } } if ( ! self::is_ajax() ) { if ( ! $this->is_addon() ) { add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY ); } } if ( $this->_storage->handle_gdpr_admin_notice ) { add_action( 'init', array( &$this, '_maybe_show_gdpr_admin_notice' ) ); } add_action( 'init', array( &$this, '_maybe_add_gdpr_optin_ajax_handler') ); add_action( 'init', array( &$this, '_maybe_add_pricing_ajax_handler' ) ); } if ( $this->is_plugin() ) { 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' ) ); } if ( is_multisite() ) { 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' ) ); 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' ) ); add_action( 'make_ham_blog', array( &$this, '_after_site_reactivated_callback' ) ); } if ( $this->is_theme() && self::is_customizer() && $this->apply_filters( 'show_customizer_upsell', true ) ) { // Register customizer upsell. add_action( 'customize_register', array( &$this, '_customizer_register' ) ); } add_action( 'admin_init', array( &$this, '_redirect_on_clicked_menu_link' ), WP_FS__LOWEST_PRIORITY ); if ( $this->is_theme() && ! $this->is_migration() ) { add_action( 'admin_init', array( &$this, '_add_tracking_links' ) ); } add_action( 'admin_init', array( &$this, '_add_license_activation' ) ); 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' ) ); $this->add_ajax_action( 'set_data_debug_mode', array( &$this, '_set_data_debug_mode' ) ); $this->add_ajax_action( 'toggle_whitelabel_mode', array( &$this, '_toggle_whitelabel_mode_ajax_handler' ) ); if ( $this->_is_network_active && fs_is_network_admin() ) { $this->add_ajax_action( 'network_activate', array( &$this, '_network_activate_ajax_action' ) ); } $this->add_ajax_action( 'install_premium_version', array( &$this, '_install_premium_version_ajax_action' ) ); $this->add_ajax_action( 'submit_affiliate_application', array( &$this, '_submit_affiliate_application' ) ); $this->add_action( 'after_plans_sync', array( &$this, '_check_for_trial_plans' ) ); $this->add_action( 'sdk_version_update', array( &$this, '_sdk_version_update' ), WP_FS__DEFAULT_PRIORITY, 2 ); $this->add_action( 'plugin_version_update', array( &$this, '_after_version_update' ), WP_FS__DEFAULT_PRIORITY, 2 ); $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_enqueue_scripts', array( &$this, '_enqueue_common_css' ) ); /** * 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' ) || 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' ) ); } } /** * Register the required hooks right after the settings parse is completed. * * @author Vova Feldman (@svovaf) * @since 2.3.1 */ private function register_after_settings_parse_hooks() { if ( is_admin() && $this->is_theme() && $this->is_premium() && ! $this->has_active_valid_license() ) { $this->add_ajax_action( 'delete_theme_update_data', array( &$this, '_delete_theme_update_data_action' ) ); } if ( $this->show_settings_with_tabs() ) { /** * Include the required hooks to capture the theme settings' page tabs * and cache them. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ if ( ! $this->_cache->has_valid( 'tabs' ) ) { add_action( 'admin_footer', array( &$this, '_tabs_capture' ) ); // Add license activation AJAX callback. $this->add_ajax_action( 'store_tabs', array( &$this, '_store_tabs_ajax_action' ) ); add_action( 'admin_enqueue_scripts', array( &$this, '_store_tabs_styles' ), 9999999 ); } add_action( 'admin_footer', array( &$this, '_add_freemius_tabs' ), /** * The tabs JS code must be executed after the tabs capture logic (_tabs_capture()). * That's why the priority is 11 while the tabs capture logic is added * with priority 10. * * @author Vova Feldman (@svovaf) */ 11 ); } if ( ! self::is_ajax() ) { if ( ! $this->is_addon() || $this->is_only_premium() ) { add_action( ( $this->_is_network_active && fs_is_network_admin() ? 'network_' : '' ) . 'admin_menu', array( &$this, '_prepare_admin_menu' ), WP_FS__LOWEST_PRIORITY ); } } } /** * Makes Freemius-related updates unavailable on the "Add Plugins" admin page (/plugin-install.php) so that * they won't interfere with the .org plugins' functionalities on that page (e.g. updating of a .org plugin). * * @author Leo Fajardo (@leorw) * @since 2.2.3 * * @param object $updates * @param string|null $transient * * @return object */ static function _remove_fs_updates_from_plugin_install_page( $updates, $transient = null ) { if ( is_object( $updates ) && isset( $updates->response ) ) { foreach ( $updates->response as $file => $plugin ) { if ( isset( $plugin->package ) && false !== strpos( $plugin->package, 'api.freemius' ) ) { unset( $updates->response[ $file ] ); } } } return $updates; } /** * Prepends the `fs_allow_updater_and_dialog` param to the plugin information URLs to tell the SDK to handle * the information that is shown on the plugin details dialog that is shown when the relevant link is clicked. * * @author Leo Fajardo (@leorw) * @since 2.2.3 * * @return string */ static function _prepend_fs_allow_updater_and_dialog_flag_url_param() { $slug_basename_map = array(); foreach ( self::$_instances as $instance ) { if ( ! $instance->is_plugin() ) { continue; } $slug_basename_map[ $instance->get_slug() ] = $instance->premium_plugin_basename(); } ?> is_beta() ) { $has_any_beta_version = true; break; } } if ( $has_any_beta_version ) { fs_enqueue_local_style( 'fs_plugins', '/admin/plugins.css' ); } } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 */ static function _maybe_add_beta_label_to_plugins_and_handle_confirmation() { $beta_data = array(); foreach ( self::$_instances as $instance ) { if ( ! $instance->is_premium() ) { continue; } /** * If there's an available beta version update, a confirmation message will be shown when the * "Update now" link on the "Plugins" or "Themes" page is clicked. */ $has_beta_update = $instance->has_beta_update(); $is_beta = ( // The "Beta" label is added separately for themes. $instance->is_plugin() && $instance->is_beta() ); if ( ! $is_beta && ! $has_beta_update ) { continue; } $beta_data[ $instance->get_plugin_basename() ] = array( 'is_installed_version_beta' => $is_beta ); if ( ! $has_beta_update ) { continue; } $beta_data[ $instance->get_plugin_basename() ]['beta_version_update_confirmation_message'] = sprintf( '%s %s', sprintf( fs_esc_attr_inline( 'An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.', 'beta-version-update-caution', $instance->get_slug() ), $instance->get_plugin_title() ), fs_esc_attr_inline( 'Would you like to proceed with the update?', 'update-confirmation', $instance->get_slug() ) ); } if ( empty( $beta_data ) ) { return; } ?> _free_plugin_basename ] ); unset( $uninstallable_plugins[ $this->premium_plugin_basename() ] ); update_option( 'uninstall_plugins', $uninstallable_plugins ); } /** * @since 1.2.0 Invalidate module's main file cache, otherwise, FS_Plugin_Updater will not fetch updates. * * @param bool $store_prev_path */ private function clear_module_main_file_cache( $store_prev_path = true ) { if ( ! isset( $this->_storage->plugin_main_file ) || empty( $this->_storage->plugin_main_file->path ) ) { return; } if ( ! $store_prev_path ) { /** * Storing the previous path is not needed when clearing the cache after an SDK version update since * the main purpose of the cache clearing in that event is to correct a wrong plugin main file path * which causes 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 */ unset( $this->_storage->plugin_main_file->path ); } else { $plugin_main_file = clone $this->_storage->plugin_main_file; // Store cached path (2nd layer cache). $plugin_main_file->prev_path = $plugin_main_file->path; // Clear cached path. unset( $plugin_main_file->path ); $this->_storage->plugin_main_file = $plugin_main_file; } /** * Clear global cached path. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map' ); unset( $id_slug_type_path_map[ $this->_module_id ]['path'] ); self::$_accounts->set_option( 'id_slug_type_path_map', $id_slug_type_path_map, true ); } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 */ function _hook_action_links_and_register_account_hooks() { if ( $this->is_migration() ) { return; } 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(); } $this->_register_account_hooks(); } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 */ private function _register_account_hooks() { if ( ! is_admin() ) { return; } /** * Always show the deactivation feedback form since we added * automatic free version deactivation upon premium code activation. * * @since 1.2.1.6 */ $this->add_ajax_action( 'submit_uninstall_reason', array( &$this, '_submit_uninstall_reason_action' ) ); $this->add_ajax_action( 'cancel_subscription_or_trial', array( &$this, 'cancel_subscription_or_trial_ajax_action' ) ); if ( ! $this->is_addon() || $this->is_parent_plugin_installed() ) { if ( ( $this->is_plugin() && self::is_plugins_page() ) || ( $this->is_theme() && self::is_themes_page() ) ) { add_action( 'admin_footer', array( &$this, '_add_deactivation_feedback_dialog_box' ) ); } } } /** * Leverage backtrace to find caller plugin file path. * * @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, $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; } } } /** * @since 1.2.1 * * `clear_module_main_file_cache()` is clearing the plugin's cached path on * deactivation. Therefore, if any plugin/theme was initiating `Freemius` * with that plugin's slug, it was overriding the empty plugin path with a wrong path. * * So, we've added a special mechanism with a 2nd layer of cache that uses `prev_path` * when the class instantiator isn't the module. */ if ( ! $is_init ) { // Fetch prev path cache. if ( isset( $this->_storage->plugin_main_file ) && ! empty( $this->_storage->plugin_main_file->prev_path ) ) { $absolute_path = $this->get_absolute_path( $this->_storage->plugin_main_file->prev_path ); if ( file_exists( $absolute_path ) ) { return $absolute_path; } } wp_die( $this->get_text_inline( 'Freemius SDK couldn\'t find the plugin\'s main file. Please contact sdk@freemius.com with the current error.', 'failed-finding-main-path' ) . " Module: {$this->_slug}; SDK: " . WP_FS__SDK_VERSION . ";", $this->get_text_inline( 'Error', 'error' ), array( 'back_link' => true ) ); } /** * @since 1.2.1 * * Only the original instantiator that calls dynamic_init can modify the module's path. */ // Find caller module. $this->_storage->plugin_main_file = (object) array( 'path' => $main_file, ); return $this->get_absolute_path( $main_file ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.3 * * @param string $path * * @return string */ private function get_relative_path( $path ) { $module_root_dir = $this->get_module_root_dir_path(); if ( 0 === strpos( $path, $module_root_dir ) ) { $path = substr( $path, strlen( $module_root_dir ) ); } return $path; } /** * @author Leo Fajardo (@leorw) * @since 1.2.3 * * @param string $path * @param string|bool $module_type * * @return string */ private function get_absolute_path( $path, $module_type = false ) { $module_root_dir = $this->get_module_root_dir_path( $module_type ); if ( 0 !== strpos( $path, $module_root_dir ) ) { $path = fs_normalize_path( $module_root_dir . $path ); } return $path; } /** * @author Leo Fajardo (@leorw) * @since 1.2.3 * * @param string|bool $module_type * * @return string */ private function get_module_root_dir_path( $module_type = false ) { $is_plugin = empty( $module_type ) ? $this->is_plugin() : ( WP_FS__MODULE_TYPE_PLUGIN === $module_type ); return fs_normalize_path( trailingslashit( $is_plugin ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) ) ); } /** * @author Leo Fajardo (@leorw) * * @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 ) { $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); $store_option = false; if ( ! isset( $id_slug_type_path_map[ $module_id ] ) ) { $id_slug_type_path_map[ $module_id ] = array( 'slug' => $slug ); $store_option = true; } else if ( isset( $id_slug_type_path_map[ $module_id ]['slug'] ) && $slug !== $id_slug_type_path_map[ $module_id ]['slug'] ) { $id_slug_type_path_map[ $module_id ]['slug'] = $slug; $store_option = true; } $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; $store_option = true; } 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']; } /** * Identifies the caller type: plugin or theme. * * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @author Vova Feldman (@svovaf) * @since 1.2.2.3 Find the earliest module in the call stack that calls to the SDK. This fix is for cases when * 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( $module_id ) { self::require_plugin_essentials(); $all_plugins = fs_get_plugins( true ); $all_plugins_paths = array(); // Get active plugin's main files real full names (might be symlinks). foreach ( $all_plugins as $relative_path => $data ) { if ( false === strpos( fs_normalize_path( $relative_path ), '/' ) ) { /** * Ignore plugins that don't have a folder (e.g. Hello Dolly) since they * can't really include the SDK. * * @author Vova Feldman * @since 1.2.1.7 */ continue; } $all_plugins_paths[] = fs_normalize_path( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ); } $caller_file_candidate = false; $caller_map = array(); $module_type = WP_FS__MODULE_TYPE_PLUGIN; $themes_dir = fs_normalize_path( get_theme_root( get_stylesheet() ) ); $plugin_dir_to_skip = false; for ( $i = 1, $bt = debug_backtrace(), $len = count( $bt ); $i < $len; $i ++ ) { if ( empty( $bt[ $i ]['file'] ) ) { continue; } if ( $i > 1 && ! empty( $bt[ $i - 1 ]['file'] ) && $bt[ $i ]['file'] === $bt[ $i - 1 ]['file'] ) { // If file same as the prev file in the stack, skip it. continue; } if ( ! empty( $bt[ $i ]['function'] ) && in_array( $bt[ $i ]['function'], array( 'do_action', 'apply_filter', // The string split is stupid, but otherwise, theme check // throws info notices. 'requir' . 'e_once', 'requir' . 'e', 'includ' . 'e_once', 'includ' . 'e', 'install_and_activate_plugin', 'try_activate_plugin', 'activate_plugin' ) ) ) { if ( 'activate_plugin' === $bt[ $i ]['function'] ) { /** * Store the directory of the activator plugin so that any other file that starts with it * cannot be mistakenly chosen as a candidate caller file. * * @author Leo Fajardo * * @since 2.3.0 */ $caller_file_path = fs_normalize_path( $bt[ $i ]['file'] ); foreach ( $all_plugins_paths as $plugin_path ) { $plugin_dir = fs_normalize_path( dirname( $plugin_path ) . '/' ); if ( false !== strpos( $caller_file_path, $plugin_dir ) ) { $plugin_dir_to_skip = $plugin_dir; break; } } } // Ignore call stack hooks and files inclusion. continue; } $caller_file_path = fs_normalize_path( $bt[ $i ]['file'] ); if ( ! empty( $plugin_dir_to_skip ) ) { /** * Skip if it's an activator plugin file to avoid mistakenly choosing it as a candidate caller file. * * @author Leo Fajardo * * @since 2.3.0 */ if ( 0 === strpos( $caller_file_path, $plugin_dir_to_skip ) ) { continue; } } if ( 'functions.php' === basename( $caller_file_path ) ) { /** * 1. Assumes that theme's starting execution file is functions.php. * 2. This complex logic fixes symlink issues (e.g. with Vargant). * * @author Vova Feldman (@svovaf) * @since 1.2.2.5 */ if ( $caller_file_path == fs_normalize_path( realpath( trailingslashit( $themes_dir ) . basename( dirname( $caller_file_path ) ) . '/' . basename( $caller_file_path ) ) ) ) { $module_type = WP_FS__MODULE_TYPE_THEME; /** * Relative path of the theme, e.g.: * `my-theme/functions.php` * * @author Leo Fajardo (@leorw) */ $caller_file_candidate = basename( dirname( $caller_file_path ) ) . '/' . basename( $caller_file_path ); continue; } } $caller_file_hash = md5( $caller_file_path ); if ( ! isset( $caller_map[ $caller_file_hash ] ) ) { foreach ( $all_plugins_paths as $plugin_path ) { if ( empty( $plugin_path ) ) { continue; } if ( false !== strpos( $caller_file_path, fs_normalize_path( dirname( $plugin_path ) . '/' ) ) ) { $caller_map[ $caller_file_hash ] = fs_normalize_path( $plugin_path ); break; } } } if ( isset( $caller_map[ $caller_file_hash ] ) ) { $module_type = WP_FS__MODULE_TYPE_PLUGIN; $caller_file_candidate = plugin_basename( $caller_map[ $caller_file_hash ] ); } } $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 ); } #---------------------------------------------------------------------------------- #region Deactivation Feedback Form #---------------------------------------------------------------------------------- /** * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins * page. * * @author Vova Feldman (@svovaf) * @author Leo Fajardo (@leorw) * * @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(); /** * @since 2.3.0 Developers can optionally hide the deactivation feedback form using the 'show_deactivation_feedback_form' filter. */ $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() ) { /** * If the add-on's 'show_deactivation_feedback_form' is not set, try to inherit the value from the parent. */ $show_deactivation_feedback_form = $this->get_parent_instance()->apply_filters( 'show_deactivation_feedback_form', true ); } $uninstall_confirmation_message = $this->apply_filters( 'uninstall_confirmation_message', '' ); if ( empty( $subscription_cancellation_dialog_box_template_params ) && ! $show_deactivation_feedback_form && empty( $uninstall_confirmation_message ) ) { return; } $vars = array( 'id' => $this->_module_id ); if ( $show_deactivation_feedback_form ) { /* Check the type of user: * 1. Long-term (long-term) * 2. Non-registered and non-anonymous short-term (non-registered-and-non-anonymous-short-term). * 3. Short-term (short-term) */ $is_long_term_user = true; // Check if the site is at least 2 days old. $time_installed = $this->_storage->install_timestamp; // Difference in seconds. $date_diff = time() - $time_installed; // Convert seconds to days. $date_diff_days = floor( $date_diff / ( 60 * 60 * 24 ) ); if ( $date_diff_days < 2 ) { $is_long_term_user = false; } $is_long_term_user = $this->apply_filters( 'is_long_term_user', $is_long_term_user ); if ( $is_long_term_user ) { $user_type = 'long-term'; } else { if ( ! $this->is_registered() && ! $this->is_anonymous() ) { $user_type = 'non-registered-and-non-anonymous-short-term'; } else { $user_type = 'short-term'; } } $uninstall_reasons = $this->_get_uninstall_reasons( $user_type ); $vars['reasons'] = $uninstall_reasons; } $vars['subscription_cancellation_dialog_box_template_params'] = &$subscription_cancellation_dialog_box_template_params; $vars['show_deactivation_feedback_form'] = $show_deactivation_feedback_form; $vars['uninstall_confirmation_message'] = $uninstall_confirmation_message; /** * Load the HTML template for the deactivation feedback dialog box. * * @todo Deactivation form core functions should be loaded only once! Otherwise, when there are multiple Freemius powered plugins the same code is loaded multiple times. The only thing that should be loaded differently is the various deactivation reasons object based on the state of the plugin. */ fs_require_template( 'forms/deactivation/form.php', $vars ); } /** * @author Leo Fajardo (@leorw) * @since 1.1.2 * * @param string $user_type * * @return array The uninstall reasons for the specified user type. */ function _get_uninstall_reasons( $user_type = 'long-term' ) { $module_type = $this->_module_type; $internal_message_template_var = array( 'id' => $this->_module_id ); $plan = $this->get_plan(); if ( $this->is_registered() && is_object( $plan ) && $plan->has_technical_support() ) { $contact_support_template = fs_get_template( 'forms/deactivation/contact.php', $internal_message_template_var ); } else { $contact_support_template = ''; } $reason_found_better_plugin = array( 'id' => self::REASON_FOUND_A_BETTER_PLUGIN, 'text' => sprintf( $this->get_text_inline( 'I found a better %s', 'reason-found-a-better-plugin' ), $module_type ), 'input_type' => 'textfield', 'input_placeholder' => sprintf( $this->get_text_inline( "What's the %s's name?", 'placeholder-plugin-name' ), $module_type ), ); $reason_temporary_deactivation = array( 'id' => self::REASON_TEMPORARY_DEACTIVATION, 'text' => sprintf( $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' ) ) ), 'input_type' => '', 'input_placeholder' => '' ); $reason_other = array( 'id' => self::REASON_OTHER, 'text' => $this->get_text_inline( 'Other', 'reason-other' ), 'input_type' => 'textfield', 'input_placeholder' => '' ); $long_term_user_reasons = array( array( 'id' => self::REASON_NO_LONGER_NEEDED, 'text' => sprintf( $this->get_text_inline( 'I no longer need the %s', 'reason-no-longer-needed' ), $module_type ), 'input_type' => '', 'input_placeholder' => '' ), $reason_found_better_plugin, array( 'id' => self::REASON_NEEDED_FOR_A_SHORT_PERIOD, 'text' => sprintf( $this->get_text_inline( 'I only needed the %s for a short period', 'reason-needed-for-a-short-period' ), $module_type ), 'input_type' => '', 'input_placeholder' => '' ), array( 'id' => self::REASON_BROKE_MY_SITE, 'text' => sprintf( $this->get_text_inline( 'The %s broke my site', 'reason-broke-my-site' ), $module_type ), 'input_type' => '', 'input_placeholder' => '', 'internal_message' => $contact_support_template ), array( 'id' => self::REASON_SUDDENLY_STOPPED_WORKING, 'text' => sprintf( $this->get_text_inline( 'The %s suddenly stopped working', 'reason-suddenly-stopped-working' ), $module_type ), 'input_type' => '', 'input_placeholder' => '', 'internal_message' => $contact_support_template ) ); if ( $this->is_paying() ) { $long_term_user_reasons[] = array( 'id' => self::REASON_CANT_PAY_ANYMORE, 'text' => $this->get_text_inline( "I can't pay for it anymore", 'reason-cant-pay-anymore' ), 'input_type' => 'textfield', 'input_placeholder' => $this->get_text_inline( 'What price would you feel comfortable paying?', 'placeholder-comfortable-price' ) ); } $reason_dont_share_info = array( 'id' => self::REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION, 'text' => $this->get_text_inline( "I don't like to share my information with you", 'reason-dont-like-to-share-my-information' ), 'input_type' => '', 'input_placeholder' => '' ); /** * If the current user has selected the "don't share data" reason in the deactivation feedback modal, inform the * user by showing additional message that he doesn't have to share data and can just choose to skip the opt-in * (the Skip button is included in the message to show). This message will only be shown if anonymous mode is * enabled and the user's account is currently not in pending activation state (similar to the way the Skip * button in the opt-in form is shown/hidden). */ if ( $this->is_enable_anonymous() && ! $this->is_pending_activation() ) { $reason_dont_share_info['internal_message'] = fs_get_template( 'forms/deactivation/retry-skip.php', $internal_message_template_var ); } $uninstall_reasons = array( 'long-term' => $long_term_user_reasons, 'non-registered-and-non-anonymous-short-term' => array( array( 'id' => self::REASON_DIDNT_WORK, 'text' => sprintf( $this->get_text_inline( "The %s didn't work", 'reason-didnt-work' ), $module_type ), 'input_type' => '', 'input_placeholder' => '' ), $reason_dont_share_info, $reason_found_better_plugin ), 'short-term' => array( array( 'id' => self::REASON_COULDNT_MAKE_IT_WORK, 'text' => $this->get_text_inline( "I couldn't understand how to make it work", 'reason-couldnt-make-it-work' ), 'input_type' => '', 'input_placeholder' => '', 'internal_message' => $contact_support_template ), $reason_found_better_plugin, array( 'id' => self::REASON_GREAT_BUT_NEED_SPECIFIC_FEATURE, 'text' => sprintf( $this->get_text_inline( "The %s is great, but I need specific feature that you don't support", 'reason-great-but-need-specific-feature' ), $module_type ), 'input_type' => 'textarea', 'input_placeholder' => $this->get_text_inline( 'What feature?', 'placeholder-feature' ) ), array( 'id' => self::REASON_NOT_WORKING, 'text' => sprintf( $this->get_text_inline( 'The %s is not working', 'reason-not-working' ), $module_type ), 'input_type' => 'textarea', 'input_placeholder' => $this->get_text_inline( "Kindly share what didn't work so we can fix it for future users...", 'placeholder-share-what-didnt-work' ) ), array( 'id' => self::REASON_NOT_WHAT_I_WAS_LOOKING_FOR, 'text' => $this->get_text_inline( "It's not what I was looking for", 'reason-not-what-i-was-looking-for' ), 'input_type' => 'textarea', 'input_placeholder' => $this->get_text_inline( "What you've been looking for?", 'placeholder-what-youve-been-looking-for' ) ), array( 'id' => self::REASON_DIDNT_WORK_AS_EXPECTED, 'text' => sprintf( $this->get_text_inline( "The %s didn't work as expected", 'reason-didnt-work-as-expected' ), $module_type ), 'input_type' => 'textarea', 'input_placeholder' => $this->get_text_inline( 'What did you expect?', 'placeholder-what-did-you-expect' ) ) ) ); // Randomize the reasons for the current user type. shuffle( $uninstall_reasons[ $user_type ] ); // Keep the following reasons as the last items in the list. $uninstall_reasons[ $user_type ][] = $reason_temporary_deactivation; $uninstall_reasons[ $user_type ][] = $reason_other; $uninstall_reasons = $this->apply_filters( 'uninstall_reasons', $uninstall_reasons ); return $uninstall_reasons[ $user_type ]; } /** * Called after the user has submitted his reason for deactivating the plugin. * * @author Leo Fajardo (@leorw) * @since 1.1.2 */ function _submit_uninstall_reason_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'submit_uninstall_reason' ); $reason_id = fs_request_get( 'reason_id' ); // Check if the given reason ID is an unsigned integer. if ( ! ctype_digit( $reason_id ) ) { exit; } $reason_info = trim( fs_request_get( 'reason_info', '' ) ); if ( ! empty( $reason_info ) ) { $reason_info = substr( $reason_info, 0, 128 ); } $reason = (object) array( 'id' => $reason_id, 'info' => $reason_info, 'is_anonymous' => fs_request_get_bool( 'is_anonymous' ) ); $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. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ if ( $this->is_theme() ) { if ( $this->is_premium() && ! $this->has_active_valid_license() ) { FS_Plugin_Updater::instance( $this )->delete_update_data(); } $this->_uninstall_plugin_event( false ); $this->remove_sdk_reference(); } // Print '1' for successful operation. echo 1; 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 */ function cancel_subscription_or_trial_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'cancel_subscription_or_trial' ); $result = $this->cancel_subscription_or_trial( fs_request_get( 'plugin_id', $this->get_id() ), false ); if ( $this->is_api_error( $result ) ) { $this->shoot_ajax_failure( $result->error->message ); } $this->shoot_ajax_success(); } /** * @author Leo Fajardo (@leorw) * @since 2.1.4 * * @param number $plugin_id * * @return object */ private function cancel_subscription_or_trial( $plugin_id ) { $fs = null; if ( $plugin_id == $this->get_id() ) { $fs = $this; } else if ( $this->is_addon_activated( $plugin_id ) ) { $fs = self::get_instance_by_id( $plugin_id ); } $result = null; if ( ! is_null( $fs ) ) { $result = $fs->is_paid_trial() ? $fs->_cancel_trial() : $fs->_downgrade_site(); } return $result; } /** * @author Leo Fajardo (@leorw) * @since 2.0.2 */ function _delete_theme_update_data_action() { FS_Plugin_Updater::instance( $this )->delete_update_data(); } #endregion #---------------------------------------------------------------------------------- #region Instance #---------------------------------------------------------------------------------- /** * Main singleton instance. * * @author Vova Feldman (@svovaf) * @since 1.0.0 * * @param number $module_id * @param string|bool $slug * @param bool $is_init Is initiation sequence. * * @return Freemius|false */ static function instance( $module_id, $slug = false, $is_init = false ) { if ( empty( $module_id ) ) { return false; } /** * Load the essential static data prior to initiating FS_Plugin_Manager since there's an essential MS network migration logic that needs to be executed prior to the initiation. */ self::_load_required_static(); if ( ! is_numeric( $module_id ) ) { if ( ! $is_init && true === $slug ) { $is_init = true; } $slug = $module_id; $module = FS_Plugin_Manager::instance( $slug )->get(); if ( is_object( $module ) ) { $module_id = $module->id; } } $key = 'm_' . $module_id; if ( ! isset( self::$_instances[ $key ] ) ) { self::$_instances[ $key ] = new Freemius( $module_id, $slug, $is_init ); } return self::$_instances[ $key ]; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param number $addon_id * * @return bool */ private static function has_instance( $addon_id ) { return isset( self::$_instances[ 'm_' . $addon_id ] ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @param string|number $id_or_slug * @param string $module_type * * @return number|false */ private static function get_module_id( $id_or_slug, $module_type = WP_FS__MODULE_TYPE_PLUGIN ) { if ( is_numeric( $id_or_slug ) ) { return $id_or_slug; } foreach ( self::$_instances as $instance ) { // Also check the module type since there can be a plugin and a theme with the same slug. if ( ( $module_type === $instance->get_module_type() ) && ( $id_or_slug === $instance->get_slug() ) ) { return $instance->get_id(); } } return false; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param number $id * * @return false|Freemius */ static function get_instance_by_id( $id ) { return isset ( self::$_instances[ 'm_' . $id ] ) ? self::$_instances[ 'm_' . $id ] : false; } /** * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param string $plugin_file * @param string $module_type * * @return false|Freemius */ static function get_instance_by_file( $plugin_file, $module_type = WP_FS__MODULE_TYPE_PLUGIN ) { $slug = self::find_slug_by_basename( $plugin_file ); return ( false !== $slug ) ? self::instance( self::get_module_id( $slug, $module_type ) ) : false; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return false|Freemius */ function get_parent_instance() { return self::get_instance_by_id( $this->_plugin->parent_plugin_id ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param string|number $id_or_slug * * @return false|Freemius */ function get_addon_instance( $id_or_slug ) { $addon_id = self::get_module_id( $id_or_slug ); return self::instance( $addon_id ); } /** * @return Freemius[] */ static function _get_all_instances() { return self::$_instances; } #endregion ------------------------------------------------------------------ /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool */ function is_parent_plugin_installed() { $is_active = self::has_instance( $this->_plugin->parent_plugin_id ); if ( $is_active ) { return true; } /** * Parent module might be a theme. If that's the case, the add-on's FS * instance will be loaded prior to the theme's FS instance, therefore, * we need to check if it's active with a "look ahead". * * @author Vova Feldman * @since 1.2.2.3 */ global $fs_active_plugins; if ( is_object( $fs_active_plugins ) && is_array( $fs_active_plugins->plugins ) ) { $active_theme = wp_get_theme(); foreach ( $fs_active_plugins->plugins as $sdk => $module ) { if ( WP_FS__MODULE_TYPE_THEME === $module->type ) { if ( $module->plugin_path == $active_theme->get_stylesheet() ) { // Parent module is a theme and it's currently active. return true; } } } } return false; } /** * Check if add-on parent plugin in activation mode. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool */ function is_parent_in_activation() { $parent_fs = $this->get_parent_instance(); if ( ! is_object( $parent_fs ) ) { return false; } return ( $parent_fs->is_activation_mode() ); } /** * Is plugin in activation mode. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param bool $and_on * * @return bool */ function is_activation_mode( $and_on = true ) { return fs_is_network_admin() ? $this->is_network_activation_mode( $and_on ) : $this->is_site_activation_mode( $and_on ); } /** * Is plugin in activation mode. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param bool $and_on * * @return bool */ function is_site_activation_mode( $and_on = true ) { return ( ( $this->is_on() || ! $and_on ) && ( ( $this->is_premium() && true === $this->_storage->require_license_activation ) || ( ( ! $this->is_registered() || ( $this->is_only_premium() && ! $this->has_features_enabled_license() ) ) && ( ! $this->is_enable_anonymous() || ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) ) ) ) ); } /** * Checks if the SDK in network activation mode. * * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param bool $and_on * * @return bool */ private function is_network_activation_mode( $and_on = true ) { if ( ! $this->_is_network_active ) { // Not network activated. return false; } if ( $this->is_network_upgrade_mode() ) { // Special flag to enforce network activation mode to decide what to do with the sites that are not yet opted-in nor skipped. return true; } if ( ! $this->is_site_activation_mode( $and_on ) ) { // Whether the context is single site or the network, if the plugin is no longer in activation mode then it is not in network activation mode as well. return false; } if ( $this->is_network_delegated_connection() ) { // Super-admin delegated the connection to the site admins -> not activation mode. return false; } if ( $this->is_network_anonymous() && true !== $this->_storage->require_license_activation ) { // Super-admin skipped the connection network wide -> not activation mode. return false; } if ( $this->is_network_registered() ) { // Super-admin connected at least one site -> not activation mode. return false; } return true; } /** * Check if current page is the opt-in/pending-activation page. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @return bool */ function is_activation_page() { if ( $this->_menu->is_activation_page( $this->show_opt_in_on_themes_page() ) ) { return true; } if ( ! $this->is_activation_mode() ) { return false; } // Check if current page is matching the activation page. return $this->is_matching_url( $this->get_activation_url() ); } /** * Check if URL path's are matching and that all querystring * arguments of the $sub_url exist in the $url with the same values. * * WARNING: * 1. This method doesn't check if the sub/domain are matching. * 2. Ignore case sensitivity. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $sub_url * @param string $url If argument is not set, check if the sub_url matching the current's page URL. * * @return bool */ private function is_matching_url( $sub_url, $url = '' ) { if ( empty( $url ) ) { $url = $_SERVER['REQUEST_URI']; } $url = strtolower( $url ); $sub_url = strtolower( $sub_url ); if ( parse_url( $sub_url, PHP_URL_PATH ) !== parse_url( $url, PHP_URL_PATH ) ) { // Different path - DO NOT OVERRIDE PAGE. return false; } $url_params = array(); parse_str( parse_url( $url, PHP_URL_QUERY ), $url_params ); $sub_url_params = array(); parse_str( parse_url( $sub_url, PHP_URL_QUERY ), $sub_url_params ); foreach ( $sub_url_params as $key => $val ) { if ( ! isset( $url_params[ $key ] ) || $val != $url_params[ $key ] ) { // Not matching query string - DO NOT OVERRIDE PAGE. return false; } } return true; } /** * Get the basenames of all active plugins for specific blog. Including network activated plugins. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id * * @return string[] */ private static function get_active_plugins_basenames( $blog_id = 0 ) { if ( is_multisite() && $blog_id > 0 ) { $active_basenames = get_blog_option( $blog_id, 'active_plugins' ); } else { $active_basenames = get_option( 'active_plugins' ); } if ( ! is_array( $active_basenames ) ) { $active_basenames = array(); } if ( is_multisite() ) { $network_active_basenames = get_site_option( 'active_sitewide_plugins' ); if ( is_array( $network_active_basenames ) && ! empty( $network_active_basenames ) ) { $active_basenames = array_merge( $active_basenames, array_keys( $network_active_basenames ) ); } } return $active_basenames; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param int $blog_id * * @return array */ static function get_active_plugins_directories_map( $blog_id = 0 ) { $active_basenames = self::get_active_plugins_basenames( $blog_id ); $map = array(); foreach ( $active_basenames as $active_basename ) { $active_basename = fs_normalize_path( $active_basename ); if ( false === strpos( $active_basename, '/' ) ) { continue; } $map[ dirname( $active_basename ) ] = true; } return $map; } /** * Get collection of all active plugins. Including network activated plugins. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param int $blog_id Since 2.0.0 * * @return array[string]array */ private static function get_active_plugins( $blog_id = 0 ) { self::require_plugin_essentials(); $active_plugin = array(); $all_plugins = fs_get_plugins(); $active_plugins_basenames = self::get_active_plugins_basenames( $blog_id ); foreach ( $active_plugins_basenames as $plugin_basename ) { $active_plugin[ $plugin_basename ] = $all_plugins[ $plugin_basename ]; } return $active_plugin; } /** * Get collection of all site active plugins for a specified blog. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id * * @return array[string]array */ private static function get_site_active_plugins( $blog_id = 0 ) { $active_basenames = ( is_multisite() && $blog_id > 0 ) ? get_blog_option( $blog_id, 'active_plugins' ) : get_option( 'active_plugins' ); $active = array(); if ( ! is_array( $active_basenames ) ) { return $active; } foreach ( $active_basenames as $basename ) { $active[ $basename ] = array( 'is_active' => true, 'Version' => '1.0', // Dummy version. 'slug' => self::get_plugin_slug( $basename ), ); } return $active; } /** * Get collection of all plugins with their activation status for a specified blog. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @param int $blog_id Since 2.0.0 * * @return array Key is the plugin file path and the value is an array of the plugin data. */ private static function get_all_plugins( $blog_id = 0 ) { self::require_plugin_essentials(); $all_plugins = fs_get_plugins(); $active_plugins_basenames = self::get_active_plugins_basenames( $blog_id ); foreach ( $all_plugins as $basename => &$data ) { // By default set to inactive (next foreach update the active plugins). $data['is_active'] = false; // Enrich with plugin slug. $data['slug'] = self::get_plugin_slug( $basename ); } // Flag active plugins. foreach ( $active_plugins_basenames as $basename ) { if ( isset( $all_plugins[ $basename ] ) ) { $all_plugins[ $basename ]['is_active'] = true; } } return $all_plugins; } /** * Get collection of all plugins and if they are network level activated. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return array Key is the plugin basename and the value is an array of the plugin data. */ private static function get_network_plugins() { self::require_plugin_essentials(); $all_plugins = fs_get_plugins(); $network_active_basenames = is_multisite() ? get_site_option( 'active_sitewide_plugins' ) : array(); foreach ( $all_plugins as $basename => &$data ) { // By default set to inactive (next foreach update the active plugins). $data['is_active'] = false; // Enrich with plugin slug. $data['slug'] = self::get_plugin_slug( $basename ); } // Flag active plugins. foreach ( $network_active_basenames as $basename ) { if ( isset( $all_plugins[ $basename ] ) ) { $all_plugins[ $basename ]['is_active'] = true; } } return $all_plugins; } /** * Cached result of get_site_transient( 'update_plugins' ) * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @var object */ private static $_plugins_info; /** * Helper function to get specified plugin's slug. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @param $basename * * @return string */ private static function get_plugin_slug( $basename ) { if ( ! isset( self::$_plugins_info ) ) { self::$_plugins_info = get_site_transient( 'update_plugins' ); } $slug = ''; if ( is_object( self::$_plugins_info ) ) { if ( isset( self::$_plugins_info->no_update ) && isset( self::$_plugins_info->no_update[ $basename ] ) && ! empty( self::$_plugins_info->no_update[ $basename ]->slug ) ) { $slug = self::$_plugins_info->no_update[ $basename ]->slug; } else if ( isset( self::$_plugins_info->response ) && isset( self::$_plugins_info->response[ $basename ] ) && ! empty( self::$_plugins_info->response[ $basename ]->slug ) ) { $slug = self::$_plugins_info->response[ $basename ]->slug; } } if ( empty( $slug ) ) { // Try to find slug from FS data. $slug = self::find_slug_by_basename( $basename ); } if ( empty( $slug ) ) { // Fallback to plugin's folder name. $slug = dirname( $basename ); } return $slug; } private static $_statics_loaded = false; /** * Load static resources. * * @author Vova Feldman (@svovaf) * @since 1.0.1 */ private static function _load_required_static() { if ( self::$_statics_loaded ) { return; } self::$_static_logger = FS_Logger::get_logger( WP_FS__SLUG, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); self::$_static_logger->entrance(); self::$_accounts = FS_Options::instance( WP_FS__ACCOUNTS_OPTION_NAME, true ); if ( is_multisite() ) { $has_skipped_migration = ( // 'id_slug_type_path_map' - was never stored on older versions, therefore, not exists on the site level. null === self::$_accounts->get_option( 'id_slug_type_path_map', null, false ) && // 'file_slug_map' stored on the site level, so it was running an SDK version before it was integrated with MS-network. null !== self::$_accounts->get_option( 'file_slug_map', null, false ) ); /** * If the file_slug_map exists on the site level but doesn't exist on the * network level storage, it means that we need to process the storage with migration. * * The code in this `if` scope will only be executed once and only for the first site that will execute it because once we migrate the storage data, file_slug_map will be already set in the network level storage. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ if ( ( $has_skipped_migration && true !== self::$_accounts->get_option( 'ms_migration_complete', false, true ) ) || ( null === self::$_accounts->get_option( 'file_slug_map', null, true ) && null !== self::$_accounts->get_option( 'file_slug_map', null, false ) ) ) { self::migrate_options_to_network(); } } self::$_global_admin_notices = FS_Admin_Notices::instance( 'global' ); if ( ! WP_FS__DEMO_MODE ) { add_action( ( fs_is_network_admin() ? 'network_' : '' ) . 'admin_menu', array( 'Freemius', '_add_debug_section' ) ); } add_action( "wp_ajax_fs_toggle_debug_mode", array( 'Freemius', '_toggle_debug_mode' ) ); self::add_ajax_action_static( 'get_debug_log', array( 'Freemius', '_get_debug_log' ) ); self::add_ajax_action_static( 'get_db_option', array( 'Freemius', '_get_db_option' ) ); self::add_ajax_action_static( 'set_db_option', array( 'Freemius', '_set_db_option' ) ); if ( 0 == did_action( 'plugins_loaded' ) ) { add_action( 'plugins_loaded', array( 'Freemius', '_load_textdomain' ), 1 ); } $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() ) { add_action( 'admin_print_footer_scripts', array( 'Freemius', '_maybe_add_beta_label_styles' ), 9 ); /** * Specifically use this hook so that the JS event handlers will work properly on the "Themes" * page. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ add_action( 'admin_footer-' . self::get_current_page(), array( 'Freemius', '_maybe_add_beta_label_to_plugins_and_handle_confirmation') ); } self::$_statics_loaded = true; } #-------------------------------------------------------------------------------- #region Clone #-------------------------------------------------------------------------------- /** * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param bool $only_if_manual_resolution_is_not_hidden * * @return bool */ 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; } return FS_Clone_Manager::instance()->has_temporary_duplicate_mode_expired(); } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param bool $only_if_manual_resolution_is_not_hidden */ function is_clone( $only_if_manual_resolution_is_not_hidden = false ) { if ( ! is_object( $this->_site ) ) { return false; } $blog_id = null; 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 ) { 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; } } if ( $strip_protocol ) { $url = fs_strip_url_protocol( $url ); } if ( $add_trailing_slash ) { $url = trailingslashit( $url ); } 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/' ); } #endregion #---------------------------------------------------------------------------------- #region Debugging #---------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 1.0.8 */ static function _add_debug_section() { if ( ! is_super_admin() ) { // Add debug page only for super-admins. return; } self::$_static_logger->entrance(); $title = sprintf( '%s [v.%s]', fs_text_inline( 'Freemius Debug' ), WP_FS__SDK_VERSION ); if ( WP_FS__DEV_MODE ) { // Add top-level debug menu item. $hook = FS_Admin_Menu_Manager::add_page( $title, $title, 'manage_options', 'freemius', array( 'Freemius', '_debug_page_render' ) ); } else { // Add hidden debug page. $hook = FS_Admin_Menu_Manager::add_subpage( '', $title, $title, 'manage_options', 'freemius', array( 'Freemius', '_debug_page_render' ) ); } if ( ! empty( $hook ) ) { add_action( "load-$hook", array( 'Freemius', '_debug_page_actions' ) ); } } /** * @author Vova Feldman (@svovaf) * @since 1.1.7.3 */ static function _toggle_debug_mode() { check_admin_referer( 'fs_toggle_debug_mode' ); if ( ! is_super_admin() ) { return; } $is_on = fs_request_get( 'is_on', false, 'post' ); if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) { update_option( 'fs_debug_mode', $is_on ); // Turn on/off storage logging. FS_Logger::_set_storage_logging( ( 1 == $is_on ) ); } exit; } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.6 */ static function _get_debug_log() { check_admin_referer( 'fs_get_debug_log' ); if ( ! is_super_admin() ) { return; } $limit = min( ! empty( $_POST['limit'] ) ? absint( $_POST['limit'] ) : 200, 200 ); $offset = min( ! empty( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 200, 200 ); $logs = FS_Logger::load_db_logs( fs_request_get( 'filters', false, 'post' ), $limit, $offset ); self::shoot_ajax_success( $logs ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.7 */ static function _get_db_option() { check_admin_referer( 'fs_get_db_option' ); $option_name = fs_request_get( 'option_name' ); if ( ! is_super_admin() || ! fs_starts_with( $option_name, 'fs_' ) ) { self::shoot_ajax_failure(); } $value = get_option( $option_name ); $result = array( 'name' => $option_name, ); if ( false !== $value ) { if ( ! is_string( $value ) ) { $value = json_encode( $value ); } $result['value'] = $value; } self::shoot_ajax_success( $result ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.7 */ static function _set_db_option() { check_admin_referer( 'fs_set_db_option' ); $option_name = fs_request_get( 'option_name' ); if ( ! is_super_admin() || ! fs_starts_with( $option_name, 'fs_' ) ) { self::shoot_ajax_failure(); } $option_value = fs_request_get_raw( 'option_value' ); if ( ! empty( $option_value ) ) { update_option( $option_name, $option_value ); } self::shoot_ajax_success(); } /** * @author Vova Feldman (@svovaf) * @since 1.0.8 */ static function _debug_page_actions() { self::_clean_admin_content_section(); if ( fs_request_is_action( 'restart_freemius' ) ) { check_admin_referer( 'restart_freemius' ); if ( ! is_multisite() ) { // Clear accounts data. self::$_accounts->clear( null, true ); } else { $sites = self::get_sites(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); self::$_accounts->clear( $blog_id, true ); } // Clear network level storage. self::$_accounts->clear( true, true ); } // Clear SDK reference cache. delete_option( 'fs_active_plugins' ); } else if ( fs_request_is_action( 'clear_updates_data' ) ) { check_admin_referer( 'clear_updates_data' ); if ( ! is_multisite() ) { set_site_transient( 'update_plugins', null ); set_site_transient( 'update_themes', null ); } else { $current_blog_id = get_current_blog_id(); $sites = self::get_sites(); foreach ( $sites as $site ) { switch_to_blog( self::get_site_blog_id( $site ) ); set_site_transient( 'update_plugins', null ); set_site_transient( 'update_themes', null ); } 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' ); $fs = freemius( fs_request_get( 'module_id' ) ); // Update SDK install to at least 24 hours before. $fs->_storage->install_timestamp = ( time() - WP_FS__TIME_24_HOURS_IN_SEC ); // Unset the trial shown timestamp. unset( $fs->_storage->trial_promotion_shown ); } else if ( fs_request_is_action( 'simulate_network_upgrade' ) ) { check_admin_referer( 'simulate_network_upgrade' ); $fs = freemius( fs_request_get( 'module_id' ) ); self::set_network_upgrade_mode( $fs->_storage ); } else if ( fs_request_is_action( 'delete_install' ) ) { check_admin_referer( 'delete_install' ); self::_delete_site_by_slug( fs_request_get( 'slug' ), fs_request_get( 'module_type' ), true, fs_request_get( 'blog_id', null ) ); } else if ( fs_request_is_action( 'delete_user' ) ) { check_admin_referer( 'delete_user' ); self::delete_user( fs_request_get( 'user_id' ) ); } else if ( fs_request_is_action( 'download_logs' ) ) { check_admin_referer( 'download_logs' ); $download_url = FS_Logger::download_db_logs( fs_request_get( 'filters', false, 'post' ) ); if ( false === $download_url ) { wp_die( 'Oops... there was an error while generating the logs download file. Please try again and if it doesn\'t work contact support@freemius.com.' ); } fs_redirect( $download_url ); } else if ( fs_request_is_action( 'migrate_options_to_network' ) ) { check_admin_referer( 'migrate_options_to_network' ); self::migrate_options_to_network(); } } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @return array */ 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() ) { 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(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); foreach ( $module_types as $type ) { $installs = self::get_all_sites( $type, $blog_id ); foreach ( $installs as $slug => $install ) { if ( ! isset( $sites_by_type[ $type ][ $slug ] ) ) { $sites_by_type[ $type ][ $slug ] = array(); } $install->blog_id = $blog_id; $sites_by_type[ $type ][ $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_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(), 'plugin_licenses' => $licenses_by_module_type[ WP_FS__MODULE_TYPE_PLUGIN ], 'theme_licenses' => $licenses_by_module_type[ WP_FS__MODULE_TYPE_THEME ] ); fs_enqueue_local_style( 'fs_debug', '/admin/debug.css' ); fs_require_once_template( 'debug.php', $vars ); } #endregion #---------------------------------------------------------------------------------- #region Connectivity Issues #---------------------------------------------------------------------------------- /** * Check if Freemius should be turned on for the current plugin install. * * Note: * $this->_is_on is updated in has_api_connectivity() * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ 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; } // If already installed or pending then sure it's on :) if ( $this->is_registered() || $this->is_pending_activation() ) { $this->_is_on = true; return true; } return false; } /** * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @param bool $flush_if_no_connectivity * * @return bool */ private function should_run_connectivity_test( $flush_if_no_connectivity = false ) { if ( ! isset( $this->_storage->connectivity_test ) ) { // Connectivity test was never executed, or cache was cleared. return true; } if ( WP_FS__PING_API_ON_IP_OR_HOST_CHANGES ) { if ( WP_FS__IS_HTTP_REQUEST ) { if ( $_SERVER['HTTP_HOST'] != $this->_storage->connectivity_test['host'] ) { // Domain changed. return true; } if ( WP_FS__REMOTE_ADDR != $this->_storage->connectivity_test['server_ip'] ) { // Server IP changed. return true; } } } if ( $this->_storage->connectivity_test['is_connected'] && $this->_storage->connectivity_test['is_active'] ) { // API connected and Freemius is active - no need to run connectivity check. return false; } if ( $flush_if_no_connectivity ) { /** * If explicitly asked to flush when no connectivity - do it only * if at least 10 sec passed from the last API connectivity test. */ return ( isset( $this->_storage->connectivity_test['timestamp'] ) && ( WP_FS__SCRIPT_START_TIME - $this->_storage->connectivity_test['timestamp'] ) > 10 ); } /** * @since 1.1.7 Don't check for connectivity on plugin downgrade. */ $version = $this->get_plugin_version(); if ( version_compare( $version, $this->_storage->connectivity_test['version'], '>' ) ) { // If it's a plugin version upgrade and Freemius is off or no connectivity, run connectivity test. return true; } return false; } /** * @author Leo Fajardo (@leorw) * @since 2.5.4 * * @param bool $is_update * * @return bool */ 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; } if ( ! is_numeric( $visibility_percentage ) ) { return true; } $min = 1; $max = 100; if ( function_exists( 'random_int' ) ) { $random = random_int( $min, $max ); } else { $random = rand( $min, $max ); } return ( $random <= $visibility_percentage ); } /** * Check if there's any connectivity issue to Freemius API. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param bool $flush_if_no_connectivity * * @return bool|null */ function has_api_connectivity( $flush_if_no_connectivity = false ) { $this->_logger->entrance(); if ( isset( $this->_has_api_connection ) && ( $this->_has_api_connection || ! $flush_if_no_connectivity ) ) { return $this->_has_api_connection; } if ( WP_FS__SIMULATE_NO_API_CONNECTIVITY && isset( $this->_storage->connectivity_test ) && true === $this->_storage->connectivity_test['is_connected'] ) { $this->clear_connectivity_info(); } if ( ! $this->should_run_connectivity_test( $flush_if_no_connectivity ) ) { $this->_has_api_connection = $this->_storage->connectivity_test['is_connected']; /** * @since 1.1.6 During dev mode, if there's connectivity - turn Freemius on regardless the configuration. * * @since 1.2.1.5 If the user running the premium version then ignore the 'is_active' flag and turn Freemius on to enable license key activation. */ $this->_is_on = $this->_storage->connectivity_test['is_active'] || $this->is_premium() || ( WP_FS__DEV_MODE && $this->_has_api_connection && ! WP_FS__SIMULATE_FREEMIUS_OFF ); return $this->_has_api_connection; } 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() ) ); $this->store_connectivity_info( (object) array( 'is_active' => $is_active ), null ); } if ( $is_active ) { $this->_is_on = true; } 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|null $is_connected */ private function store_connectivity_info( $pong, $is_connected ) { $this->_logger->entrance(); $version = $this->get_plugin_version(); if ( false === $is_connected || WP_FS__SIMULATE_FREEMIUS_OFF ) { $is_active = false; } else { $is_active = ( isset( $pong->is_active ) && true == $pong->is_active ); } $is_active = $this->apply_filters( 'is_on', $is_active, $this->is_plugin_update(), $version ); $this->_storage->connectivity_test = array( 'is_connected' => $is_connected, 'host' => $_SERVER['HTTP_HOST'], 'server_ip' => WP_FS__REMOTE_ADDR, 'is_active' => $is_active, 'timestamp' => WP_FS__SCRIPT_START_TIME, // Last version with connectivity attempt. 'version' => $version, ); $this->_has_api_connection = $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. * * @author Vova Feldman (@svovaf) * @since 1.1.8.1 * * @return bool TRUE if successfully turned on. */ private function turn_on() { $this->_logger->entrance(); if ( $this->is_on() || ! isset( $this->_storage->connectivity_test['is_active'] ) ) { return false; } $updated_connectivity = $this->_storage->connectivity_test; $updated_connectivity['is_active'] = true; $updated_connectivity['timestamp'] = WP_FS__SCRIPT_START_TIME; $this->_storage->connectivity_test = $updated_connectivity; $this->_is_on = true; return true; } /** * Anonymous and unique site identifier (Hash). * * @author Vova Feldman (@svovaf) * @since 1.1.0 * * @param null|int $blog_id Since 2.0.0 * * @return string */ 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 = self::get_unfiltered_site_url( $blog_id, true ); $secure_auth = defined( 'SECURE_AUTH_KEY' ) ? SECURE_AUTH_KEY : ''; if ( empty( $secure_auth ) || false !== strpos( $secure_auth, ' ' ) || 'put your unique phrase here' === $secure_auth ) { // Protect against default auth key. $secure_auth = md5( microtime() ); } /** * Base the unique identifier on the WP secure authentication key. Which * turns the key into a secret anonymous identifier. This will help us * to avoid duplicate installs generation on the backend upon opt-in. * * @author Vova Feldman (@svovaf) * @since 1.2.3 */ $unique_id = md5( $key . $secure_auth ); self::$_accounts->set_option( 'unique_id', $unique_id, true, $blog_id ); } $this->_logger->departure( $unique_id ); 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 * * @return \WP_User */ static function _get_current_wp_user() { self::require_pluggable_essentials(); self::wp_cookie_constants(); return wp_get_current_user(); } /** * Define cookie constants which are required by Freemius::_get_current_wp_user() since * it uses wp_get_current_user() which needs the cookie constants set. When a plugin * is network activated the cookie constants are only configured after the network * plugins activation, therefore, if we don't define those constants WP will throw * PHP warnings/notices. * * @author Vova Feldman (@svovaf) * @since 2.1.1 */ private static function wp_cookie_constants() { if ( defined( 'LOGGED_IN_COOKIE' ) && ( defined( 'AUTH_COOKIE' ) || defined( 'SECURE_AUTH_COOKIE' ) ) ) { return; } /** * Used to guarantee unique hash cookies * * @since 1.5.0 */ if ( ! defined( 'COOKIEHASH' ) ) { $siteurl = get_site_option( 'siteurl' ); if ( $siteurl ) { define( 'COOKIEHASH', md5( $siteurl ) ); } else { define( 'COOKIEHASH', '' ); } } if ( ! defined( 'LOGGED_IN_COOKIE' ) ) { define( 'LOGGED_IN_COOKIE', 'wordpress_logged_in_' . COOKIEHASH ); } /** * @since 2.5.0 */ if ( ! defined( 'AUTH_COOKIE' ) ) { define( 'AUTH_COOKIE', 'wordpress_' . COOKIEHASH ); } /** * @since 2.6.0 */ if ( ! defined( 'SECURE_AUTH_COOKIE' ) ) { define( 'SECURE_AUTH_COOKIE', 'wordpress_sec_' . COOKIEHASH ); } } /** * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @return int */ static function get_current_wp_user_id() { $wp_user = self::_get_current_wp_user(); return $wp_user->ID; } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $email * * @return bool */ static function is_valid_email( $email ) { if ( false === filter_var( $email, FILTER_VALIDATE_EMAIL ) ) { return false; } $parts = explode( '@', $email ); if ( 2 !== count( $parts ) || empty( $parts[1] ) ) { return false; } $blacklist = array( 'admin.', 'webmaster.', 'localhost.', 'dev.', 'development.', 'test.', 'stage.', 'staging.', ); // Make sure domain is not one of the blacklisted. foreach ( $blacklist as $invalid ) { if ( 0 === strpos( $parts[1], $invalid ) ) { return false; } } // Get the UTF encoded domain name. $domain = idn_to_ascii( $parts[1] ) . '.'; return ( checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) ); } #endregion #---------------------------------------------------------------------------------- #region Email #---------------------------------------------------------------------------------- /** * Generates and sends an HTML email with customizable sections. * * @author Leo Fajardo (@leorw) * @since 1.1.2 * * @param string $to_address * @param string $subject * @param array $sections * @param array $headers * * @return bool Whether the email contents were sent successfully. */ private function send_email( $to_address, $subject, $sections = array(), $headers = array() ) { $default_sections = $this->get_email_sections(); // Insert new sections or replace the default email sections. if ( is_array( $sections ) && ! empty( $sections ) ) { foreach ( $sections as $section_id => $custom_section ) { if ( ! isset( $default_sections[ $section_id ] ) ) { // If the section does not exist, add it. $default_sections[ $section_id ] = $custom_section; } else { // If the section already exists, override it. $current_section = $default_sections[ $section_id ]; // Replace the current section's title if a custom section title exists. if ( isset( $custom_section['title'] ) ) { $current_section['title'] = $custom_section['title']; } // Insert new rows under the current section or replace the default rows. if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) { foreach ( $custom_section['rows'] as $row_id => $row ) { $current_section['rows'][ $row_id ] = $row; } } $default_sections[ $section_id ] = $current_section; } } } $vars = array( 'sections' => $default_sections ); $message = fs_get_template( 'email.php', $vars ); // Set the type of email to HTML. $headers[] = 'Content-type: text/html; charset=UTF-8'; $header_string = implode( "\r\n", $headers ); return wp_mail( $to_address, $subject, $message, $header_string ); } /** * Generates the data for the sections of the email content. * * @author Leo Fajardo (@leorw) * @since 1.1.2 * * @return array */ private function get_email_sections() { // Retrieve the current user's information so that we can get the user's email, first name, and last name below. $current_user = self::_get_current_wp_user(); // Retrieve the cURL version information so that we can get the version number below. $curl_version_information = curl_version(); $active_plugin = self::get_active_plugins(); // Generate the list of active plugins separated by new line. $active_plugin_string = ''; foreach ( $active_plugin as $plugin ) { $active_plugin_string .= sprintf( '%s [v%s]
', $plugin['PluginURI'], $plugin['Name'], $plugin['Version'] ); } $server_ip = WP_FS__REMOTE_ADDR; // Add PHP info for deeper investigation. ob_start(); phpinfo(); $php_info = ob_get_clean(); $api_domain = substr( FS_API__ADDRESS, strpos( FS_API__ADDRESS, ':' ) + 3 ); // Generate the default email sections. $sections = array( 'sdk' => array( 'title' => 'SDK', 'rows' => array( 'fs_version' => array( 'FS Version', $this->version ), 'curl_version' => array( 'cURL Version', $curl_version_information['version'] ) ) ), 'plugin' => array( 'title' => ucfirst( $this->get_module_type() ), 'rows' => array( 'name' => array( 'Name', $this->get_plugin_name() ), 'version' => array( 'Version', $this->get_plugin_version() ) ) ), 'api' => array( 'title' => 'API Subdomain', 'rows' => array( 'dns' => array( 'DNS_CNAME', function_exists( 'dns_get_record' ) ? var_export( dns_get_record( $api_domain, DNS_CNAME ), true ) : 'dns_get_record() disabled/blocked' ), 'ip' => array( 'IP', function_exists( 'gethostbyname' ) ? gethostbyname( $api_domain ) : 'gethostbyname() disabled/blocked' ), ), ), 'site' => array( 'title' => 'Site', 'rows' => array( 'unique_id' => array( 'Unique ID', $this->get_anonymous_id() ), 'address' => array( 'Address', site_url() ), 'host' => array( 'HTTP_HOST', ( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' ) ), 'hosting' => array( 'Hosting Company' => fs_request_has( 'hosting_company' ) ? fs_request_get( 'hosting_company' ) : 'Unknown', ), 'server_addr' => array( 'SERVER_ADDR', '' . $server_ip . '' ) ) ), 'user' => array( 'title' => 'User', 'rows' => array( 'email' => array( 'Email', $current_user->user_email ), 'first' => array( 'First', $current_user->user_firstname ), 'last' => array( 'Last', $current_user->user_lastname ) ) ), 'plugins' => array( 'title' => 'Plugins', 'rows' => array( 'active_plugins' => array( 'Active Plugins', $active_plugin_string ) ) ), 'php_info' => array( 'title' => 'PHP Info', 'rows' => array( 'info' => array( $php_info ) ), ) ); // Allow the sections to be modified by other code. $sections = $this->apply_filters( 'email_template_sections', $sections ); return $sections; } #endregion #---------------------------------------------------------------------------------- #region Initialization #---------------------------------------------------------------------------------- /** * Init plugin's Freemius instance. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param number $id * @param string $public_key * @param bool $is_live * @param bool $is_premium */ function init( $id, $public_key, $is_live = true, $is_premium = true ) { $this->_logger->entrance(); $this->dynamic_init( array( 'id' => $id, 'public_key' => $public_key, 'is_live' => $is_live, 'is_premium' => $is_premium, ) ); } /** * Dynamic initiator, originally created to support initiation * with parent_id for add-ons. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param array $plugin_info * * @throws Freemius_Exception */ function dynamic_init( array $plugin_info ) { $this->_logger->entrance(); $this->parse_settings( $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; } if ( ! $this->is_registered() ) { if ( $this->is_anonymous() ) { // If user skipped, no need to test connectivity. $this->_has_api_connection = true; $this->_is_on = true; } else { if ( false === $this->has_api_connectivity() ) { return; } else { if ( $this->_anonymous_mode ) { // Simulate anonymous mode. $this->_is_anonymous = true; } } } } /** * This should be executed even if Freemius is off for the core module, * 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) */ if ( $this->is_user_in_admin() && 'plugin-information' === fs_request_get( 'tab', false ) && $this->should_use_freemius_updater_and_dialog() && ( ( $this->is_addon() && $this->get_slug() == fs_request_get( 'plugin', false ) ) || ( $this->has_addons() && $this->get_id() == fs_request_get( 'parent_plugin_id', false ) ) ) ) { require_once WP_FS__DIR_INCLUDES . '/fs-plugin-info-dialog.php'; new FS_Plugin_Info_Dialog( $this->is_addon() ? $this->get_parent_instance() : $this ); } // Check if Freemius is on for the current plugin. // This MUST be executed after all the plugin variables has been loaded. if ( ! $this->is_registered() && ! $this->is_on() ) { return; } if ( $this->has_api_connectivity() ) { if ( self::is_cron() ) { $this->hook_callback_to_sync_cron(); } else if ( $this->is_user_in_admin() ) { /** * Schedule daily data sync cron if: * * 1. User opted-in (for tracking). * 2. If skipped, but later upgraded (opted-in via upgrade). * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * */ 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(); } if ( $this->is_addon() ) { if ( $this->is_parent_plugin_installed() ) { // Link to parent FS. $this->_parent = self::get_instance_by_id( $this->_plugin->parent_plugin_id ); // Get parent plugin reference. $this->_parent_plugin = $this->_parent->get_plugin(); } } 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 ); if ( isset( $plugin_info['parent'] ) ) { $parent_name = $this->get_option( $plugin_info['parent'], 'name', null ); } $this->_admin_notices->add( ( ! empty( $parent_name ) ? sprintf( $this->get_text_x_inline( '%s cannot run without %s.', 'addonX cannot run without pluginY', 'addon-x-cannot-run-without-y' ), $this->get_plugin_name(), $parent_name ) : sprintf( $this->get_text_x_inline( '%s cannot run without the plugin.', 'addonX cannot run...', 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() ) ), $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', 'error' ); return; } else { $is_network_admin = fs_is_network_admin(); if ( ! $this->_parent->is_registered() && $this->is_registered() ) { // If add-on activated and parent not, automatically install parent for the user. $this->activate_parent_account( $this->_parent ); } else if ( $this->_parent->is_registered() && ! $this->is_registered() && /** * If not registered for add-on and the following conditions for the add-on are met, activate add-on account. * * Network active and in network admin - network activate add-on account. * * Network active and not in network admin - activate add-on account for the current blog. * * Not network active and not in network admin - activate add-on account for the current blog. * * If not registered for add-on, not network active, and in network admin, do not handle the add-on activation. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ ( $this->is_network_active() || ! $is_network_admin ) ) { $premium_license = null; if ( ! $this->has_free_plan() && $this->is_bundle_license_auto_activation_enabled() && $this->_parent->is_activated_with_bundle_license() ) { /** * If the add-on has no free plan, try to activate the account only when there's a bundle license. * * @author Leo Fajardo (@leorw) * @since 2.4.0 */ $bundle_license = $this->get_active_parent_license( $this->_parent->_get_license()->secret_key, false ); if ( is_object( $bundle_license ) && ! empty( $bundle_license->products ) && in_array( $this->get_id(), $bundle_license->products ) ) { $premium_license = $bundle_license; } } if ( $this->has_free_plan() || is_object( $premium_license) ) { // If parent plugin activated, automatically install add-on for the user. $this->_activate_addon_account( $this->_parent, ( $this->is_network_active() && $is_network_admin ) ? true : get_current_blog_id(), $premium_license ); } } // @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic. if ( $this->is_premium() ) { // Remove add-on download admin-notice. $this->_parent->_admin_notices->remove_sticky( array( 'addon_plan_upgraded_' . $this->_slug, 'no_addon_license_' . $this->_slug, ) ); } // $this->deactivate_premium_only_addon_without_license(); } } add_action( 'admin_init', array( &$this, '_admin_init_action' ) ); // if ( $this->is_registered() || // $this->is_anonymous() || // $this->is_pending_activation() // ) { // $this->_init_admin(); // } } /** * Should be called outside `$this->is_user_in_admin()` scope * 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. * In the future, there might be additional logic added. * * @author Vova Feldman * @since 1.2.1.6 */ if ( $this->should_use_freemius_updater_and_dialog() && ( $this->is_premium() || /** * If not premium but the premium version is installed, also instantiate the updater so that the * plugin information dialog of the premium version will have the information from the server. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->premium_plugin_basename() ) ) ) ) && $this->has_release_on_freemius() && ( ! $this->is_unresolved_clone( true ) ) ) { FS_Plugin_Updater::instance( $this ); } $this->do_action( 'initiated' ); if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) { if ( isset( $this->_storage->prev_is_premium ) ) { $this->apply_filters( 'after_code_type_change', // New code type. $this->_plugin->is_premium ); } else { // Set for code type for the first time. $this->_storage->prev_is_premium = $this->_plugin->is_premium; } } if ( ! $this->is_addon() ) { if ( $this->is_registered() ) { // Fix for upgrade from versions < 1.0.9. if ( ! isset( $this->_storage->activation_timestamp ) ) { $this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME; } $this->do_action( 'after_init_plugin_registered' ); } else if ( $this->is_anonymous() ) { $this->do_action( 'after_init_plugin_anonymous' ); } else if ( $this->is_pending_activation() ) { $this->do_action( 'after_init_plugin_pending_activations' ); } } else { if ( $this->is_registered() ) { $this->do_action( 'after_init_addon_registered' ); } else if ( $this->is_anonymous() ) { $this->do_action( 'after_init_addon_anonymous' ); } else if ( $this->is_pending_activation() ) { $this->do_action( 'after_init_addon_pending_activations' ); } } } /** * @author Leo Fajardo (@leorw) * @since 2.2.3 * * @return bool */ 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' ) ) ) ); } /** * @param string[] $permissions * @param bool $is_enabled * @param int|null $blog_id * * @return true|object `true` on success, API error object on failure. */ private function update_site_permissions( array $permissions, $is_enabled, $blog_id = null ) { $this->_logger->entrance(); $params = array( 'permissions' => implode( ',', $permissions ), 'is_enabled' => $is_enabled, ); $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 ); } $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_object( $result ) || ! isset( $result->install_id ) ) { $this->_logger->api_error( $result ); return $result; } return true; } /** * @param string[] $permissions * @param bool $is_enabled * @param bool $has_site_delegated_connection * * @return true|object `true` on success, API error object on failure. */ private function update_network_permissions( array $permissions, $is_enabled, &$has_site_delegated_connection ) { $this->_logger->entrance(); $install_id_2_blog_id = array(); $install_by_blog_id = $this->get_blog_install_map(); $has_site_delegated_connection = false; foreach ( $install_by_blog_id as $blog_id => $install ) { if ( $this->is_site_delegated_connection( $blog_id ) ) { // Only update permissions of non-delegated installs. $has_site_delegated_connection = true; continue; } $install_id_2_blog_id[ $install->id ] = $blog_id; } if ( empty( $install_id_2_blog_id ) ) { return 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/permissions.json", 'put', $params ); if ( ! $this->is_api_result_object( $result, 'installs_metadata' ) ) { $this->_logger->api_error( $result ); return $result; } return true; } /** * @param mixed $result * * @return string */ 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 ) . ' '; 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; } /** * @author Vova Feldman (@svovaf) * @since 2.5.1 */ function _toggle_permission_tracking_callback() { $this->_logger->entrance(); $this->check_ajax_referer( 'toggle_permission_tracking' ); if ( ! $this->is_registered( true ) ) { self::shoot_ajax_failure( 'User never opted-in.' ); } $is_enabled = fs_request_get_bool( 'is_enabled' ); $permissions = fs_request_get( 'permissions' ); if ( ! is_string( $permissions ) ) { self::shoot_ajax_failure( 'The permissions param must be a string.' ); } $permissions = explode( ',', $permissions ); $result = $this->toggle_permission_tracking( $permissions, $is_enabled ); if ( true !== $result ) { self::shoot_ajax_failure( $this->get_api_error_message( $result ) ); } self::shoot_ajax_success(); } /** * @param string[] $permissions * @param bool $is_enabled * @param int|null $blog_id * * @return bool|mixed `true` if updated successfully or no update is needed. */ private function toggle_permission_tracking( $permissions, $is_enabled, $blog_id = null ) { if ( ! $this->is_registered( true ) ) { // User never opted-in. return false; } // 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; } $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; } if ( ! empty( $api_managed_permissions ) ) { $has_site_delegated_connection = false; 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 ( true !== $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 ); } } if ( in_array( FS_Permission_Manager::PERMISSION_USER, $api_managed_permissions ) ) { $this->toggle_user_permission( $is_enabled, $blog_id ); } } $this->update_tracking_permissions( $permissions, $is_enabled, $blog_id ); 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. * * 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 * * @bool $is_enabled * * @return bool|object */ private function toggle_site_tracking( $is_enabled, $blog_id = null ) { $this->_logger->entrance(); return $this->toggle_permission_tracking( FS_Permission_Manager::instance( $this )->get_site_tracking_permission_names(), $is_enabled, $blog_id ); } /** * If user opted-in and later disabled usage-tracking, * re-allow tracking for licensing and updates. * * @author Leo Fajardo (@leorw) * @since 1.2.1.5 * * @param bool $is_context_single_site */ private function reconnect_locally( $is_context_single_site = false ) { $this->_logger->entrance(); if ( ! $this->is_registered() ) { return; } if ( ! fs_is_network_admin() || $is_context_single_site ) { if ( $this->is_tracking_prohibited() ) { FS_Permission_Manager::instance( $this )->update_site_tracking( true ); } } else { $installs_map = $this->get_blog_install_map(); foreach ( $installs_map as $blog_id => $install ) { /** * @var FS_Site $install */ if ( ! $this->is_tracking_allowed( $blog_id, $install ) ) { FS_Permission_Manager::instance( $this )->update_site_tracking( true, $blog_id ); } } } } /** * 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. * * @param string[] $permissions * @param bool $is_enabled * @param int|null $blog_id * * @return array */ private function update_tracking_permissions( $permissions, $is_enabled, $blog_id = null ) { // Alias. $permission_manager = FS_Permission_Manager::instance( $this ); $network_or_blog_ids = is_numeric( $blog_id ) ? $blog_id : fs_is_network_admin(); if ( true === $network_or_blog_ids ) { // Update the permission for all non-delegated sub-sites. $blog_ids = $this->get_non_delegated_blog_ids(); // 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; } $blog_ids = is_array( $network_or_blog_ids ) ? $network_or_blog_ids : array( $network_or_blog_ids ); } $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 ); } if ( ! $is_permission_supported ) { $permission = 'no_match'; } $result[ $permission ] = $is_enabled; } return $result; } /** * Parse plugin's settings (as defined by the plugin dev). * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @param array $plugin_info * * @throws \Freemius_Exception */ private function parse_settings( &$plugin_info ) { $this->_logger->entrance(); $id = $this->get_numeric_option( $plugin_info, 'id', false ); $public_key = $this->get_option( $plugin_info, 'public_key', false ); $secret_key = $this->get_option( $plugin_info, 'secret_key', null ); $parent_id = $this->get_numeric_option( $plugin_info, 'parent_id', null ); $parent_name = $this->get_option( $plugin_info, 'parent_name', null ); /** * @author Vova Feldman (@svovaf) * @since 1.1.9 Try to pull secret key from external config. */ if ( is_null( $secret_key ) && defined( "WP_FS__{$this->_slug}_SECRET_KEY" ) ) { $secret_key = constant( "WP_FS__{$this->_slug}_SECRET_KEY" ); } if ( isset( $plugin_info['parent'] ) ) { $parent_id = $this->get_numeric_option( $plugin_info['parent'], 'id', null ); // $parent_slug = $this->get_option( $plugin_info['parent'], 'slug', null ); // $parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null ); // $parent_name = $this->get_option( $plugin_info['parent'], 'name', null ); } if ( false === $id ) { throw new Freemius_Exception( array( 'error' => array( 'type' => 'ParameterNotSet', 'message' => 'Plugin id parameter is not set.', 'code' => 'plugin_id_not_set', 'http' => 500, ) ) ); } if ( false === $public_key ) { throw new Freemius_Exception( array( 'error' => array( 'type' => 'ParameterNotSet', 'message' => 'Plugin public_key parameter is not set.', 'code' => 'plugin_public_key_not_set', 'http' => 500, ) ) ); } $plugin = ( $this->_plugin instanceof FS_Plugin ) ? $this->_plugin : new FS_Plugin(); $premium_suffix = $this->get_option( $plugin_info, 'premium_suffix', '(Premium)' ); $plugin->update( array( 'id' => $id, 'type' => $this->get_option( $plugin_info, 'type', $this->_module_type ), 'public_key' => $public_key, 'slug' => $this->_slug, 'premium_slug' => $this->get_option( $plugin_info, 'premium_slug', "{$this->_slug}-premium" ), 'parent_plugin_id' => $parent_id, 'version' => $this->get_plugin_version(), 'title' => $this->get_plugin_name( $premium_suffix ), 'file' => $this->_plugin_basename, 'is_premium' => $this->get_bool_option( $plugin_info, 'is_premium', true ), 'premium_suffix' => $premium_suffix, 'is_live' => $this->get_bool_option( $plugin_info, 'is_live', true ), '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() ) { // Update plugin details. $this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->store( $plugin ); } // Set the secret key after storing the plugin, we don't want to store the key in the storage. $this->_plugin->secret_key = $secret_key; /** * If the product is network integrated and activated and the current view is in the network level Admin dashboard, if the product's network-level menu located differently from the sub-site level, then use the network menu details (when set). * * @author Vova Feldman * @since 2.4.5 */ if ( $this->is_network_active() && fs_is_network_admin() ) { if ( isset( $plugin_info['menu_network'] ) && is_array( $plugin_info['menu_network'] ) && ! empty( $plugin_info['menu_network'] ) ) { $plugin_info['menu'] = $plugin_info['menu_network']; } } if ( ! isset( $plugin_info['menu'] ) ) { $plugin_info['menu'] = array(); if ( ! empty( $this->_storage->sdk_last_version ) && version_compare( $this->_storage->sdk_last_version, '1.1.2', '<=' ) ) { // Backward compatibility to 1.1.2 $plugin_info['menu']['slug'] = isset( $plugin_info['menu_slug'] ) ? $plugin_info['menu_slug'] : $this->_slug; } } $this->_menu = FS_Admin_Menu_Manager::instance( $this->_module_id, $this->_module_type, $this->get_unique_affix() ); $this->_menu->init( $plugin_info['menu'], $this->is_addon() ); $this->_has_addons = $this->get_bool_option( $plugin_info, 'has_addons', false ); $this->_has_paid_plans = $this->get_bool_option( $plugin_info, 'has_paid_plans', true ); $this->_has_premium_version = $this->get_bool_option( $plugin_info, 'has_premium_version', $this->_has_paid_plans ); $this->_ignore_pending_mode = $this->get_bool_option( $plugin_info, 'ignore_pending_mode', false ); $this->_is_org_compliant = $this->get_bool_option( $plugin_info, 'is_org_compliant', true ); $this->_is_premium_only = $this->get_bool_option( $plugin_info, 'is_premium_only', false ); if ( $this->_is_premium_only ) { // If premium only plugin, disable anonymous mode. $this->_enable_anonymous = false; $this->_anonymous_mode = false; } else { $this->_enable_anonymous = $this->get_bool_option( $plugin_info, 'enable_anonymous', true ); $this->_anonymous_mode = $this->get_bool_option( $plugin_info, 'anonymous_mode', false ); } $this->_permissions = $this->get_option( $plugin_info, 'permissions', array() ); $this->_is_bundle_license_auto_activation_enabled = $this->get_option( $plugin_info, 'bundle_license_auto_activation', false ); if ( ! empty( $plugin_info['trial'] ) ) { $this->_trial_days = $this->get_numeric_option( $plugin_info['trial'], 'days', // Default to 0 - trial without days specification. 0 ); $this->_is_trial_require_payment = $this->get_bool_option( $plugin_info['trial'], 'is_require_payment', false ); } $this->_navigation = $this->get_option( $plugin_info, 'navigation', $this->is_free_wp_org_theme() ? self::NAVIGATION_TABS : self::NAVIGATION_MENU ); } /** * @param string[] $options * @param string $key * @param mixed $default * * @return bool */ private function get_option( &$options, $key, $default = false ) { return ! empty( $options[ $key ] ) ? $options[ $key ] : $default; } private function get_bool_option( &$options, $key, $default = false ) { return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default; } private function get_numeric_option( &$options, $key, $default = false ) { return isset( $options[ $key ] ) && is_numeric( $options[ $key ] ) ? $options[ $key ] : $default; } /** * Gate keeper. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @return bool */ private function should_stop_execution() { if ( empty( $this->_storage->was_plugin_loaded ) ) { /** * Don't execute Freemius until plugin was fully loaded at least once, * to give the opportunity for the activation hook to run before pinging * the API for connectivity test. This logic is relevant for the * identification of new plugin install vs. plugin update. * * @author Vova Feldman (@svovaf) * @since 1.1.9 */ return true; } if ( $this->is_activation_mode() ) { if ( ! is_admin() ) { /** * If in activation mode, don't execute Freemius outside the admin dashboard. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 */ return true; } if ( ! WP_FS__IS_HTTP_REQUEST ) { /** * If in activation and executed without HTTP context (e.g. CLI, Cronjob), * then don't start Freemius. * * @author Vova Feldman (@svovaf) * @since 1.1.6.3 * * @link https://wordpress.org/support/topic/errors-in-the-freemius-class-when-running-in-wordpress-in-cli */ return true; } if ( self::is_cron() ) { /** * If in activation mode, don't execute Freemius during wp crons * (wp crons have HTTP context - called as HTTP request). * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 */ return true; } if ( self::is_ajax() ) { /** * During activation, if running in AJAX mode, unless there's a sticky * connectivity issue notice, don't run Freemius. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 */ return true; } } return false; } /** * Triggered after code type has changed. * * @author Vova Feldman (@svovaf) * @since 1.1.9.1 */ function _after_code_type_change() { $this->_logger->entrance(); if ( $this->is_theme() ) { // Expire the cache of the previous tabs since the theme may // have setting updates after code type has changed. $this->_cache->expire( 'tabs' ); $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_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" ); } } } /** * Handles plugin's code type change (free <--> premium). * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ function _plugin_code_type_changed() { $this->_logger->entrance(); if ( $this->is_premium() ) { $this->reconnect_locally(); // Activated premium code. $this->do_action( 'after_premium_version_activation' ); // Remove all sticky messages related to download of the premium version. $this->_admin_notices->remove_sticky( array( 'trial_started', 'plan_upgraded', 'plan_changed', 'license_activated', ) ); $notice = ''; if ( ! $this->is_only_premium() ) { $notice = sprintf( $this->get_text_inline( 'Premium %s version was successfully activated.', 'premium-activated-message' ), $this->_module_type ); } $license_notice = $this->get_license_network_activation_notice(); if ( ! empty( $license_notice ) ) { $notice .= ' ' . $license_notice; } if ( ! empty( $notice ) ) { $this->_admin_notices->add_sticky( trim( $notice ), 'premium_activated', $this->get_text_x_inline( 'W00t', 'Used to express elation, enthusiasm, or triumph (especially in electronic communication).', 'woot' ) . '!' ); } } else { // Remove sticky message related to premium code activation. $this->_admin_notices->remove_sticky( 'premium_activated' ); // Activated free code (after had the premium before). $this->do_action( 'after_free_version_reactivation' ); if ( $this->is_paying() && ! $this->is_premium() ) { $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() ), 'plan_upgraded' ); } } // 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 * triggering a fatal error when uninstalling that plugin. For example, after deactivating the "free" version * of a specific plugin, its uninstall hook should be unregistered after the "premium" version has been * activated. If we don't do that, a fatal error will occur when we try to uninstall the "free" version since * the main file of the "free" version will be loaded first before calling the hooked callback. Since the * free and premium versions are almost identical (same class or have same functions), a fatal error like * "Cannot redeclare class MyClass" or "Cannot redeclare my_function()" will occur. */ $this->unregister_uninstall_hook(); $this->clear_module_main_file_cache(); // Update is_premium of latest version. $this->_storage->prev_is_premium = $this->_plugin->is_premium; } #endregion #---------------------------------------------------------------------------------- #region Add-ons #---------------------------------------------------------------------------------- /** * Check if add-on installed and activated on site. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param string|number $id_or_slug * @param bool|null $is_premium Since 1.2.1.7 can check for specified add-on version. * * @return bool */ function is_addon_activated( $id_or_slug, $is_premium = null ) { $this->_logger->entrance(); $addon_id = self::get_module_id( $id_or_slug ); $is_activated = self::has_instance( $addon_id ); if ( ! $is_activated ) { return false; } if ( is_bool( $is_premium ) ) { // Check if the specified code version is activate. $addon = $this->get_addon_instance( $addon_id ); $is_activated = ( $is_premium === $addon->is_premium() ); } return $is_activated; } /** * Check if add-on was connected to install * * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @param string|number $id_or_slug * * @return bool */ function is_addon_connected( $id_or_slug ) { $this->_logger->entrance(); $sites = self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ); $addon_id = self::get_module_id( $id_or_slug ); $addon = $this->get_addon( $addon_id ); $slug = $addon->slug; if ( ! isset( $sites[ $slug ] ) ) { return false; } $site = $sites[ $slug ]; $plugin = FS_Plugin_Manager::instance( $addon_id )->get(); if ( $plugin->parent_plugin_id != $this->_plugin->id ) { // The given slug do NOT belong to any of the plugin's add-ons. return false; } return ( is_object( $site ) && is_numeric( $site->id ) && is_numeric( $site->user_id ) && FS_Plugin_Plan::is_valid_id( $site->plan_id ) ); } /** * Determines if add-on installed. * * NOTE: This is a heuristic and only works if the folder/file named as the slug. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param string|number $id_or_slug * * @return bool */ function is_addon_installed( $id_or_slug ) { $this->_logger->entrance(); $addon_id = self::get_module_id( $id_or_slug ); return file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->get_addon_basename( $addon_id ) ) ); } /** * Get add-on basename. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param string|number $id_or_slug * * @return string */ function get_addon_basename( $id_or_slug ) { $addon_id = self::get_module_id( $id_or_slug ); if ( $this->is_addon_activated( $addon_id ) ) { return self::instance( $addon_id )->get_plugin_basename(); } $addon = $this->get_addon( $addon_id ); $premium_basename = "{$addon->premium_slug}/{$addon->slug}.php"; if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_basename ) ) ) { return $premium_basename; } $all_plugins = $this->get_all_plugins(); foreach ( $all_plugins as $basename => $data ) { if ( $addon->slug === $data['slug'] || $addon->premium_slug === $data['slug'] ) { return $basename; } } $free_basename = "{$addon->slug}/{$addon->slug}.php"; return $free_basename; } /** * Get installed add-ons instances. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return Freemius[] */ function get_installed_addons() { if ( $this->is_addon() ) { // Add-on cannot have add-ons. return array(); } $installed_addons = array(); foreach ( self::$_instances as $instance ) { if ( $instance->is_addon_of( $this->_plugin->id ) ) { $installed_addons[] = $instance; } } return $installed_addons; } /** * Check if any add-ons of the plugin are installed. * * @author Leo Fajardo (@leorw) * @since 1.1.1 * * @return bool */ function has_installed_addons() { if ( ! $this->has_addons() ) { return false; } foreach ( self::$_instances as $instance ) { if ( $instance->is_addon() && is_object( $instance->_parent_plugin ) ) { if ( $this->_plugin->id == $instance->_parent_plugin->id ) { return true; } } } return false; } /** * Tell Freemius that the current plugin is an add-on. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param number $parent_plugin_id The parent plugin ID */ function init_addon( $parent_plugin_id ) { $this->_plugin->parent_plugin_id = $parent_plugin_id; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool */ function is_addon() { return ( isset( $this->_plugin->parent_plugin_id ) && is_numeric( $this->_plugin->parent_plugin_id ) ); } /** * @author Vova Feldman (@svovaf) * @since 2.3.2 * * @param number $parent_product_id * * @return bool */ function is_addon_of( $parent_product_id ) { return ( $this->is_addon() && $parent_product_id == $this->_plugin->parent_plugin_id ); } /** * Deactivate add-on if it's premium only and the user does't have a valid license. * * @param bool $is_after_trial_cancel * * @return bool If add-on was deactivated. */ private function deactivate_premium_only_addon_without_license( $is_after_trial_cancel = false ) { if ( ! $this->has_free_plan() && ! $this->has_features_enabled_license() && ! $this->_has_premium_license() ) { if ( $this->is_registered() ) { // IF wrapper is turned off because activation_timestamp is currently only stored for plugins (not addons). // if (empty($this->_storage->activation_timestamp) || // (WP_FS__SCRIPT_START_TIME - $this->_storage->activation_timestamp) > 30 // ) { /** * @todo When it's first fail, there's no reason to try and re-sync because the licenses were just synced after initial activation. * * Retry syncing the user add-on licenses. */ // Sync licenses. $this->_sync_licenses(); // } // Try to activate premium license. $this->_activate_license( true ); } if ( ! $this->has_free_plan() && ! $this->has_features_enabled_license() && ! $this->_has_premium_license() ) { // @todo Check if deactivate plugins also call the deactivation hook. $this->_parent->_admin_notices->add_sticky( sprintf( ( $is_after_trial_cancel ? $this->_parent->get_text_inline( '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.', 'addon-trial-cancelled-message' ) : $this->_parent->get_text_inline( '%s is a premium only add-on. You have to purchase a license first before activating the plugin.', 'addon-no-license-message' ) ), '' . $this->_plugin->title . '' ) . ' ' . sprintf( '%s  ➜', $this->_parent->addon_url( $this->_slug ), esc_attr( sprintf( $this->_parent->get_text_inline( 'More information about %s', 'more-information-about-x' ), $this->_plugin->title ) ), $this->_parent->get_text_inline( 'Purchase License', 'purchase-license' ) ), 'no_addon_license_' . $this->_slug, ( $is_after_trial_cancel ? '' : $this->_parent->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...' ), ( $is_after_trial_cancel ? 'success' : 'error' ) ); deactivate_plugins( array( $this->_plugin_basename ), true ); return true; } } return false; } #endregion #---------------------------------------------------------------------------------- #region Sandbox #---------------------------------------------------------------------------------- /** * Set Freemius into sandbox mode for debugging. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $secret_key */ function init_sandbox( $secret_key ) { $this->_plugin->secret_key = $secret_key; // Update plugin details. FS_Plugin_Manager::instance( $this->_module_id )->update( $this->_plugin, true ); } /** * Check if running payments in sandbox mode. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @return bool */ function is_payments_sandbox() { return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key ); } #endregion /** * Check if running test vs. live plugin. * * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @return bool */ function is_live() { return $this->_plugin->is_live; } /** * Check if super-admin skipped connection for all sites in the network. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function is_network_anonymous() { if ( ! $this->_is_network_active ) { return false; } $is_anonymous_ms = $this->_storage->get( 'is_anonymous_ms' ); if ( empty( $is_anonymous_ms ) ) { return false; } return $is_anonymous_ms['is']; } /** * Check if super-admin opted-in for all sites in the network. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function is_network_connected() { if ( ! $this->_is_network_active ) { return false; } return $this->_storage->get( 'is_network_connected' ); } /** * Check if the user skipped connecting the account with Freemius. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool */ function is_anonymous() { if ( ! isset( $this->_is_anonymous ) ) { if ( $this->is_network_anonymous() ) { $this->_is_anonymous = true; } 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; } else if ( is_bool( $this->_storage->is_anonymous ) ) { // For back compatibility, since the variable was boolean before. $this->_is_anonymous = $this->_storage->is_anonymous; // Upgrade stored data format to 1.1.3 format. $this->set_anonymous_mode( $this->_storage->is_anonymous ); } else { // Version 1.1.3 and later. $this->_is_anonymous = $this->_storage->is_anonymous['is']; } } } return $this->_is_anonymous; } /** * Check if the user skipped the connection of a specified site. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id * * @return bool */ function is_anonymous_site( $blog_id = 0 ) { if ( $this->is_network_anonymous() ) { return true; } $is_anonymous = $this->_storage->get( 'is_anonymous', false, $blog_id ); if ( empty( $is_anonymous ) ) { return false; } return $is_anonymous['is']; } /** * Check if user connected his account and install pending email activation. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool */ 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. * * @since 1.0.7 * * @return bool */ function is_org_repo_compliant() { return $this->_is_org_compliant; } #-------------------------------------------------------------------------------- #region WP Cron Common #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. * * @return object */ private function get_cron_data( $name ) { $this->_logger->entrance( $name ); /** * @var object $cron_data */ return $this->_storage->get( "{$name}_cron", null ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. */ private function clear_cron_data( $name ) { $this->_logger->entrance( $name ); $this->_storage->remove( "{$name}_cron" ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. * @param int $cron_blog_id The cron executing blog ID. */ private function set_cron_data( $name, $cron_blog_id = 0 ) { $this->_logger->entrance( $name ); $this->_storage->store( "{$name}_cron", (object) array( 'version' => $this->get_plugin_version(), 'blog_id' => $cron_blog_id, 'sdk_version' => $this->version, 'timestamp' => WP_FS__SCRIPT_START_TIME, 'on' => true, ) ); } /** * Get the cron's executing blog ID. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. * * @return int */ private function get_cron_blog_id( $name ) { $this->_logger->entrance( $name ); 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 ) ) ? $cron_data->blog_id : 0; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. * * @return bool */ private function is_cron_on( $name ) { $this->_logger->entrance( $name ); /** * @var object $cron_data */ $cron_data = $this->get_cron_data( $name ); return ( ! is_null( $cron_data ) && true === $cron_data->on ); } /** * Unix timestamp for previous cron execution or false if never executed. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. * * @return int|false */ private function cron_last_execution( $name ) { $this->_logger->entrance( $name ); return $this->_storage->get( "{$name}_timestamp" ); } /** * Set cron execution time to now. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. */ private function set_cron_execution_timestamp( $name ) { $this->_logger->entrance( $name ); $this->_storage->store( "{$name}_timestamp", time() ); } /** * Sets the keepalive time to now. * * @author Leo Fajardo (@leorw) * @since 2.2.3 * * @param bool|null $use_network_level_storage */ private function set_keepalive_timestamp( $use_network_level_storage = null ) { $this->_logger->entrance(); $this->_storage->store( 'keepalive_timestamp', time(), $use_network_level_storage ); } /** * Check if cron was executed in the last $period of seconds. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. * @param int $period In seconds * * @return bool */ private function is_cron_executed( $name, $period = WP_FS__TIME_24_HOURS_IN_SEC ) { $this->_logger->entrance( $name ); $last_execution = $this->cron_last_execution( $name ); if ( ! is_numeric( $last_execution ) ) { return false; } return ( $last_execution > ( WP_FS__SCRIPT_START_TIME - $period ) ); } /** * WP Cron is executed on a site level. When running in a multisite network environment * with the network integration activated, for optimization reasons, we are consolidating * the installs data sync cron to be executed only from a single site. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $except_blog_id Target any except the excluded blog ID. * * @return int */ private function get_cron_target_blog_id( $except_blog_id = 0 ) { if ( ! is_multisite() ) { return 0; } if ( $this->_is_network_active ) { $network_install_blog_id = $this->_storage->network_install_blog_id; if ( is_numeric( $network_install_blog_id ) && $except_blog_id != $network_install_blog_id && self::is_site_active( $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; } } } // Get first opted-in blog ID with active tracking. $installs = $this->get_blog_install_map(); foreach ( $installs as $blog_id => $install ) { if ( $except_blog_id != $blog_id && self::is_site_active( $blog_id ) && $this->is_tracking_allowed( $blog_id, $install ) ) { return $blog_id; } } return 0; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. * @param string $action_tag Callback action tag. * @param bool $is_network_clear If set to TRUE, clear sync cron even if there are installs that are still connected. */ private function clear_cron( $name, $action_tag = '', $is_network_clear = false ) { $this->_logger->entrance( $name ); if ( ! $this->is_cron_on( $name ) ) { return; } $clear_cron = true; if ( ! $is_network_clear && $this->_is_network_active ) { $installs = $this->get_blog_install_map(); foreach ( $installs as $blog_id => $install ) { /** * @var FS_Site $install */ if ( $this->is_tracking_allowed( $blog_id, $install ) ) { $clear_cron = false; break; } } } if ( ! $clear_cron ) { return; } $cron_blog_id = $this->get_cron_blog_id( $name ); $this->clear_cron_data( $name ); if ( 0 < $cron_blog_id ) { switch_to_blog( $cron_blog_id ); } if ( empty( $action_tag ) ) { $action_tag = $name; } wp_clear_scheduled_hook( $this->get_action_tag( $action_tag ) ); if ( 0 < $cron_blog_id ) { restore_current_blog(); } } /** * Unix timestamp for next cron execution or false if not scheduled. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. * @param string $action_tag Callback action tag. * * @return int|false */ private function get_next_scheduled_cron( $name, $action_tag = '' ) { $this->_logger->entrance( $name ); if ( ! $this->is_cron_on( $name ) ) { return false; } $cron_blog_id = $this->get_cron_blog_id( $name ); if ( 0 < $cron_blog_id ) { switch_to_blog( $cron_blog_id ); } if ( empty( $action_tag ) ) { $action_tag = $name; } $next_scheduled = wp_next_scheduled( $this->get_action_tag( $action_tag ) ); if ( 0 < $cron_blog_id ) { restore_current_blog(); } return $next_scheduled; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. * @param string $action_tag Callback action tag. * @param string $recurrence 'single' or 'daily'. * @param int $start_at Defaults to now. * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise, schedule job to start right away. * @param int $except_blog_id Target any except the excluded blog ID. */ private function schedule_cron( $name, $action_tag = '', $recurrence = 'single', $start_at = WP_FS__SCRIPT_START_TIME, $randomize_start = true, $except_blog_id = 0 ) { $this->_logger->entrance( $name ); $this->clear_cron( $name, $action_tag, true ); $cron_blog_id = $this->get_cron_target_blog_id( $except_blog_id ); if ( is_multisite() && 0 == $cron_blog_id ) { // Don't schedule cron since couldn't find a target blog. return; } if ( 0 < $cron_blog_id ) { switch_to_blog( $cron_blog_id ); } if ( 'daily' === $recurrence ) { if ( $randomize_start ) { // Schedule first sync with a random 12 hour time range from now. $start_at += rand( 0, ( WP_FS__TIME_24_HOURS_IN_SEC / 2 ) ); } // Schedule daily WP cron. wp_schedule_event( $start_at, 'daily', $this->get_action_tag( $action_tag ) ); } else if ( 'single' === $recurrence ) { // Schedule single cron. wp_schedule_single_event( $start_at, $this->get_action_tag( $action_tag ) ); } $this->set_cron_data( $name, $cron_blog_id ); if ( 0 < $cron_blog_id ) { restore_current_blog(); } } /** * Consolidated cron execution for performance optimization. The max number of API requests is based on the number of unique opted-in users. * that doesn't halt page loading. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $name Cron name. * @param callable $callable The function that should be executed. */ private function execute_cron( $name, $callable ) { $this->_logger->entrance( $name ); // Store the last time data sync was executed. $this->set_cron_execution_timestamp( $name ); // Check if API is temporary down. if ( FS_Api::is_temporary_down() ) { return; } // @todo Add logic that identifies API latency, and reschedule the next background sync randomly between 8-16 hours. $users_2_blog_ids = array(); if ( ! is_multisite() ) { // Add dummy blog. $users_2_blog_ids[0] = array( 0 ); } else { $installs = $this->get_blog_install_map(); foreach ( $installs as $blog_id => $install ) { 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(); } $users_2_blog_ids[ $install->user_id ][] = $blog_id; } } } $current_blog_id = get_current_blog_id(); foreach ( $users_2_blog_ids as $user_id => $blog_ids ) { if ( 0 < $blog_ids[0] ) { $this->switch_to_blog( $blog_ids[0] ); } call_user_func_array( $callable, array( $blog_ids, ( is_multisite() ? $current_blog_id : null ) ) ); foreach ( $blog_ids as $blog_id ) { $this->do_action( "after_{$name}_cron", $blog_id ); } } if ( is_multisite() ) { $this->switch_to_blog( $current_blog_id, fs_is_network_admin() ? $this->get_network_install() : null ); $this->do_action( "after_{$name}_cron_multisite" ); } } #endregion #---------------------------------------------------------------------------------- #region Daily Sync Cron #---------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return bool */ private function is_sync_cron_scheduled() { return $this->is_cron_on( 'sync' ); } /** * Get the sync cron's executing blog ID. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return int */ private function get_sync_cron_blog_id() { return $this->get_cron_blog_id( 'sync' ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7.3 */ private function run_manual_sync() { if ( ! $this->is_user_admin() ) { return; } // Run manual sync. $this->_sync_cron(); // Reschedule next cron to run 24 hours from now (performance optimization). $this->schedule_sync_cron( time() + WP_FS__TIME_24_HOURS_IN_SEC, false ); } /** * Data sync cron job. Replaces the background sync non blocking HTTP request * that doesn't halt page loading. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * @since 2.0.0 Consolidate all the data sync into the same cron for performance optimization. The max number of API requests is based on the number of unique opted-in users. */ function _sync_cron() { $this->_logger->entrance(); $this->execute_cron( 'sync', array( &$this, '_sync_cron_method' ) ); } /** * The actual data sync cron logic. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int[] $blog_ids * @param int|null $current_blog_id @since 2.2.3. This is passed from the `execute_cron` method and used by the * `_sync_plugin_license` method in order to switch to the previous blog when sending * updates for a single site in case `execute_cron` has switched to a different blog. */ function _sync_cron_method( array $blog_ids, $current_blog_id = null ) { if ( $this->is_registered() ) { if ( $this->has_paid_plan() ) { // Initiate background plan sync. $this->_sync_license( true, false, $current_blog_id ); if ( $this->is_paying() ) { // Check for premium plugin updates. $this->check_updates( true ); } } else { // Sync install(s) (only if something changed locally). if ( 1 < count( $blog_ids ) ) { $this->sync_installs(); } else { $this->sync_install(); } $this->maybe_sync_install_user(); } } } /** * Check if sync was executed in the last $period of seconds. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @param int $period In seconds * * @return bool */ private function is_sync_executed( $period = WP_FS__TIME_24_HOURS_IN_SEC ) { return $this->is_cron_executed( 'sync', $period ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @return bool */ 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 * * @param int $start_at Defaults to now. * @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise, schedule job to start right away. * @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_sync_cron( $start_at = WP_FS__SCRIPT_START_TIME, $randomize_start = true, $except_blog_id = 0 ) { $this->schedule_cron( 'sync', 'data_sync', 'daily', $start_at, $randomize_start, $except_blog_id ); } /** * Add the actual sync function to the cron job hook. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 */ private function hook_callback_to_sync_cron() { $this->add_action( 'data_sync', array( &$this, '_sync_cron' ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @param bool $is_network_clear Since 2.0.0 If set to TRUE, clear sync cron even if there are installs that are still connected. */ private function clear_sync_cron( $is_network_clear = false ) { $this->_logger->entrance(); $this->clear_cron( 'sync', 'data_sync', $is_network_clear ); } /** * Unix timestamp for next sync cron execution or false if not scheduled. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @return int|false */ function next_sync_cron() { return $this->get_next_scheduled_cron( 'sync', 'data_sync' ); } /** * Unix timestamp for previous sync cron execution or false if never executed. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @return int|false */ function last_sync_cron() { return $this->cron_last_execution( 'sync' ); } #endregion Daily Sync Cron ------------------------------------------------------------------ #---------------------------------------------------------------------------------- #region Async Install Sync #---------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @return bool */ private function is_install_sync_scheduled() { return $this->is_cron_on( 'install_sync' ); } /** * Get the sync cron's executing blog ID. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return int */ private function get_install_sync_cron_blog_id() { return $this->get_cron_blog_id( 'install_sync' ); } /** * Instead of running blocking install sync event, execute non blocking scheduled wp-cron. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @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 ); } /** * Unix timestamp for previous install sync cron execution or false if never executed. * * @todo There's some very strange bug that $this->_storage->install_sync_timestamp value is not being updated. But for sure the sync event is working. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @return int|false */ function last_install_sync() { return $this->cron_last_execution( 'install_sync' ); } /** * Unix timestamp for next install sync cron execution or false if not scheduled. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @return int|false */ function next_install_sync() { return $this->get_next_scheduled_cron( 'install_sync', 'install_sync' ); } /** * Add the actual install sync function to the cron job hook. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 */ private function hook_callback_to_install_sync() { $this->add_action( 'install_sync', array( &$this, '_run_sync_install' ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @param bool $is_network_clear Since 2.0.0 If set to TRUE, clear sync cron even if there are installs that are still connected. */ private function clear_install_sync_cron( $is_network_clear = false ) { $this->_logger->entrance(); $this->clear_cron( 'install_sync', 'install_sync', $is_network_clear ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * @since 2.0.0 Consolidate all the data sync into the same cron for performance optimization. The max number of API requests is based on the number of unique opted-in users. */ public function _run_sync_install() { $this->_logger->entrance(); $this->execute_cron( 'sync', array( &$this, '_sync_install_cron_method' ) ); } /** * The actual install(s) sync cron logic. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int[] $blog_ids * @param int|null $current_blog_id */ function _sync_install_cron_method( array $blog_ids, $current_blog_id = null ) { if ( $this->is_registered() ) { if ( 1 < count( $blog_ids ) ) { $this->sync_installs( array(), true ); } else { $this->sync_install( array(), true ); } $this->maybe_sync_install_user(); } } #endregion Async Install Sync ------------------------------------------------------------------ /** * 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_address * @param bool $is_pending_trial Since 1.2.1.5 * @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( vsprintf( $formatted_message, $formatted_message_args ), 'activation_pending', $notice_title ); } /** * Check if currently in plugin activation. * * @author Vova Feldman (@svovaf) * @since 1.1.4 * * @return bool */ function is_plugin_activation() { $result = get_transient( "fs_{$this->_module_type}_{$this->_slug}_activated" ); return !empty($result); } /** * * NOTE: admin_menu action executed before admin_init. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ function _admin_init_action() { $is_migration = $this->is_migration(); /** * Automatically redirect to connect/activation page after plugin activation. * * @since 1.1.7 Do NOT redirect to opt-in when running in network admin mode. */ if ( $this->is_plugin_activation() ) { delete_transient( "fs_{$this->_module_type}_{$this->_slug}_activated" ); if ( isset( $_GET['activate-multi'] ) ) { /** * 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; } } if ( $is_migration ) { return; } if ( fs_request_is_action( $this->get_unique_affix() . '_skip_activation' ) ) { check_admin_referer( $this->get_unique_affix() . '_skip_activation' ); $this->skip_connection( fs_is_network_admin() ); fs_redirect( $this->get_after_activation_url( 'after_skip_url' ) ); } if ( $this->is_network_activation_mode() && fs_request_is_action( $this->get_unique_affix() . '_delegate_activation' ) ) { check_admin_referer( $this->get_unique_affix() . '_delegate_activation' ); $this->delegate_connection(); fs_redirect( $this->get_after_activation_url( 'after_delegation_url' ) ); } $this->_add_upgrade_action_link(); if ( ! ( ! $this->_is_network_active && fs_is_network_admin() ) && ( ( true === $this->_storage->require_license_activation ) || // Not registered nor anonymous. ( ! $this->is_registered() && ! $this->is_anonymous() ) || // OR, network level and in network upgrade mode. ( fs_is_network_admin() && $this->_is_network_active && $this->is_network_upgrade_mode() ) ) ) { if ( ! $this->is_pending_activation() ) { if ( ! $this->is_activation_page() ) { /** * If a user visits any other admin page before activating the premium-only theme with a valid * license, reactivate the previous theme. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ if ( $this->is_theme() && ! $this->has_settings_menu() && ! isset( $_REQUEST['fs_action'] ) && $this->can_activate_previous_theme() ) { if ( $this->is_only_premium() ) { $this->activate_previous_theme(); return; } if ( true === $this->_storage->require_license_activation ) { $this->_storage->require_license_activation = false; } } if ( ! fs_is_network_admin() && $this->is_network_activation_mode() && ! $this->is_delegated_connection() ) { return; } if ( $this->is_plugin_new_install() || $this->is_only_premium() ) { if ( ! $this->_anonymous_mode && ( ! $this->is_addon() || ! $this->_parent->is_anonymous() ) ) { // Show notice for new plugin installations. $this->_admin_notices->add( sprintf( $this->get_text_inline( 'You are just one step away - %s', 'you-are-step-away' ), sprintf( '%s', $this->get_activation_url( array(), ! $this->is_delegated_connection() ), sprintf( $this->get_text_x_inline( 'Complete "%s" Activation Now', '%s - plugin name. As complete "PluginX" activation now', 'activate-x-now' ), $this->get_plugin_name() ) ) ), '', 'update-nag' ); } } else { if ( $this->should_add_sticky_optin_notice() ) { $this->add_sticky_optin_admin_notice(); } if ( $this->has_filter( 'optin_pointer_element' ) ) { // Don't show admin nag if plugin update. wp_enqueue_script( 'wp-pointer' ); wp_enqueue_style( 'wp-pointer' ); $this->_enqueue_connect_essentials(); add_action( 'admin_print_footer_scripts', array( $this, '_add_connect_pointer_script' ) ); } } } } if ( $this->show_opt_in_on_themes_page() && $this->is_activation_page() ) { $this->_show_theme_activation_optin_dialog(); } } } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return bool */ private function should_add_sticky_optin_notice() { if ( $this->is_addon() && $this->_parent->is_anonymous() ) { return false; } if ( fs_is_network_admin() ) { if ( ! $this->_is_network_active ) { return false; } if ( ! $this->is_network_activation_mode() ) { return false; } return ! isset( $this->_storage->sticky_optin_added_ms ); } if ( ! $this->is_activation_mode() ) { return false; } // If running from a blog admin and delegated the connection. return ! isset( $this->_storage->sticky_optin_added ); } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 */ private function add_sticky_optin_admin_notice() { if ( ! $this->_is_network_active || ! fs_is_network_admin() ) { $this->_storage->sticky_optin_added = true; } else { $this->_storage->sticky_optin_added_ms = true; } // Show notice for new plugin installations. $this->_admin_notices->add_sticky( sprintf( $this->get_text_inline( 'We made a few tweaks to the %s, %s', 'few-plugin-tweaks' ), $this->_module_type, sprintf( '%s', $this->get_activation_url(), sprintf( $this->get_text_inline( 'Opt in to make "%s" better!', 'optin-x-now' ), $this->get_plugin_name() ) ) ), 'connect_account', '', 'update-nag' ); } /** * Enqueue connect requires scripts and styles. * * @author Vova Feldman (@svovaf) * @since 1.1.4 */ function _enqueue_connect_essentials() { wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'json2' ); fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' ); fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); } /** * Add connect / opt-in pointer. * * @author Vova Feldman (@svovaf) * @since 1.1.4 */ function _add_connect_pointer_script() { $vars = array( 'id' => $this->_module_id ); $pointer_content = fs_get_template( 'connect.php', $vars ); ?> _menu->get_raw_slug() ) || fs_is_plugin_page( $this->_slug ); } /* Events ------------------------------------------------------------------------------------------------------------------*/ /** * Delete site install from Database. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param bool $store * @param int|null $blog_id Since 2.0.0 * * @return false|int The install ID if deleted. Otherwise, FALSE (when install not exist). */ function _delete_site( $store = true, $blog_id = null ) { return self::_delete_site_by_slug( $this->_slug, $this->_module_type, $store, $blog_id ); } /** * Delete site install from Database. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @param string $slug * @param string $module_type * @param bool $store * @param int|null $blog_id Since 2.0.0 * * @return false|int The install ID if deleted. Otherwise, FALSE (when install not exist). */ static function _delete_site_by_slug( $slug, $module_type, $store = true, $blog_id = null ) { $sites = self::get_all_sites( $module_type, $blog_id ); $install_id = false; if ( isset( $sites[ $slug ] ) ) { if ( is_object( $sites[ $slug ] ) ) { $install_id = $sites[ $slug ]->id; } unset( $sites[ $slug ] ); self::set_account_option_by_module( $module_type, 'sites', $sites, $store, $blog_id ); } return $install_id; } /** * Delete user. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $user_id * @param bool $store * * @return false|int The user ID if deleted. Otherwise, FALSE (when install not exist). */ private static function delete_user( $user_id, $store = true ) { $users = self::get_all_users(); if ( ! is_array( $users ) || ! isset( $users[ $user_id ] ) ) { return false; } unset( $users[ $user_id ] ); self::$_accounts->set_option( 'users', $users, $store ); return $user_id; } /** * Delete plugin's plans information. * * @param bool $store Flush to Database if true. * @param bool $keep_associated_plans If set to false, delete all plans, even if a plan is associated with an install. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ private function _delete_plans( $store = true, $keep_associated_plans = true ) { $this->_logger->entrance(); $plans = self::get_all_plans( $this->_module_type ); $plans_to_keep = array(); if ( $keep_associated_plans ) { $plans_ids_to_keep = $this->get_plans_ids_associated_with_installs(); foreach ( $plans_ids_to_keep as $plan_id ) { $plan = self::_get_plan_by_id( $plan_id ); if ( is_object( $plan ) ) { $plans_to_keep[] = self::_encrypt_entity( $plan ); } } } if ( ! empty( $plans_to_keep ) ) { $plans[ $this->_slug ] = $plans_to_keep; } else { unset( $plans[ $this->_slug ] ); } $this->set_account_option( 'plans', $plans, $store ); } /** * Delete all plugin licenses. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param bool $store */ private function _delete_licenses( $store = true ) { $this->_logger->entrance(); $all_licenses = self::get_all_licenses(); unset( $all_licenses[ $this->_module_id ] ); self::$_accounts->set_option( 'all_licenses', $all_licenses, $store ); } /** * Check if Freemius was added on new plugin installation. * * @author Vova Feldman (@svovaf) * @since 1.1.5 * * @return bool */ function is_plugin_new_install() { return isset( $this->_storage->is_plugin_new_install ) && $this->_storage->is_plugin_new_install; } /** * Check if it's the first plugin release that is running Freemius. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @return bool */ function is_first_freemius_powered_version() { return empty( $this->_storage->plugin_last_version ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool|string */ private function get_previous_theme_slug() { return isset( $this->_storage->previous_theme ) ? $this->_storage->previous_theme : false; } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool */ private function can_activate_previous_theme() { 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 ); return $theme_instance->exists(); } return false; } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 */ private function activate_previous_theme() { switch_theme( $this->get_previous_theme_slug() ); unset( $this->_storage->previous_theme ); global $pagenow; if ( 'themes.php' === $pagenow ) { /** * Refresh the active theme information. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ fs_redirect( $this->admin_url( $pagenow ) ); } } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return string */ function get_previous_theme_activation_url() { if ( ! $this->can_activate_previous_theme() ) { return ''; } /** * Activation URL * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ return wp_nonce_url( $this->admin_url( 'themes.php?action=activate&stylesheet=' . urlencode( $this->get_previous_theme_slug() ) ), 'switch-theme_' . $this->get_previous_theme_slug() ); } /** * Saves the slug of the previous theme if it still exists so that it can be used by the logic in the opt-in * form that decides whether to add a close button to the opt-in dialog or not. So after a premium-only theme is * activated, the close button will appear and will reactivate the previous theme if clicked. If the previous * theme doesn't exist, then there will be no close button. * * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @param string $slug_or_name Old theme's slug or name. * @param bool|WP_Theme $old_theme WP_Theme instance of the old theme if it still exists. */ function _activate_theme_event_hook( $slug_or_name, $old_theme = false ) { $this->_storage->previous_theme = ( false !== $old_theme ) ? $old_theme->get_stylesheet() : $slug_or_name; $this->_activate_plugin_event_hook(); } /** * Plugin activated hook. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @uses FS_Api */ function _activate_plugin_event_hook() { $this->_logger->entrance( 'slug = ' . $this->_slug ); if ( ! $this->is_user_admin() ) { return; } $this->unregister_uninstall_hook(); // Clear API cache on activation. FS_Api::clear_cache(); $is_premium_version_activation = $this->is_plugin() ? ( 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() ) { // This logic is relevant only to plugins since both the free and premium versions of a plugin can be active at the same time. // 1. If running in the activation of the FREE module, get the basename of the PREMIUM. // 2. If running in the activation of the PREMIUM module, get the basename of the FREE. $other_version_basename = $is_premium_version_activation ? $this->_free_plugin_basename : $this->premium_plugin_basename(); if ( ! $this->_is_network_active ) { /** * Themes are always network activated, but the ACTUAL activation is per site. * * During the activation, the plugin isn't yet active, therefore, * _is_network_active will be set to false even if it's a network level * activation. So we need to fix that by looking at the is_network_admin() value. * * @author Vova Feldman */ $this->_is_network_active = ( $this->_is_multisite_integrated && fs_is_network_admin() ); } /** * If the other module version is active, deactivate it. * * is_plugin_active() checks if the plugin is active on the site or the network level and * deactivate_plugins() deactivates the plugin whether it's activated on the site or network level. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ if ( is_plugin_active( $other_version_basename ) && $this->apply_filters( 'deactivate_on_activation', true ) ) { deactivate_plugins( $other_version_basename ); } } if ( $this->is_registered() ) { if ( $is_premium_version_activation ) { $this->reconnect_locally(); } // Schedule re-activation event and sync. // $this->sync_install( array(), true ); $this->schedule_install_sync(); // If activating the premium module version, add an admin notice to congratulate for an upgrade completion. if ( $is_premium_version_activation ) { $this->_admin_notices->add( sprintf( $this->get_text_inline( 'The upgrade of %s was successfully completed.', 'successful-version-upgrade-message' ), sprintf( '%s', $this->_plugin->title ) ), $this->get_text_x_inline( 'W00t', 'Used to express elation, enthusiasm, or triumph (especially in electronic communication).', 'woot' ) . '!' ); } } else if ( $this->is_anonymous() ) { if ( isset( $this->_storage->is_anonymous_ms ) && $this->_storage->is_anonymous_ms['is'] ) { $plugin_version = $this->_storage->is_anonymous_ms['version']; $network = true; } else { $plugin_version = isset( $this->_storage->is_anonymous ) ? $this->_storage->is_anonymous['version'] : null; $network = false; } /** * Reset "skipped" click cache on the following: * 1. Freemius DEV mode. * 2. WordPress DEBUG mode. * 3. If a plugin and the user skipped the exact same version before. * * @since 1.2.2.7 Ulrich Pogson (@grapplerulrich) asked to not reset the SKIPPED flag if the exact same THEME version was activated before unless the developer is running with WP_DEBUG on, or Freemius debug mode on (WP_FS__DEV_MODE). * * @todo 4. If explicitly asked to retry after every activation. */ if ( WP_FS__DEV_MODE || ( ( $this->is_plugin() || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) && $this->get_plugin_version() == $plugin_version ) ) { $this->reset_anonymous_mode( $network ); } } $is_trial_or_has_features_enabled_license = ( $this->is_trial() || $this->has_features_enabled_license() ); if ( $this->is_addon() && ! $is_trial_or_has_features_enabled_license ) { /** * When activating an add-on, try to also activate a license. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ if ( ! $this->_is_network_active ) { $this->maybe_activate_addon_license(); } else { $this->maybe_network_activate_addon_license(); } /** * Avoid redirecting to the license activation screen after automatically activating an add-on license. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $is_trial_or_has_features_enabled_license = ( $this->is_trial() || $this->has_features_enabled_license() ); if ( $is_trial_or_has_features_enabled_license && true === $this->_storage->require_license_activation ) { $this->_storage->require_license_activation = false; } } if ( $is_premium_version_activation && ( ( ! $this->is_registered() && $this->is_anonymous() ) || ( $this->is_registered() && ! $is_trial_or_has_features_enabled_license ) ) ) { $this->_storage->require_license_activation = true; } if ( ! isset( $this->_storage->is_plugin_new_install ) ) { /** * If no previous version of plugin's version exist, it means that it's either * the first time that the plugin installed on the site, or the plugin was installed * before but didn't have Freemius integrated. * * Since register_activation_hook() do NOT fires on updates since 3.1, and only fires * on manual activation via the dashboard, is_plugin_activation() is TRUE * only after immediate activation. * * @since 1.1.4 * @link https://make.wordpress.org/core/2010/10/27/plugin-activation-hooks-no-longer-fire-for-updates/ */ $this->_storage->is_plugin_new_install = empty( $this->_storage->plugin_last_version ); } /** * Also flush when activating the premium version so that even if Freemius was off before, the API * connectivity test can be run again. * * @author Leo Fajardo (@leorw) * @since 2.2.3.1 */ $has_api_connectivity = $this->has_api_connectivity( WP_FS__DEV_MODE || $is_premium_version_activation ); if ( ! $this->_anonymous_mode && ( false !== $has_api_connectivity ) && ! $this->_isAutoInstall ) { // Store hint that the plugin was just activated to enable auto-redirection to settings. set_transient( "fs_{$this->_module_type}_{$this->_slug}_activated", true, 60 ); } /** * Activation hook is executed after the plugin's main file is loaded, therefore, * after the plugin was loaded. The logic is located at activate_plugin() * ./wp-admin/includes/plugin.php. * * @author Vova Feldman (@svovaf) * @since 1.1.9 */ $this->_storage->was_plugin_loaded = true; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 */ private function maybe_activate_addon_license() { $parent_fs = $this->get_parent_instance(); if ( ! is_object( $parent_fs ) || ( ! $parent_fs->is_registered() && ! $parent_fs->is_network_registered() ) ) { // Try to activate a license only if the parent plugin is active and has a valid `install`. return; } $license = $this->get_active_parent_license(); if ( ! is_object( $license ) ) { return; } if ( $this->is_bundle_license_auto_activation_enabled() && ! empty( $license->products ) ) { $this->activate_bundle_license( $license ); return; } if ( ! $this->is_registered() ) { // Opt in with a license key. $this->opt_in( $parent_fs->get_current_or_network_user()->email, false, false, $license->secret_key ); } else { // Activate the license. $install = $this->api_site_call( '/', 'put', array( 'license_key' => $this->apply_filters( 'license_key', $license->secret_key ) ) ); if ( ! FS_Api::is_api_error( $install ) ) { $this->_sync_addon_license( $this->get_id(), true ); } } } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param FS_Plugin_License $license */ private function maybe_network_activate_addon_license( $license = null ) { $parent_fs = $this->get_parent_instance(); if ( ! is_object( $parent_fs ) || ( ! $parent_fs->is_registered() && ! $parent_fs->is_network_registered() ) ) { // Try to activate a license only if the parent plugin is active and has a valid `install`. return; } $license = ( ! is_null( $license ) ) ? $license : $this->get_active_parent_license(); if ( ! is_object( $license ) ) { return; } if ( $this->is_bundle_license_auto_activation_enabled() && ! empty( $license->products ) ) { $this->activate_bundle_license( $license ); return; } if ( ! $this->is_network_registered() ) { $sites = $this->get_sites_for_network_level_optin(); if ( count( $sites ) > $license->left() ) { // If the add-on is network active, try to activate the license only if it can be activated on all sites. return; } // Opt in with a license key. $this->opt_in( $parent_fs->get_user()->email, false, false, $license->secret_key, false, false, false, null, $sites ); } else { $blog_2_install_map = array(); $site_ids = array(); $all_sites = Freemius::get_sites(); foreach ( $all_sites as $site ) { $blog_id = Freemius::get_site_blog_id( $site ); $install = $this->get_install_by_blog_id( $blog_id ); if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) { // Skip license activation for installs that are already associated with a license. continue; } if ( is_object( $install ) ) { $blog_2_install_map[ $blog_id ] = $install; } else { $site_ids[] = $blog_id; } } if ( ( count( $blog_2_install_map ) + count( $site_ids ) ) > $license->left() ) { return; } $user = $this->get_current_or_network_user(); if ( ! empty( $blog_2_install_map ) ) { $result = $this->activate_license_on_many_installs( $user, $license->secret_key, $blog_2_install_map ); if ( true !== $result ) { return; } } if ( ! empty( $site_ids ) ) { $this->activate_license_on_many_sites( $user, $license->secret_key, $site_ids ); } } } /** * Tries to activate a bundle license for all supported products if the current product is activated with a bundle license. This is called after activating an available license (not via the license activation dialog but by clicking on a license activation button) for a product via its "Account" page. * * @author Leo Fajardo (@leorw) * @since 2.4.0 * * @param FS_Plugin_License $license * @param array $sites * @param int $blog_id */ private function maybe_activate_bundle_license( FS_Plugin_License $license = null, $sites = array(), $blog_id = 0 ) { if ( ! is_object( $license ) && $this->has_active_valid_license() ) { $license = $this->_license; } if ( ! is_object( $license ) ) { return; } $parent_license = ( ! empty( $license->products ) ) ? $license : $this->get_active_parent_license( $license->secret_key ); if ( is_object( $parent_license ) ) { $this->activate_bundle_license( $parent_license, $sites, $blog_id ); } } /** * Try to activate a bundle license for all the bundle products installed on the site. * (1) If a child product install already has a license, the bundle license won't be activated. * (2) On multi-site networks, if the attempt to activate the bundle license is triggered from the network admin, the bundle license activation will only work for non-delegated sites and only if none of them is associated with a license. Even if one of the sites has the product installed with a license key, skip the bundle license activation for the product. * (3) On multi-site networks, if the attempt to activate the bundle license is triggered from a site-level admin, only activate the license if the product is site-level activated or delegated, and the product installation is not yet associated with a license. * * @author Leo Fajardo (@leorw) * @since 2.4.0 * * @param FS_Plugin_License $license * @param array $sites * @param int $current_blog_id */ private function activate_bundle_license( $license, $sites = array(), $current_blog_id = 0 ) { $is_network_admin = fs_is_network_admin(); $installs_by_blog_map = array(); $site_info_by_blog_map = array(); /** * Try to activate the license for all supported products. * * @author Leo Fajardo */ foreach ( $license->products as $product_id ) { $fs = self::get_instance_by_id( $product_id ); if ( ! is_object( $fs ) ) { continue; } if ( ! $fs->has_paid_plan() ) { continue; } if ( ! $fs->is_addon() && ! FS_Plan_Manager::instance()->has_paid_plan( $fs->_plans ) ) { /** * The parent product can be free-only but can have its `has_paid_plan` flag set to `true` when * there is a context bundle. */ continue; } if ( $current_blog_id > 0 ) { $fs->switch_to_blog( $current_blog_id ); } if ( $fs->has_active_valid_license() ) { continue; } if ( ! $is_network_admin || $current_blog_id > 0 ) { if ( $fs->is_network_active() && ! $fs->is_delegated_connection( $current_blog_id ) ) { // Do not try to activate the license in the site level if the product is network active and the connection was not delegated. continue; } } else { if ( ! $fs->is_network_active() ) { // Do not try to activate the license in the network level if the product is not network active. continue; } if ( $fs->is_network_delegated_connection() ) { // Do not try to activate the license in the network level if the activation has been delegated to site admins. continue; } $has_install_with_license = false; // Collection of sites that have an install entity that is not activated with a license or non-delegated sites that have no install entity, or both types of site. $filtered_sites = array(); if ( empty( $sites ) ) { $all_sites = self::get_sites(); foreach ( $all_sites as $site ) { $sites[] = array( 'blog_id' => self::get_site_blog_id( $site ) ); } } else { // Populate the map here to avoid calling `$fs->get_site_info( $site );` in the other `for` loop below. foreach ( $sites as $site ) { if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) { continue; } $site_info_by_blog_map[ $site['blog_id'] ] = $site; } } foreach ( $sites as $site ) { if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) { continue; } $blog_id = $site['blog_id']; if ( ! isset( $installs_by_blog_map[ $blog_id ] ) ) { $installs_by_blog_map[ $blog_id ] = self::get_all_sites( $fs->get_module_type(), $blog_id ); } $installs = $installs_by_blog_map[ $blog_id ]; $install = null; if ( isset( $installs[ $fs->get_slug() ] ) ) { $install = $installs[ $fs->get_slug() ]; if ( is_object( $install ) && ( ! FS_Site::is_valid_id( $install->id ) || ! FS_User::is_valid_id( $install->user_id ) || ! FS_Plugin_Plan::is_valid_id( $install->plan_id ) ) ) { $install = null; } } if ( is_object( $install ) && FS_Plugin_License::is_valid_id( $install->license_id ) ) { $has_install_with_license = true; break; } if ( $fs->is_site_delegated_connection( $blog_id ) ) { // Site activation delegated, don't activate bundle license on the site in the network admin. continue; } if ( ! isset( $site_info_by_blog_map[ $blog_id ] ) ) { $site_info_by_blog_map[ $blog_id ] = $fs->get_site_info( $site ); } $filtered_sites[] = $site_info_by_blog_map[ $blog_id ]; } if ( $has_install_with_license || empty( $filtered_sites ) ) { // Do not try to activate the license at the network level if there's any install with a license or there's no site to activate the license on. continue; } $sites = $filtered_sites; } $fs->activate_migrated_license( $license->secret_key, null, null, $sites, ( $current_blog_id > 0 ? $current_blog_id : null ) ); } } /** * Returns a parent license that can be activated for the context product. * * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param string|null $license_key * @param bool $flush * * @return FS_Plugin_License */ function get_active_parent_license( $license_key = null, $flush = true ) { $parent_licenses_endpoint = "/plugins/{$this->get_id()}/parent_licenses.json?filter=activatable"; $fs = $this; if ( $this->is_addon() ) { $parent_instance = $this->get_parent_instance(); if ( is_object( $parent_instance ) && $parent_instance->is_registered() ) { $fs = $parent_instance; } } $foreign_licenses = $fs->get_foreign_licenses_info( self::get_all_licenses( $this->get_parent_id() ) ); if ( ! empty ( $foreign_licenses ) ) { $foreign_licenses = array( // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) ); $parent_licenses_endpoint = add_query_arg( $foreign_licenses, $parent_licenses_endpoint ); } $result = $fs->get_current_or_network_user_api_scope()->get( $parent_licenses_endpoint, $flush ); if ( ! $this->is_api_result_object( $result, 'licenses' ) || ! is_array( $result->licenses ) || empty( $result->licenses ) ) { return null; } $parent_license = null; if ( empty( $license_key ) ) { $parent_license = $result->licenses[0]; } else { foreach ( $result->licenses as $license ) { if ( $license_key === $license->secret_key ) { $parent_license = $license; break; } } } if ( ! is_null( $parent_license ) ) { $parent_license = new FS_Plugin_License( $parent_license ); } return $parent_license; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @return array */ function get_sites_for_network_level_optin() { $sites = array(); $all_sites = self::get_sites(); foreach ( $all_sites as $site ) { $blog_id = self::get_site_blog_id( $site ); if ( ! $this->is_site_delegated_connection( $blog_id ) && ! $this->is_installed_on_site( $blog_id ) ) { $sites[] = $this->get_site_info( $site ); } } return $sites; } /** * Delete account. * * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param bool $check_user Enforce checking if user have plugins activation privileges. */ function delete_account_event( $check_user = true ) { $this->_logger->entrance( 'slug = ' . $this->_slug ); if ( $check_user && ! $this->is_user_admin() ) { return; } $this->do_action( 'before_account_delete' ); // Clear all admin notices. $this->_admin_notices->clear_all_sticky( false ); $this->_delete_site( false ); $delete_network_common_data = true; if ( $this->_is_network_active ) { $installs = $this->get_blog_install_map(); // Don't delete common network data unless no other installs left. $delete_network_common_data = empty( $installs ); } if ( $delete_network_common_data ) { $this->_delete_plans( false ); $this->_delete_licenses( false ); // Delete add-ons related to plugin's account. $this->_delete_account_addons( false ); } // @todo Delete plans and licenses of add-ons. self::$_accounts->store(); /** * IMPORTANT: * Clear crons must be executed before clearing all storage. * Otherwise, the cron will not be cleared. */ if ( $delete_network_common_data ) { $this->clear_sync_cron(); } $this->clear_install_sync_cron(); // Clear all storage data. $this->_storage->clear_all( true, array( 'is_delegated_connection', 'connectivity_test', 'is_on', ), false ); // Send delete event. $this->get_api_site_scope()->call( '/', 'delete' ); $this->do_action( 'after_account_delete' ); } /** * Delete network level account. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param bool $check_user Enforce checking if user have plugins activation privileges. */ function delete_network_account_event( $check_user = true ) { $this->_logger->entrance( 'slug = ' . $this->_slug ); if ( $check_user && ! $this->is_user_admin() ) { return; } $this->do_action( 'before_network_account_delete' ); // Clear all admin notices. $this->_admin_notices->clear_all_sticky(); $this->_delete_plans( false, false ); $this->_delete_licenses( false ); // Delete add-ons related to plugin's account. $this->_delete_account_addons( false ); // @todo Delete plans and licenses of add-ons. self::$_accounts->store( true ); /** * IMPORTANT: * Clear crons must be executed before clearing all storage. * Otherwise, the cron will not be cleared. */ $this->clear_sync_cron( true ); $this->clear_install_sync_cron( true ); $sites = self::get_sites(); $install_ids = array(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); if ( $this->is_site_delegated_connection( $blog_id ) ) { continue; } $install_id = $this->_delete_site( true, $blog_id ); // Clear all storage data. $this->_storage->clear_all( true, array( 'connectivity_test' ), $blog_id ); if ( FS_Site::is_valid_id( $install_id ) ) { $install_ids[] = $install_id; } switch_to_blog( $blog_id ); $this->do_action( 'after_account_delete' ); restore_current_blog(); } $this->_storage->clear_all( true, array( 'connectivity_test', 'is_on', ), true ); // Send delete event. if ( ! empty( $install_ids ) ) { $result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json?ids=" . implode( ',', $install_ids ), 'delete' ); } $this->do_action( 'after_network_account_delete' ); } /** * Plugin deactivation hook. * * @author Vova Feldman (@svovaf) * @since 1.0.1 */ function _deactivate_plugin_hook() { $this->_logger->entrance( 'slug = ' . $this->_slug ); if ( ! $this->is_user_admin() ) { return; } $is_network_deactivation = fs_is_network_admin(); $storage_keys_for_removal = array(); $this->_admin_notices->clear_all_sticky(); $storage_keys_for_removal[] = 'sticky_optin_added'; if ( isset( $this->_storage->sticky_optin_added ) ) { unset( $this->_storage->sticky_optin_added ); } if ( ! isset( $this->_storage->is_plugin_new_install ) ) { // Remember that plugin was already installed. $this->_storage->is_plugin_new_install = false; } // Hook to plugin uninstall. register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) ); $this->clear_module_main_file_cache(); $this->clear_sync_cron( $this->_is_network_active ); $this->clear_install_sync_cron(); if ( $this->is_registered() ) { if ( $this->is_premium() && ! $this->has_active_valid_license() ) { FS_Plugin_Updater::instance( $this )->delete_update_data(); } if ( $is_network_deactivation ) { // Send deactivation event. $this->sync_installs( array( 'is_active' => false, ) ); } else { // Send deactivation event. $this->sync_install( array( 'is_active' => false, ) ); } } else { if ( false === $this->has_api_connectivity() && ! $this->is_premium() ) { // Reset connectivity test cache. $this->clear_connectivity_info(); $storage_keys_for_removal[] = 'connectivity_test'; } } if ( $is_network_deactivation ) { if ( isset( $this->_storage->sticky_optin_added_ms ) ) { unset( $this->_storage->sticky_optin_added_ms ); } if ( ! empty( $storage_keys_for_removal ) ) { $sites = self::get_sites(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); foreach ( $storage_keys_for_removal as $key ) { $this->_storage->remove( $key, false, $blog_id ); } $this->_storage->save( $blog_id ); } } } // Clear API cache on deactivation. FS_Api::clear_cache(); $this->remove_sdk_reference(); } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 */ private function remove_sdk_reference() { global $fs_active_plugins; foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { if ( $this->_plugin_basename == $data->plugin_path ) { unset( $fs_active_plugins->plugins[ $sdk_path ] ); break; } } fs_fallback_to_newest_active_sdk(); } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param bool $is_anonymous * @param bool|int $network_or_blog_id Since 2.0.0 */ private function set_anonymous_mode( $is_anonymous = true, $network_or_blog_id = 0 ) { // Store information regarding skip to try and opt-in the user // again in the future. $skip_info = array( 'is' => $is_anonymous, 'timestamp' => WP_FS__SCRIPT_START_TIME, 'version' => $this->get_plugin_version(), ); if ( true === $network_or_blog_id ) { $this->_storage->is_anonymous_ms = $skip_info; } else { $this->_storage->store( 'is_anonymous', $skip_info, $network_or_blog_id ); } $this->network_upgrade_mode_completed(); // Update anonymous mode cache. $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 * * @param int $blog_id Site ID. * @param int $user_id User ID. * @param string $domain Site domain. * @param string $path Site path. * @param int $network_id Network ID. Only relevant on multi-network installations. * @param array $meta Metadata. Used to set initial site options. * * @uses Freemius::is_license_network_active() to check if the context license was network activated by the super-admin. * @uses Freemius::is_network_connected() to check if the super-admin network opted-in. * @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. */ 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 ) && $this->_license->can_activate( FS_Site::is_localhost_by_address( $domain ) ) && $this->is_license_network_active( $blog_id ) ) { /** * Running the premium version, the license was network activated, and the license can also be activated on the current site -> so try to opt-in with the license key. */ $current_blog_id = get_current_blog_id(); $license = clone $this->_license; $this->switch_to_blog( $blog_id ); // Opt-in with network user. $this->install_with_user( $this->get_network_user(), $license->secret_key, false, false, false ); if ( is_object( $this->_site ) ) { if ( $this->_site->license_id == $license->id ) { /** * If the license was activated successfully, sync the license data from the remote server. */ $this->_license = $license; $this->sync_site_license(); } } $site = $this->_site; $this->switch_to_blog( $current_blog_id ); 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; } } if ( $this->is_network_anonymous() ) { /** * Opt-in was network skipped so automatically skip the opt-in for the new site. */ $this->skip_site_connection( $blog_id ); } else if ( $this->is_network_delegated_connection() ) { /** * Opt-in was network delegated so automatically delegate the opt-in for the new site's admin. */ $this->delegate_site_connection( $blog_id ); } else if ( $this->is_network_connected() ) { /** * Opt-in was network activated so automatically opt-in with the network user and new site admin. */ $current_blog_id = get_current_blog_id(); $this->switch_to_blog( $blog_id ); // Opt-in with network user. $this->install_with_user( $this->get_network_user(), false, false, false, false ); $site = $this->_site; $this->switch_to_blog( $current_blog_id ); } else { /** * If the super-admin mixed different options (connect, skip, delegated): * a) If at least one site connection was delegated, then automatically delegate connection. * b) Otherwise, it means that at least one site was skipped and at least one site was connected. For a simplified UX in the initial release of the multisite network integration, skip the connection for the newly created site. If the super-admin will want to opt-in they can still do that from the network level Account page. */ $has_delegated_site = false; $sites = self::get_sites(); 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; break; } } if ( $has_delegated_site ) { $this->delegate_site_connection( $blog_id ); } else { $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|int[] $network_or_blog_ids Since 2.0.0. */ 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 ); } /** * Ensure that this field is also "false", otherwise, if the current module's type is "theme" and the module * has no menus, the opt-in popup will not be shown immediately (in this case, the user will have to click * on the admin notice that contains the opt-in link in order to trigger the opt-in popup). * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ 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. * * @author Leo Fajardo (@leorw) * * @since 2.1.3 */ private function maybe_set_slug_and_network_menu_exists_flag() { if ( ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { $this->_menu->set_slug_and_network_menu_exists_flag( $this->_menu->has_menu() ? $this->_menu->get_slug() : $this->_slug ); } } /** * Clears the anonymous mode and redirects to the opt-in screen. * * @author Vova Feldman (@svovaf) * @since 1.1.7 */ function connect_again() { if ( ! $this->is_anonymous() && ! $this->is_pending_activation() ) { return; } 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( $activation_url_params ) ); } /** * Skip account connect, and set anonymous mode. * * @author Vova Feldman (@svovaf) * @since 1.1.1 * * @param bool|int|int[] $network_or_blog_ids Since 2.5.1 */ function skip_connection( $network_or_blog_ids = false ) { $this->_logger->entrance(); $this->_admin_notices->remove_sticky( 'connect_account' ); if ( true === $network_or_blog_ids ) { $this->set_anonymous_mode( true, 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->skip_site_connection( $blog_id ); } $this->network_upgrade_mode_completed(); } /** * Skip connection for specific site in the network. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int|null $blog_id * @param bool $send_skip */ 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 ); } /** * Plugin version update hook. * * @author Vova Feldman (@svovaf) * @since 1.0.4 */ private function update_plugin_version_event() { $this->_logger->entrance(); if ( ! $this->is_registered() ) { return; } $this->schedule_install_sync(); // $this->sync_install( array(), true ); } /** * Generate an MD5 signature of a plugins collection. * This helper methods used to identify changes in a plugins collection. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param array [string]array $plugins * * @return string */ private function get_plugins_thumbprint( $plugins ) { ksort( $plugins ); $thumbprint = ''; foreach ( $plugins as $basename => $data ) { $thumbprint .= $data['slug'] . ',' . $data['Version'] . ',' . ( $data['is_active'] ? '1' : '0' ) . ';'; } return md5( $thumbprint ); } /** * Return a list of modified plugins since the last sync. * * Note: * There's no point to store a plugins counter since even if the number of * plugins didn't change, we still need to check if the versions are all the * same and the activity state is similar. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return array|false */ private function get_plugins_data_for_api() { // Alias. $site_active_plugins_option_name = 'active_plugins'; $network_plugins_option_name = 'all_plugins'; /** * Collection of all site level active plugins. */ $site_active_plugins_cache = self::$_accounts->get_option( $site_active_plugins_option_name ); if ( ! is_object( $site_active_plugins_cache ) ) { $site_active_plugins_cache = (object) array( 'timestamp' => '', 'md5' => '', 'plugins' => array(), ); } $time = time(); if ( ! empty( $site_active_plugins_cache->timestamp ) && ( $time - $site_active_plugins_cache->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC ) { // Don't send plugin updates if last update was in the past 5 min. return false; } // Write timestamp to lock the logic. $site_active_plugins_cache->timestamp = $time; self::$_accounts->set_option( $site_active_plugins_option_name, $site_active_plugins_cache, true ); // Reload options from DB. self::$_accounts->load( true ); $site_active_plugins_cache = self::$_accounts->get_option( $site_active_plugins_option_name ); if ( $time != $site_active_plugins_cache->timestamp ) { // If timestamp is different, then another thread captured the lock. return false; } /** * Collection of all plugins (network level). */ $network_plugins_cache = self::$_accounts->get_option( $network_plugins_option_name ); if ( ! is_object( $network_plugins_cache ) ) { $network_plugins_cache = (object) array( 'timestamp' => '', 'md5' => '', 'plugins' => array(), ); } // Check if there's a change in plugins. $network_plugins = self::get_network_plugins(); $site_active_plugins = self::get_site_active_plugins(); $network_plugins_thumbprint = $this->get_plugins_thumbprint( $network_plugins ); $site_active_plugins_thumbprint = $this->get_plugins_thumbprint( $site_active_plugins ); // Check if plugins status changed (version or active/inactive). $network_plugins_changed = ( $network_plugins_cache->md5 !== $network_plugins_thumbprint ); $site_active_plugins_changed = ( $site_active_plugins_cache->md5 !== $site_active_plugins_thumbprint ); if ( ! $network_plugins_changed && ! $site_active_plugins_changed ) { // No changes. return array(); } $plugins_update_data = array(); foreach ( $network_plugins_cache->plugins as $basename => $data ) { if ( ! isset( $network_plugins[ $basename ] ) ) { // Plugin uninstalled. $uninstalled_plugin_data = $data; $uninstalled_plugin_data['is_active'] = false; $uninstalled_plugin_data['is_uninstalled'] = true; $plugins_update_data[] = $uninstalled_plugin_data; unset( $network_plugins[ $basename ] ); unset( $network_plugins_cache->plugins[ $basename ] ); unset( $site_active_plugins_cache->plugins[ $basename ] ); continue; } $was_active = $data['is_active'] || ( isset( $site_active_plugins_cache->plugins[ $basename ] ) && true === $site_active_plugins_cache->plugins[ $basename ]['is_active'] ); $is_active = $network_plugins[ $basename ]['is_active'] || ( isset( $site_active_plugins[ $basename ] ) && $site_active_plugins[ $basename ]['is_active'] ); if ( ! isset( $site_active_plugins_cache->plugins[ $basename ] ) && isset( $site_active_plugins[ $basename ] ) ) { // Plugin was site level activated. $site_active_plugins_cache->plugins[ $basename ] = $network_plugins[ $basename ]; $site_active_plugins_cache->plugins[ $basename ]['is_active'] = true; } else if ( isset( $site_active_plugins_cache->plugins[ $basename ] ) && ! isset( $site_active_plugins[ $basename ] ) ) { // Plugin was site level deactivated. unset( $site_active_plugins_cache->plugins[ $basename ] ); } $prev_version = $data['version']; $current_version = $network_plugins[ $basename ]['Version']; if ( $was_active !== $is_active || $prev_version !== $current_version ) { // Plugin activated or deactivated, or version changed. if ( $was_active !== $is_active ) { if ( $data['is_active'] != $network_plugins[ $basename ]['is_active'] ) { $network_plugins_cache->plugins[ $basename ]['is_active'] = $data['is_active']; } } if ( $prev_version !== $current_version ) { $network_plugins_cache->plugins[ $basename ]['Version'] = $current_version; } $updated_plugin_data = $data; $updated_plugin_data['is_active'] = $is_active; $updated_plugin_data['version'] = $current_version; $updated_plugin_data['title'] = $network_plugins[ $basename ]['Name']; $plugins_update_data[] = $updated_plugin_data; } } // Find new plugins that weren't yet seen before. foreach ( $network_plugins as $basename => $data ) { if ( ! isset( $network_plugins_cache->plugins[ $basename ] ) ) { // New plugin. $new_plugin = array( 'slug' => $data['slug'], 'version' => $data['Version'], 'title' => $data['Name'], 'is_active' => $data['is_active'], 'is_uninstalled' => false, ); $network_plugins_cache->plugins[ $basename ] = $new_plugin; $is_site_level_active = ( isset( $site_active_plugins[ $basename ] ) && $site_active_plugins[ $basename ]['is_active'] ); /** * If not network active, set the activity status based on the site-level plugin status. */ if ( ! $new_plugin['is_active'] ) { $new_plugin['is_active'] = $is_site_level_active; } $plugins_update_data[] = $new_plugin; if ( isset( $site_active_plugins[ $basename ] ) ) { $site_active_plugins_cache->plugins[ $basename ] = $new_plugin; $site_active_plugins_cache->plugins[ $basename ]['is_active'] = $is_site_level_active; } } } $site_active_plugins_cache->md5 = $site_active_plugins_thumbprint; $site_active_plugins_cache->timestamp = $time; self::$_accounts->set_option( $site_active_plugins_option_name, $site_active_plugins_cache, true ); $network_plugins_cache->md5 = $network_plugins_thumbprint; $network_plugins_cache->timestamp = $time; self::$_accounts->set_option( $network_plugins_option_name, $network_plugins_cache, true ); return $plugins_update_data; } /** * Return a list of modified themes since the last sync. * * Note: * There's no point to store a themes counter since even if the number of * themes didn't change, we still need to check if the versions are all the * same and the activity state is similar. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return array|false */ private function get_themes_data_for_api() { // Alias. $option_name = 'all_themes'; $all_cached_themes = self::$_accounts->get_option( $option_name ); if ( ! is_object( $all_cached_themes ) ) { $all_cached_themes = (object) array( 'timestamp' => '', 'md5' => '', 'themes' => array(), ); } $time = time(); if ( ! empty( $all_cached_themes->timestamp ) && ( $time - $all_cached_themes->timestamp ) < WP_FS__TIME_5_MIN_IN_SEC ) { // Don't send theme updates if last update was in the past 5 min. return false; } // Write timestamp to lock the logic. $all_cached_themes->timestamp = $time; self::$_accounts->set_option( $option_name, $all_cached_themes, true ); // Reload options from DB. self::$_accounts->load( true ); $all_cached_themes = self::$_accounts->get_option( $option_name ); if ( $time != $all_cached_themes->timestamp ) { // If timestamp is different, then another thread captured the lock. return false; } // Get active theme. $active_theme = wp_get_theme(); $active_theme_stylesheet = $active_theme->get_stylesheet(); // Check if there's a change in themes. $all_themes = wp_get_themes(); // Check if themes changed. ksort( $all_themes ); $themes_signature = ''; foreach ( $all_themes as $slug => $data ) { $is_active = ( $slug === $active_theme_stylesheet ); $themes_signature .= $slug . ',' . $data->version . ',' . ( $is_active ? '1' : '0' ) . ';'; } // Check if themes status changed (version or active/inactive). $themes_changed = ( $all_cached_themes->md5 !== md5( $themes_signature ) ); $themes_update_data = array(); if ( $themes_changed ) { // Change in themes, report changes. // Update existing themes info. foreach ( $all_cached_themes->themes as $slug => $data ) { $is_active = ( $slug === $active_theme_stylesheet ); if ( ! isset( $all_themes[ $slug ] ) ) { // Plugin uninstalled. $uninstalled_theme_data = $data; $uninstalled_theme_data['is_active'] = false; $uninstalled_theme_data['is_uninstalled'] = true; $themes_update_data[] = $uninstalled_theme_data; unset( $all_themes[ $slug ] ); unset( $all_cached_themes->themes[ $slug ] ); } else if ( $data['is_active'] !== $is_active || $data['version'] !== $all_themes[ $slug ]->version ) { // Plugin activated or deactivated, or version changed. $all_cached_themes->themes[ $slug ]['is_active'] = $is_active; $all_cached_themes->themes[ $slug ]['version'] = $all_themes[ $slug ]->version; $themes_update_data[] = $all_cached_themes->themes[ $slug ]; } } // Find new themes that weren't yet seen before. foreach ( $all_themes as $slug => $data ) { if ( ! isset( $all_cached_themes->themes[ $slug ] ) ) { $is_active = ( $slug === $active_theme_stylesheet ); // New plugin. $new_plugin = array( 'slug' => $slug, 'version' => $data->version, 'title' => $data->name, 'is_active' => $is_active, 'is_uninstalled' => false, ); $themes_update_data[] = $new_plugin; $all_cached_themes->themes[ $slug ] = $new_plugin; } } $all_cached_themes->md5 = md5( $themes_signature ); $all_cached_themes->timestamp = time(); self::$_accounts->set_option( $option_name, $all_cached_themes, true ); } return $themes_update_data; } /** * Get site data for API install request. * * @author Vova Feldman (@svovaf) * @since 1.1.2 * * @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, title, and URL). * * @return array */ private function get_install_data_for_api( array $override, $include_plugins = true, $include_themes = true, $include_blog_data = true ) { // 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. */ if ( $include_plugins && ! isset( $override['plugins'] ) ) { $plugins = $this->get_plugins_data_for_api(); if ( ! empty( $plugins ) ) { $override['plugins'] = $plugins; } } } if ( ! defined( 'WP_FS__TRACK_THEMES' ) || false !== WP_FS__TRACK_THEMES ) { /** * @since 1.1.8 Also send themes updates. */ if ( $include_themes && ! isset( $override['themes'] ) ) { $themes = $this->get_themes_data_for_api(); if ( ! empty( $themes ) ) { $override['themes'] = $themes; } } } } $versions = $this->get_versions(); $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_uninstalled' => false, ), $override ); } /** * Update installs details. * * @todo V1 of multiste network support doesn't support plugin and theme data sending. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @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. * * @return array */ private function get_installs_data_for_api( array $override, $only_diff = false, $is_keepalive = false, $include_plugins = true, $include_themes = true ) { /** * @since 1.1.8 Also send plugin updates. */ // if ( $include_plugins && ! isset( $override['plugins'] ) ) { // $plugins = $this->get_plugins_data_for_api(); // if ( ! empty( $plugins ) ) { // $override['plugins'] = $plugins; // } // } /** * @since 1.1.8 Also send themes updates. */ // if ( $include_themes && ! isset( $override['themes'] ) ) { // $themes = $this->get_themes_data_for_api(); // if ( ! empty( $themes ) ) { // $override['themes'] = $themes; // } // } // Common properties. $versions = $this->get_versions(); $common = array_merge( $versions, array( 'version' => $this->get_plugin_version(), 'is_premium' => $this->is_premium(), ), $override ); $is_common_diff_for_any_site = false; $common_diff_union = array(); $installs_data = array(); $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 ); $install = $this->get_install_by_blog_id( $blog_id ); if ( is_object( $install ) ) { if ( $install->user_id != $this->_user->id ) { // Install belongs to a different owner. continue; } if ( ! $this->is_tracking_allowed( $blog_id, $install ) ) { // Don't send updates regarding opted-out installs. continue; } $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; } 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_active'] = $this->is_active_for_site( $blog_id ); $install_data['is_uninstalled'] = $install->is_uninstalled; $common_diff = null; $is_common_diff = false; if ( $only_diff ) { $install_data = $this->get_install_diff_for_api( $install_data, $install, $override ); $common_diff = $this->get_install_diff_for_api( $common, $install, $override ); $is_common_diff = ! empty( $common_diff ); if ( $is_common_diff ) { foreach ( $common_diff as $k => $v ) { if ( ! isset( $common_diff_union[ $k ] ) ) { $common_diff_union[ $k ] = $v; } } } $is_common_diff_for_any_site = $is_common_diff_for_any_site || $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; $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; } else if ( ! empty( $common_diff_union ) ) { $installs_data[] = $common_diff_union; } } foreach ( $installs_data as &$data ) { $data = (object) $data; } return $installs_data; } /** * Compare site actual data to the stored install data and return the differences for an API data sync. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param array $site * @param FS_Site $install * @param string[] string $override * * @return array */ private function get_install_diff_for_api( $site, $install, $override = array() ) { $diff = array(); $special = array(); $special_override = false; foreach ( $site as $p => $v ) { if ( property_exists( $install, $p ) ) { if ( ( is_bool( $install->{$p} ) || ! empty( $install->{$p} ) ) && $install->{$p} != $v ) { $val = self::get_api_sanitized_property( $p, $v ); if ( $install->{$p} != $val ) { $install->{$p} = $val; $diff[ $p ] = $val; } } } else { $special[ $p ] = $v; if ( isset( $override[ $p ] ) || 'plugins' === $p || 'themes' === $p ) { $special_override = true; } } } if ( $special_override || 0 < count( $diff ) ) { // Add special params only if has at least one // standard param, or if explicitly requested to // override a special param or a param which is not exist // in the install object. $diff = array_merge( $diff, $special ); } 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 ); } } /** * Update install only if changed. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @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, $is_two_way_sync = false ) { $this->_logger->entrance(); $check_properties = $this->get_install_data_for_api( $override ); if ( $flush ) { $params = $check_properties; } else { $params = $this->get_install_diff_for_api( $check_properties, $this->_site, $override ); } if ( empty( $params ) ) { $keepalive_only_update = $this->should_send_keepalive_update(); if ( ! $keepalive_only_update ) { /** * There are no updates to send including keepalive. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ return false; } } 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. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ if ( ! is_multisite() ) { // Update last install sync timestamp. $this->set_cron_execution_timestamp( 'install_sync' ); } $params['uid'] = $this->get_anonymous_id(); } $this->set_keepalive_timestamp(); // Send updated values to FS. $site = $this->api_site_call( '/', 'put', $params, true ); if ( $is_two_way_sync && $this->is_api_result_entity( $site ) ) { /** * Clear scheduled install sync after a two-way sync call. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ if ( ! is_multisite() ) { // I successfully sent install update, clear scheduled sync if exist. $this->clear_install_sync_cron(); } } return $site; } /** * Update installs only if changed. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @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, $is_two_way_sync = false ) { $this->_logger->entrance(); /** * 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 ); $installs_data = $this->get_installs_data_for_api( $override, ! $flush, $should_send_keepalive ); if ( empty( $installs_data ) ) { return false; } 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' ); } /** * Pass `true` to use the network level storage since the update is for many installs. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ $this->set_keepalive_timestamp( true ); // Send updated values to FS. $result = $this->get_api_user_scope()->call( "/plugins/{$this->_plugin->id}/installs.json", 'put', $installs_data ); 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(); } return $result; } /** * @author Leo Fajardo (@leorw) * * @param bool|null $use_network_level_storage * * @return bool */ private function should_send_keepalive_update( $use_network_level_storage = null ) { $keepalive_timestamp = $this->_storage->get( 'keepalive_timestamp', 0, $use_network_level_storage ); if ( $keepalive_timestamp < ( time() - WP_FS__TIME_WEEK_IN_SEC ) ) { // If updated more than 7 days ago, trigger a keepalive and update the time it was triggered. return true; } else { // If updated 7 days ago or less, "flip a coin", if the value is 7 trigger a keepalive and update the last time it was triggered. return ( 7 == rand( 1, 7 ) ); } } /** * Syncs the install owner's data if needed (i.e., if the install owner is different from the loaded user). * * @author Leo Fajardo (@leorw) * @since 2.3.2 */ private function maybe_sync_install_user() { if ( $this->_user->id == $this->_site->user_id ) { return; } // Fetch user data and store if found. $this->sync_user_by_current_install(); } /** * Update install only if changed. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string[] string $override * @param bool $flush */ function sync_install( $override = array(), $flush = false ) { $this->_logger->entrance(); $site = $this->send_install_update( $override, $flush, true ); if ( false === $site ) { // No sync required. return; } if ( ! $this->is_api_result_entity( $site ) ) { // Failed to sync, don't update locally. return; } $this->_site = new FS_Site( $site ); $this->_store_site( true ); } /** * Update install only if changed. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string[] string $override * @param bool $flush */ private function sync_installs( $override = array(), $flush = false ) { $this->_logger->entrance(); $result = $this->send_installs_update( $override, $flush, true ); if ( false === $result ) { // No sync required. return; } if ( ! $this->is_api_result_object( $result, 'installs' ) ) { // Failed to sync, don't update locally. return; } $address_to_blog_map = $this->get_address_to_blog_map(); foreach ( $result->installs as $install ) { $this->_site = new FS_Site( $install ); $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); $blog_id = $address_to_blog_map[ $address ]; $this->_store_site( true, $blog_id ); } } /** * Track install's custom event. * * IMPORTANT: * Custom event tracking is currently only supported for specific clients. * If you are not one of them, please don't use this method. If you will, * the API will simply ignore your request based on the plugin ID. * * Need custom tracking for your plugin or theme? * If you are interested in custom event tracking please contact yo@freemius.com * for further details. * * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @param string $name Event name. * @param array $properties Associative key/value array with primitive values only * @param bool $process_at A valid future date-time in the following format Y-m-d H:i:s. * @param bool $once If true, event will be tracked only once. IMPORTANT: Still trigger the API call. * * @return object|false Event data or FALSE on failure. * * @throws \Freemius_InvalidArgumentException */ public function track_event( $name, $properties = array(), $process_at = false, $once = false ) { $this->_logger->entrance( http_build_query( array( 'name' => $name, 'once' => $once ) ) ); if ( ! $this->is_registered() ) { return false; } $event = array( 'type' => $name ); if ( is_numeric( $process_at ) && $process_at > time() ) { $event['process_at'] = $process_at; } if ( $once ) { $event['once'] = true; } if ( ! empty( $properties ) ) { // Verify associative array values are primitive. foreach ( $properties as $k => $v ) { if ( ! is_scalar( $v ) ) { throw new Freemius_InvalidArgumentException( 'The $properties argument must be an associative key/value array with primitive values only.' ); } } $event['properties'] = $properties; } $result = $this->get_api_site_scope()->call( 'events.json', 'post', $event ); return $this->is_api_error( $result ) ? false : $result; } /** * Track install's custom event only once, but it still triggers the API call. * * IMPORTANT: * Custom event tracking is currently only supported for specific clients. * If you are not one of them, please don't use this method. If you will, * the API will simply ignore your request based on the plugin ID. * * Need custom tracking for your plugin or theme? * If you are interested in custom event tracking please contact yo@freemius.com * for further details. * * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @param string $name Event name. * @param array $properties Associative key/value array with primitive values only * @param bool $process_at A valid future date-time in the following format Y-m-d H:i:s. * * @return object|false Event data or FALSE on failure. * * @throws \Freemius_InvalidArgumentException * * @user Freemius::track_event() */ public function track_event_once( $name, $properties = array(), $process_at = false ) { return $this->track_event( $name, $properties, $process_at, true ); } /** * Plugin uninstall hook. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param bool $check_user Enforce checking if user have plugins activation privileges. */ function _uninstall_plugin_event( $check_user = true ) { $this->_logger->entrance( 'slug = ' . $this->_slug ); if ( $check_user && ! current_user_can( 'activate_plugins' ) ) { return; } $params = array(); $uninstall_reason = null; if ( isset( $this->_storage->uninstall_reason ) ) { $uninstall_reason = $this->_storage->uninstall_reason; $params['reason_id'] = $uninstall_reason->id; $params['reason_info'] = $uninstall_reason->info; } if ( ! $this->is_registered() ) { // Send anonymous uninstall event only if user submitted a feedback. if ( isset( $uninstall_reason ) ) { if ( isset( $uninstall_reason->is_anonymous ) && ! $uninstall_reason->is_anonymous ) { $this->opt_in( false, false, false, false, true ); } else { $params['uid'] = $this->get_anonymous_id(); $this->get_api_plugin_scope()->call( 'uninstall.json', 'put', $params ); } } } else { $params = array_merge( $params, array( 'is_active' => false, 'is_uninstalled' => true, ) ); if ( $this->_is_network_active ) { // Send uninstall event. $this->send_installs_update( $params ); } else { // Send uninstall event and handle the result. $this->sync_install( $params ); } } // @todo Decide if we want to delete plugin information from db. } /** * Set the basename of the current product and hook _activate_plugin_event_hook() to the activation action. * * @author Vova Feldman (@svovaf) * @since 2.2.1 * * @param string $is_premium * @param string $caller * * @return string */ function set_basename( $is_premium, $caller ) { $basename = plugin_basename( $caller ); $current_basename = $is_premium ? $this->_premium_plugin_basename : $this->_free_plugin_basename; if ( $current_basename == $basename ) { // Basename value set correctly. return; } if ( $is_premium ) { $this->_premium_plugin_basename = $basename; } else { $this->_free_plugin_basename = $basename; } $plugin_dir = dirname( $this->_plugin_dir_path ) . '/'; register_activation_hook( $plugin_dir . $basename, array( &$this, '_activate_plugin_event_hook' ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.1 * @since 2.2.1 If the context product is in its premium version, use the current module's basename, even if it was renamed. * * @return string */ function premium_plugin_basename() { if ( ! isset( $this->_premium_plugin_basename ) ) { $this->_premium_plugin_basename = $this->is_premium() ? // The product is premium, so use the current basename. $this->_plugin_basename : $this->get_premium_slug() . '/' . basename( $this->_free_plugin_basename ); } return $this->_premium_plugin_basename; } /** * Uninstall plugin hook. Called only when connected his account with Freemius for active sites tracking. * * @author Vova Feldman (@svovaf) * @since 1.0.2 */ public static function _uninstall_plugin_hook() { self::_load_required_static(); self::$_static_logger->entrance(); if ( ! current_user_can( 'activate_plugins' ) ) { return; } $plugin_file = substr( current_filter(), strlen( 'uninstall_' ) ); self::$_static_logger->info( 'plugin = ' . $plugin_file ); define( 'WP_FS__UNINSTALL_MODE', true ); $fs = self::get_instance_by_file( $plugin_file ); if ( is_object( $fs ) ) { $fs->remove_sdk_reference(); self::require_plugin_essentials(); if ( is_plugin_active( $fs->_free_plugin_basename ) || is_plugin_active( $fs->premium_plugin_basename() ) ) { // Deleting Free or Premium plugin version while the other version still installed. return; } 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' ); } } #---------------------------------------------------------------------------------- #region Plugin Information #---------------------------------------------------------------------------------- /** * Load WordPress core plugin.php essential module. * * @author Vova Feldman (@svovaf) * @since 1.1.1 */ private static function require_plugin_essentials() { if ( ! function_exists( 'get_plugins' ) ) { self::$_static_logger->log( 'Including wp-admin/includes/plugin.php...' ); require_once ABSPATH . 'wp-admin/includes/plugin.php'; } } /** * Load WordPress core pluggable.php module. * * @author Vova Feldman (@svovaf) * @since 1.1.2 */ private static function require_pluggable_essentials() { if ( ! function_exists( 'wp_get_current_user' ) ) { require_once ABSPATH . 'wp-includes/pluggable.php'; } } /** * Return plugin data. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param bool $reparse_plugin_metadata * * @return array */ function get_plugin_data( $reparse_plugin_metadata = false ) { if ( ! isset( $this->_plugin_data ) || $reparse_plugin_metadata ) { self::require_plugin_essentials(); if ( $this->is_plugin() ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.0 When using get_plugin_data() do NOT translate plugin data. * * @link https://github.com/Freemius/wordpress-sdk/issues/77 */ $plugin_data = get_plugin_data( $this->_plugin_main_file_path, false, false ); } else { $theme_data = wp_get_theme(); if ( $this->_plugin_basename !== $theme_data->get_stylesheet() && is_child_theme() ) { $parent_theme = $theme_data->parent(); if ( ( $parent_theme instanceof WP_Theme ) && $this->_plugin_basename === $parent_theme->get_stylesheet() ) { $theme_data = $parent_theme; } } $plugin_data = array( 'Name' => $theme_data->get( 'Name' ), 'Version' => $theme_data->get( 'Version' ), 'Author' => $theme_data->get( 'Author' ), 'Description' => $theme_data->get( 'Description' ), 'PluginURI' => $theme_data->get( 'ThemeURI' ), ); } $this->_plugin_data = $plugin_data; } return $this->_plugin_data; } /** * @author Vova Feldman (@svovaf) * @since 1.0.1 * @since 1.2.2.5 If slug not set load slug by module ID. * * @return string Plugin slug. */ function get_slug() { if ( ! isset( $this->_slug ) ) { $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); $this->_slug = $id_slug_type_path_map[ $this->_module_id ]['slug']; } return $this->_slug; } /** * @author Leo Fajardo (@leorw) * @since 2.2.1 * * @return string */ function get_premium_slug() { return ( is_object( $this->_plugin ) && ! empty( $this->_plugin->premium_slug ) ) ? $this->_plugin->premium_slug : "{$this->_slug}-premium"; } /** * Retrieve the desired folder name for the product. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @return string Plugin slug. */ function get_target_folder_name() { return $this->can_use_premium_code() ? $this->_plugin->premium_slug : $this->_slug; } /** * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @return number Plugin ID. */ function get_id() { return $this->_plugin->id; } /** * @author Leo Fajardo (@leorw) * @since 2.2.4 * * @return number|null Bundle ID. */ function get_bundle_id() { return ( isset( $this->_plugin->bundle_id ) && FS_Plugin::is_valid_id( $this->_plugin->bundle_id ) ) ? $this->_plugin->bundle_id : null; } /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @return string|null Bundle public key. */ function get_bundle_public_key() { return isset( $this->_plugin->bundle_public_key ) ? $this->_plugin->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 * * @return string Freemius SDK version */ function get_sdk_version() { return $this->version; } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @return number Parent plugin ID (if parent exist). */ function get_parent_id() { return $this->is_addon() ? $this->get_parent_instance()->get_id() : $this->_plugin->id; } /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @return string */ function get_usage_tracking_terms_url() { return $this->apply_filters( 'usage_tracking_terms_url', "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}/" ); } /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @return string */ function get_eula_url() { return $this->apply_filters( 'eula_url', "https://freemius.com/product/{$this->_plugin->id}/{$this->_slug}/legal/eula/" ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @return string Plugin public key. */ function get_public_key() { return $this->_plugin->public_key; } /** * Will be available only on sandbox mode. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @return mixed Plugin secret key. */ function get_secret_key() { return $this->_plugin->secret_key; } /** * @author Vova Feldman (@svovaf) * @since 1.1.1 * * @return bool */ function has_secret_key() { return ! empty( $this->_plugin->secret_key ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string|bool $premium_suffix * * @return string */ function get_plugin_name( $premium_suffix = false ) { $this->_logger->entrance(); /** * This `if-else` can be squeezed into a single `if` but I intentionally split it for code readability. * * @author Vova Feldman */ if ( ! isset( $this->_plugin_name ) ) { // Name is not yet set. $this->set_name( $premium_suffix ); } else if ( ! empty( $premium_suffix ) && ( ! is_object( $this->_plugin ) || $this->_plugin->premium_suffix !== $premium_suffix ) ) { // Name is already set, but there's a change in the premium suffix. $this->set_name( $premium_suffix ); } return $this->_plugin_name; } /** * Calculates and stores the product's name. This helper function was created specifically for get_plugin_name() just to make the code clearer. * * @author Vova Feldman (@svovaf) * @since 2.2.1 * * @param string $premium_suffix */ private function set_name( $premium_suffix = '' ) { $plugin_data = $this->get_plugin_data(); // Get name. $this->_plugin_name = $plugin_data['Name']; if ( is_string( $premium_suffix ) ) { $premium_suffix = trim( $premium_suffix ); if ( ! empty( $premium_suffix ) ) { // Check if plugin name contains " (premium)" or a custom suffix and remove it. $suffix = ( ' ' . strtolower( $premium_suffix ) ); $suffix_len = strlen( $suffix ); if ( strlen( $plugin_data['Name'] ) > $suffix_len && $suffix === substr( strtolower( $plugin_data['Name'] ), - $suffix_len ) ) { $this->_plugin_name = substr( $plugin_data['Name'], 0, - $suffix_len ); } } } $this->_logger->departure( 'Name = ' . $this->_plugin_name ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.0 * * @param bool $reparse_plugin_metadata * * @return string */ function get_plugin_version( $reparse_plugin_metadata = false ) { $this->_logger->entrance(); $plugin_data = $this->get_plugin_data( $reparse_plugin_metadata ); $this->_logger->departure( 'Version = ' . $plugin_data['Version'] ); return $this->apply_filters( 'plugin_version', $plugin_data['Version'] ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @return string */ function get_plugin_title() { $this->_logger->entrance(); $title = $this->_plugin->title; return $this->apply_filters( 'plugin_title', $title ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @param bool $lowercase * * @return string */ function get_module_label( $lowercase = false ) { $label = $this->is_addon() ? $this->get_text_inline( 'Add-On', 'addon' ) : ( $this->is_plugin() ? $this->get_text_inline( 'Plugin', 'plugin' ) : $this->get_text_inline( 'Theme', 'theme' ) ); if ( $lowercase ) { $label = strtolower( $label ); } return $label; } /** * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @return string */ function get_plugin_basename() { if ( ! isset( $this->_plugin_basename ) ) { if ( $this->is_plugin() ) { $this->_plugin_basename = plugin_basename( $this->_plugin_main_file_path ); } else { $this->_plugin_basename = basename( dirname( $this->_plugin_main_file_path ) ); } } return $this->_plugin_basename; } function get_plugin_folder_name() { $this->_logger->entrance(); $plugin_folder = $this->_plugin_basename; while ( '.' !== dirname( $plugin_folder ) ) { $plugin_folder = dirname( $plugin_folder ); } $this->_logger->departure( 'Folder Name = ' . $plugin_folder ); return $plugin_folder; } #endregion ------------------------------------------------------------------ /* Account ------------------------------------------------------------------------------------------------------------------*/ /** * Find plugin's slug by plugin's basename. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string $plugin_base_name * * @return false|string */ private static function find_slug_by_basename( $plugin_base_name ) { $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); if ( ! array( $file_slug_map ) || ! isset( $file_slug_map[ $plugin_base_name ] ) ) { return false; } return $file_slug_map[ $plugin_base_name ]; } /** * Store the map between the plugin's basename to the slug. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ private function store_file_slug_map() { $file_slug_map = self::$_accounts->get_option( 'file_slug_map', array() ); if ( ! array( $file_slug_map ) ) { $file_slug_map = array(); } if ( ! isset( $file_slug_map[ $this->_plugin_basename ] ) || $file_slug_map[ $this->_plugin_basename ] !== $this->_slug ) { $file_slug_map[ $this->_plugin_basename ] = $this->_slug; self::$_accounts->set_option( 'file_slug_map', $file_slug_map, true ); } } /** * @return array[number]FS_User */ static function get_all_users() { $users = self::maybe_get_entities_account_option( 'users', array() ); if ( ! is_array( $users ) ) { $users = array(); } return $users; } /** * @param string $module_type * @param null|int $blog_id Since 2.0.0 * * @return array[string]FS_Site */ private static function get_all_sites( $module_type = WP_FS__MODULE_TYPE_PLUGIN, $blog_id = null, $is_backup = false ) { $sites = self::get_account_option( ( $is_backup ? 'prev_' : '' ) . 'sites', $module_type, $blog_id ); if ( ! is_array( $sites ) ) { $sites = array(); } return $sites; } /** * @author Leo Fajardo (@leorw) * * @since 1.2.2 * * @param string $option_name * @param string $module_type * @param null|int $network_level_or_blog_id Since 2.0.0 * * @return mixed */ private static function get_account_option( $option_name, $module_type = null, $network_level_or_blog_id = null ) { if ( ! is_null( $module_type ) && WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) { $option_name = $module_type . '_' . $option_name; } return self::maybe_get_entities_account_option( $option_name, array(), $network_level_or_blog_id ); } /** * @author Leo Fajardo (@leorw) * * @since 1.2.2 * * @param string $option_name * @param mixed $option_value * @param bool $store * @param null|int $network_level_or_blog_id Since 2.0.0 */ private function set_account_option( $option_name, $option_value, $store, $network_level_or_blog_id = null ) { self::set_account_option_by_module( $this->_module_type, $option_name, $option_value, $store, $network_level_or_blog_id ); } /** * @author Vova Feldman (@svovaf) * * @since 1.2.2.7 * * @param string $module_type * @param string $option_name * @param mixed $option_value * @param bool $store * @param null|int $network_level_or_blog_id Since 2.0.0 */ private static function set_account_option_by_module( $module_type, $option_name, $option_value, $store, $network_level_or_blog_id = null ) { if ( WP_FS__MODULE_TYPE_PLUGIN != $module_type ) { $option_name = $module_type . '_' . $option_name; } self::$_accounts->set_option( $option_name, $option_value, $store, $network_level_or_blog_id ); } /** * This method can also return non-entity or non-entities collection option like the `user_id_license_ids_map` option. * * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @param string $option_name * @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::$_SITE_LEVEL_PARAMS). * * @return mixed|FS_Plugin[]|FS_User[]|FS_Site[]|FS_Plugin_License[]|FS_Plugin_Plan[]|FS_Plugin_Tag[] */ private static function maybe_get_entities_account_option( $option_name, $default = null, $network_level_or_blog_id = null ) { $option = self::$_accounts->get_option( $option_name, $default, $network_level_or_blog_id ); $class_name = ''; if ( fs_starts_with( $option_name, WP_FS__MODULE_TYPE_THEME . '_' ) ) { $option_name = str_replace( WP_FS__MODULE_TYPE_THEME . '_', '', $option_name ); } switch ( $option_name ) { case 'plugins': case 'themes': case 'addons': $class_name = FS_Plugin::get_class_name(); break; case 'users': $class_name = FS_User::get_class_name(); break; case 'sites': $class_name = FS_Site::get_class_name(); break; case 'licenses': case 'all_licenses': $class_name = FS_Plugin_License::get_class_name(); break; case 'plans': $class_name = FS_Plugin_Plan::get_class_name(); break; case 'updates': $class_name = FS_Plugin_Tag::get_class_name(); break; } if ( empty( $class_name ) ) { return $option; } return fs_get_entities( $option, $class_name ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param number|null $module_id * * @return FS_Plugin_License[] */ private static function get_all_licenses( $module_id = null ) { $licenses = self::get_account_option( 'all_licenses' ); if ( ! is_array( $licenses ) ) { $licenses = array(); } if ( is_null( $module_id ) ) { return $licenses; } $licenses = isset( $licenses[ $module_id ] ) ? $licenses[ $module_id ] : array(); return $licenses; } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @return array */ private static function get_all_licenses_by_module_type() { $licenses = self::get_account_option( 'all_licenses' ); $licenses_by_module_type = array( WP_FS__MODULE_TYPE_PLUGIN => array(), WP_FS__MODULE_TYPE_THEME => array() ); if ( ! is_array( $licenses ) ) { return $licenses_by_module_type; } foreach ( $licenses as $module_id => $module_licenses ) { $fs = self::get_instance_by_id( $module_id ); if ( false === $fs ) { continue; } $licenses_by_module_type[ $fs->_module_type ] = array_merge( $licenses_by_module_type[ $fs->_module_type ], $module_licenses ); } return $licenses_by_module_type; } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param number $module_id * @param number|null $user_id * * @return array */ private static function get_user_id_license_ids_map( $module_id, $user_id = null ) { $all_modules_user_id_license_ids_map = self::get_account_option( 'user_id_license_ids_map' ); if ( ! is_array( $all_modules_user_id_license_ids_map ) ) { $all_modules_user_id_license_ids_map = array(); } $user_id_license_ids_map = isset( $all_modules_user_id_license_ids_map[ $module_id ] ) ? $all_modules_user_id_license_ids_map[ $module_id ] : array(); if ( FS_User::is_valid_id( $user_id ) ) { $user_id_license_ids_map = isset( $user_id_license_ids_map[ $user_id ] ) ? $user_id_license_ids_map[ $user_id ] : array(); } return $user_id_license_ids_map; } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param array $new_user_id_license_ids_map * @param number $module_id * @param number|null $user_id */ private static function store_user_id_license_ids_map( $new_user_id_license_ids_map, $module_id, $user_id = null ) { $all_modules_user_id_license_ids_map = self::get_account_option( 'user_id_license_ids_map' ); if ( ! is_array( $all_modules_user_id_license_ids_map ) ) { $all_modules_user_id_license_ids_map = array(); } if ( ! isset( $all_modules_user_id_license_ids_map[ $module_id ] ) ) { $all_modules_user_id_license_ids_map[ $module_id ] = array(); } if ( FS_User::is_valid_id( $user_id ) ) { $all_modules_user_id_license_ids_map[ $module_id ][ $user_id ] = $new_user_id_license_ids_map; } else { $all_modules_user_id_license_ids_map[ $module_id ] = $new_user_id_license_ids_map; } self::$_accounts->set_option( 'user_id_license_ids_map', $all_modules_user_id_license_ids_map, true ); } /** * Get a collection of the user's linked license IDs. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $user_id * * @return number[] */ private function get_user_linked_license_ids( $user_id ) { return self::get_user_id_license_ids_map( $this->_module_id, $user_id ); } /** * Override the user's linked license IDs with a new IDs collection. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $user_id * @param number[] $license_ids */ private function set_user_linked_license_ids( $user_id, array $license_ids ) { self::store_user_id_license_ids_map( $license_ids, $this->_module_id, $user_id ); } /** * Link a specified license ID to a given user. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $license_id * @param number $user_id */ private function link_license_2_user( $license_id, $user_id ) { $license_ids = $this->get_user_linked_license_ids( $user_id ); if ( in_array( $license_id, $license_ids ) ) { // License already linked. return; } $license_ids[] = $license_id; $this->set_user_linked_license_ids( $user_id, $license_ids ); } /** * @param string|bool $module_type * * @return FS_Plugin_Plan[] */ private static function get_all_plans( $module_type = false ) { $plans = self::get_account_option( 'plans', $module_type ); if ( ! is_array( $plans ) ) { $plans = array(); } return $plans; } /** * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @return FS_Plugin_Tag[] */ private static function get_all_updates() { $updates = self::maybe_get_entities_account_option( 'updates', array() ); if ( ! is_array( $updates ) ) { $updates = array(); } return $updates; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return array|false */ private static function get_all_addons() { $addons = self::maybe_get_entities_account_option( 'addons', array() ); if ( ! is_array( $addons ) ) { $addons = array(); } return $addons; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return number[]|false */ private static function get_all_account_addons() { $addons = self::$_accounts->get_option( 'account_addons', array() ); if ( ! is_array( $addons ) ) { $addons = array(); } return $addons; } /** * Check if user has connected his account (opted-in). * * Note: * If the user opted-in and opted-out on a later stage, * this will still return true. If you want to check if the * user is currently opted-in, use: * `$fs->is_registered() && $fs->is_tracking_allowed()` * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param bool $ignore_anonymous_state Since 2.5.1 * * @return bool */ function is_registered( $ignore_anonymous_state = false ) { return ( is_object( $this->_user ) && ( $this->is_premium() || $ignore_anonymous_state || ! $this->is_anonymous() ) ); } /** * Returns TRUE if the user opted-in and didn't disconnect (opt-out). * * @author Leo Fajardo (@leorw) * @since 1.2.1.5 * * @return bool */ 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 ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.4.0 * * @return bool */ function is_bundle_license_auto_activation_enabled() { return $this->is_addon() ? ( is_object( $this->_parent ) && $this->_parent->is_bundle_license_auto_activation_enabled() ) : $this->_is_bundle_license_auto_activation_enabled; } /** * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @return FS_Plugin */ function get_plugin() { return $this->_plugin; } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @return FS_User */ function get_user() { return $this->_user; } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @return FS_Site */ 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. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @since 1.1.7.3 If not yet loaded, fetch data from the API. * * @param bool $flush * * @return FS_Plugin[]|false */ function get_addons( $flush = false ) { $this->_logger->entrance(); if ( ! $this->_has_addons ) { return false; } $addons = $this->sync_addons( $flush ); return ( ! is_array( $addons ) || empty( $addons ) ) ? false : $addons; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return number[]|false */ function get_account_addons() { $this->_logger->entrance(); $addons = self::get_all_account_addons(); if ( ! is_array( $addons ) || ! isset( $addons[ $this->_plugin->id ] ) || ! is_array( $addons[ $this->_plugin->id ] ) || 0 === count( $addons[ $this->_plugin->id ] ) ) { return false; } return $addons[ $this->_plugin->id ]; } /** * Check if user has any * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @return bool */ function has_account_addons() { $addons = $this->get_account_addons(); return is_array( $addons ) && ( 0 < count( $addons ) ); } /** * Get add-on by ID (from local data). * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param number $id * * @return FS_Plugin|false */ function get_addon( $id ) { $this->_logger->entrance(); $addons = $this->get_addons(); if ( is_array( $addons ) ) { foreach ( $addons as $addon ) { if ( $id == $addon->id ) { return $addon; } } } return false; } /** * Get add-on by slug (from local data). * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param string $slug * * @param bool $flush * * @return FS_Plugin|false */ function get_addon_by_slug( $slug, $flush = false ) { $this->_logger->entrance(); $addons = $this->get_addons( $flush ); if ( is_array( $addons ) ) { foreach ( $addons as $addon ) { if ( $slug === $addon->slug ) { return $addon; } } } return false; } /** * @var array { * @key number Add-on ID. * @val object[] The add-on's plans and prices object. * } */ private $plans_and_pricing_by_addon_id; /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @return array { * @key number Add-on ID. * @val object[] The add-on's plans and prices object. * } */ function _get_addons_plans_and_pricing_map_by_id() { if ( ! isset( $this->plans_and_pricing_by_addon_id ) ) { $result = $this->get_api_plugin_scope()->get( $this->add_show_pending( "/addons/pricing.json?type=visible" ) ); $plans_and_pricing_by_addon_id = array(); if ( $this->is_api_result_object( $result, 'addons' ) ) { foreach ( $result->addons as $addon ) { $plans_and_pricing_by_addon_id[ $addon->id ] = $addon->plans; } } $this->plans_and_pricing_by_addon_id = $plans_and_pricing_by_addon_id; } return $this->plans_and_pricing_by_addon_id; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param number $addon_id * @param bool $is_installed * * @return array */ function _get_addon_info( $addon_id, $is_installed ) { $addon = $this->get_addon( $addon_id ); if ( ! is_object( $addon ) ) { // Unexpected call. return array(); } $slug = $addon->slug; $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug ); if ( ! fs_is_network_admin() ) { // Get blog-level activated installations. $sites = self::maybe_get_entities_account_option( 'sites', array() ); } else { $sites = null; if ( $this->is_addon_activated( $addon_id ) && $this->get_addon_instance( $addon_id )->is_network_active() ) { if ( FS_Site::is_valid_id( $addon_storage->network_install_blog_id ) ) { // Get network-level activated installations. $sites = self::maybe_get_entities_account_option( 'sites', array(), $addon_storage->network_install_blog_id ); } } } $addon_info = array( 'is_connected' => false, 'slug' => $slug, 'title' => $addon->title, 'is_whitelabeled' => $addon_storage->is_whitelabeled ); if ( ! $is_installed ) { $plans_and_pricing_by_addon_id = $this->_get_addons_plans_and_pricing_map_by_id(); if ( isset( $plans_and_pricing_by_addon_id[ $addon_id ] ) ) { $has_paid_plan = false; $plans = $plans_and_pricing_by_addon_id[ $addon_id ]; if ( is_array( $plans ) && count( $plans ) > 0 ) { foreach ( $plans as $plan ) { if ( isset( $plan->pricing ) && is_array( $plan->pricing ) && count( $plan->pricing ) > 0 ) { $has_paid_plan = true; break; } } } $addon_info['has_paid_plan'] = $has_paid_plan; } } if ( ! is_array( $sites ) || ! isset( $sites[ $slug ] ) ) { return $addon_info; } $site = $sites[ $slug ]; $addon_info['is_connected'] = ( ( $addon->parent_plugin_id == $this->get_id() ) && is_object( $site ) && FS_Site::is_valid_id( $site->id ) && FS_User::is_valid_id( $site->user_id ) && FS_Plugin_Plan::is_valid_id( $site->plan_id ) ); if ( $addon_info['is_connected'] && $is_installed ) { return $addon_info; } $addon_info['site'] = $site; $plugins_data = self::maybe_get_entities_account_option( WP_FS__MODULE_TYPE_PLUGIN . 's', array() ); if ( isset( $plugins_data[ $slug ] ) ) { $plugin_data = $plugins_data[ $slug ]; $addon_info['version'] = $plugin_data->version; } $all_plans = self::maybe_get_entities_account_option( 'plans', array() ); if ( isset( $all_plans[ $slug ] ) ) { $plans = $all_plans[ $slug ]; foreach ( $plans as $plan ) { if ( $site->plan_id == Freemius::_decrypt( $plan->id ) ) { $addon_info['plan_name'] = Freemius::_decrypt( $plan->name ); $addon_info['plan_title'] = Freemius::_decrypt( $plan->title ); break; } } } $licenses = self::maybe_get_entities_account_option( 'all_licenses', array() ); if ( is_array( $licenses ) && isset( $licenses[ $addon_id ] ) ) { foreach ( $licenses[ $addon_id ] as $license ) { if ( $license->id == $site->license_id ) { $addon_info['license'] = $license; break; } } } if ( isset( $addon_info['license'] ) ) { if ( isset( $addon_storage->subscriptions ) && ! empty( $addon_storage->subscriptions ) ) { $addon_subscriptions = fs_get_entities( $addon_storage->subscriptions, FS_Subscription::get_class_name() ); foreach ( $addon_subscriptions as $subscription ) { if ( $subscription->license_id == $site->license_id ) { $addon_info['subscription'] = $subscription; break; } } } } return $addon_info; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $user_id * * @return FS_User */ static function _get_user_by_id( $user_id ) { self::$_static_logger->entrance( "user_id = {$user_id}" ); $users = self::get_all_users(); if ( is_array( $users ) ) { if ( isset( $users[ $user_id ] ) && $users[ $user_id ] instanceof FS_User && $user_id == $users[ $user_id ]->id ) { return $users[ $user_id ]; } // If user wasn't found by the key, iterate over all the users collection. foreach ( $users as $user ) { /** * @var FS_User $user */ if ( $user_id == $user->id ) { return $user; } } } return null; } /** * Checks if a Freemius user_id is associated with a super-admin. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $user_id * * @return bool */ private static function is_super_admin( $user_id ) { $is_super_admin = false; $user = self::_get_user_by_id( $user_id ); if ( $user instanceof FS_User && ! empty( $user->email ) ) { self::require_pluggable_essentials(); $wp_user = get_user_by( 'email', $user->email ); if ( $wp_user instanceof WP_User ) { $super_admins = get_super_admins(); $is_super_admin = ( is_array( $super_admins ) && in_array( $wp_user->user_login, $super_admins ) ); } } return $is_super_admin; } #---------------------------------------------------------------------------------- #region Plans & Licensing #---------------------------------------------------------------------------------- /** * Check if running premium plugin code. * * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @return bool */ function is_premium() { /** * `$this->_plugin` will be `false` when `is_activation_mode` calls this method directly from the * `register_constructor_hooks` method. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ return is_object( $this->_plugin ) ? $this->_plugin->is_premium : false; } /** * Get site's plan ID. * * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @return number */ function get_plan_id() { return $this->_site->plan_id; } /** * Get site's plan title. * * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @return string */ function get_plan_title() { $plan = $this->get_plan(); return is_object( $plan ) ? $plan->title : 'PLAN_TITLE'; } /** * Get site's plan name. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function get_plan_name() { $plan = $this->get_plan(); return is_object( $plan ) ? $plan->name : 'PLAN_NAME'; } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return FS_Plugin_Plan|false */ function get_plan() { if ( ! is_object( $this->_site ) ) { return false; } return FS_Plugin_Plan::is_valid_id( $this->_site->plan_id ) ? $this->_get_plan_by_id( $this->_site->plan_id ) : false; } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @return bool */ function is_trial() { $this->_logger->entrance(); if ( ! $this->is_registered( true ) || ! is_object( $this->_site ) ) { return false; } return $this->_site->is_trial(); } /** * Check if currently in a trial with payment method (credit card or paypal). * * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @return bool */ function is_paid_trial() { $this->_logger->entrance(); if ( ! $this->is_trial() ) { return false; } if ( ! $this->has_active_valid_license() ) { return false; } if ( $this->_site->trial_plan_id != $this->_license->plan_id ) { return false; } /** * @var FS_Subscription $subscription */ $subscription = $this->_get_subscription( $this->_license->id ); return ( is_object( $subscription ) && $subscription->is_active() ); } /** * Check if trial already utilized. * * @since 1.0.9 * * @return bool */ function is_trial_utilized() { $this->_logger->entrance(); if ( ! $this->is_registered() ) { return false; } return $this->_site->is_trial_utilized(); } /** * Get trial plan information (if in trial). * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool|FS_Plugin_Plan */ function get_trial_plan() { $this->_logger->entrance(); if ( ! $this->is_trial() ) { return false; } // Try to load plan from local cache. $trial_plan = $this->_get_plan_by_id( $this->_site->trial_plan_id ); if ( ! is_object( $trial_plan ) ) { $trial_plan = $this->_fetch_site_plan( $this->_site->trial_plan_id ); /** * If managed to fetch the plan, add it to the plans collection. */ if ( $trial_plan instanceof FS_Plugin_Plan ) { if ( ! is_array( $this->_plans ) ) { $this->_plans = array(); } $this->_plans[] = $trial_plan; $this->_store_plans(); } } if ( $trial_plan instanceof FS_Plugin_Plan ) { return $trial_plan; } /** * If for some reason failed to get the trial plan, fallback to a dummy name and title. */ $trial_plan = new FS_Plugin_Plan(); $trial_plan->id = $this->_site->trial_plan_id; $trial_plan->name = 'pro'; $trial_plan->title = 'Pro'; return $trial_plan; } /** * Check if the user has an activate, non-expired license on current plugin's install. * * @since 1.0.9 * * @return bool */ function is_paying() { $this->_logger->entrance(); if ( ! $this->is_registered( true ) ) { return false; } if ( ! $this->has_paid_plan() ) { return false; } return ( ! $this->is_trial() && 'free' !== $this->get_plan_name() && $this->has_active_valid_license() ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @return bool */ function is_free_plan() { if ( ! $this->is_registered() ) { return true; } if ( ! $this->has_paid_plan() ) { return true; } return ( 'free' === $this->get_plan_name() || ! $this->has_features_enabled_license() ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @return bool */ function _has_premium_license() { $this->_logger->entrance(); $premium_license = $this->_get_available_premium_license(); return ( false !== $premium_license ); } /** * Check if user has any licenses associated with the plugin (including expired or blocking). * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @param bool $including_foreign * * @return bool */ function has_any_license( $including_foreign = true ) { if ( ! is_array( $this->_licenses ) || 0 === count( $this->_licenses ) ) { return false; } if ( $including_foreign ) { return true; } foreach ( $this->_licenses as $license ) { if ( $this->_user->id == $license->user_id ) { return true; } } return false; } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param bool|null $is_localhost * * @return FS_Plugin_License|false */ function _get_available_premium_license( $is_localhost = null ) { $this->_logger->entrance(); $licenses = $this->get_available_premium_licenses( $is_localhost ); if ( ! empty( $licenses ) ) { return $licenses[0]; } return false; } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param bool|null $is_localhost * * @return FS_Plugin_License[] */ function get_available_premium_licenses( $is_localhost = null ) { $this->_logger->entrance(); $licenses = array(); if ( ! $this->has_paid_plan() ) { return $licenses; } if ( is_array( $this->_licenses ) ) { foreach ( $this->_licenses as $license ) { if ( ! $license->can_activate( $is_localhost ) ) { continue; } $licenses[] = $license; } } return $licenses; } /** * Sync local plugin plans with remote server. * * IMPORTANT: If for some reason a site is associated with deleted plan, we'll preserve the plan's information and append it as the last plan. This means that if plan is deleted, the is_plan() method will ALWAYS return true for any given argument (it becomes the most inclusive plan). * * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @return FS_Plugin_Plan[]|object */ function _sync_plans() { $plans = $this->_fetch_plugin_plans(); if ( $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) { $plans_map = array(); foreach ( $plans as $plan ) { $plans_map[ $plan->id ] = true; } $plans_ids_to_keep = $this->get_plans_ids_associated_with_installs(); foreach ( $plans_ids_to_keep as $plan_id ) { if ( isset( $plans_map[ $plan_id ] ) ) { continue; } $missing_plan = self::_get_plan_by_id( $plan_id ); if ( is_object( $missing_plan ) ) { $plans[] = $missing_plan; } } $this->_plans = $plans; $this->_store_plans(); } $this->do_action( 'after_plans_sync', $plans ); return $this->_plans; } /** * Check if specified plan exists locally. If not, fetch it and store it. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $plan_id * * @return \FS_Plugin_Plan|object The plan entity or the API error object on failure. */ private function sync_plan_if_not_exist( $plan_id ) { $plan = self::_get_plan_by_id( $plan_id ); if ( is_object( $plan ) ) { // Plan already exists. return $plan; } $plan = $this->fetch_plan_by_id( $plan_id ); if ( $plan instanceof FS_Plugin_Plan ) { $this->_plans[] = $plan; $this->_store_plans(); return $plan; } return $plan; } /** * Check if specified license exists locally. If not, fetch it and store it. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $license_id * @param string $license_key * * @return \FS_Plugin_Plan|object The plan entity or the API error object on failure. */ private function sync_license_if_not_exist( $license_id, $license_key ) { $license = $this->_get_license_by_id( $license_id ); if ( is_object( $license ) ) { // License already exists. return $license; } $license = $this->fetch_license_by_key( $license_id, $license_key ); if ( $license instanceof FS_Plugin_License ) { $this->_licenses[] = $license; $this->set_license( $license ); $this->_store_licenses(); return $license; } return $license; } /** * Get a collection of unique plan IDs that are associated with any installs in the network. * * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @return number[] */ private function get_plans_ids_associated_with_installs() { if ( ! is_multisite() ) { if ( ! is_object( $this->_site ) || ! FS_Plugin_Plan::is_valid_id( $this->_site->plan_id ) ) { return array(); } return array( $this->_site->plan_id ); } $plan_ids = array(); $sites = self::get_sites(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); $install = $this->get_install_by_blog_id( $blog_id ); if ( ! is_object( $install ) || ! FS_Plugin_Plan::is_valid_id( $install->plan_id ) ) { continue; } $plan_ids[ $install->plan_id ] = true; } return array_keys( $plan_ids ); } /** * Get a collection of unique license IDs that are associated with any installs in the network. * * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @return number[] */ private function get_license_ids_associated_with_installs() { if ( ! $this->_is_network_active ) { if ( ! is_object( $this->_site ) || ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) ) { return array(); } return array( $this->_site->license_id ); } $license_ids = array(); $sites = self::get_sites(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); $install = $this->get_install_by_blog_id( $blog_id ); if ( ! is_object( $install ) || ! FS_Plugin_License::is_valid_id( $install->license_id ) ) { continue; } $license_ids[ $install->license_id ] = true; } return array_keys( $license_ids ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param number $id * * @return FS_Plugin_Plan|false */ function _get_plan_by_id( $id ) { $this->_logger->entrance(); if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) { $this->_sync_plans(); } foreach ( $this->_plans as $plan ) { if ( $id == $plan->id ) { return $plan; } } return false; } /** * @author Vova Feldman (@svovaf) * @since 1.1.8.1 * * @param string $name * * @return FS_Plugin_Plan|false */ private function get_plan_by_name( $name ) { $this->_logger->entrance(); if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) { $this->_sync_plans(); } foreach ( $this->_plans as $plan ) { if ( $name == $plan->name ) { return $plan; } } return false; } /** * Sync local licenses with remote server. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param number|bool $site_license_id * @param number|null $blog_id * * @return FS_Plugin_License[]|object */ function _sync_licenses( $site_license_id = false, $blog_id = null ) { $this->_logger->entrance(); $is_network_admin = fs_is_network_admin(); if ( $is_network_admin && is_null( $blog_id ) ) { $all_licenses = self::get_all_licenses( $this->_module_id ); } else { $all_licenses = $this->get_user_licenses( $this->_user->id ); } $foreign_licenses = $this->get_foreign_licenses_info( $all_licenses, $site_license_id ); $all_licenses_map = array(); foreach ( $all_licenses as $license ) { $all_licenses_map[ $license->id ] = true; } $licenses = $this->_fetch_licenses( false, $site_license_id, $foreign_licenses, $blog_id ); if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) { $licenses_map = array(); foreach ( $licenses as $license ) { $licenses_map[ $license->id ] = true; } // $license_ids_to_keep = $this->get_license_ids_associated_with_installs(); // foreach ( $license_ids_to_keep as $license_id ) { // if ( isset( $licenses_map[ $license_id ] ) ) { // continue; // } // // $missing_license = self::_get_license_by_id( $license_id, false ); // if ( is_object( $missing_license ) ) { // $licenses[] = $missing_license; // $licenses_map[ $missing_license->id ] = true; // } // } $user_license_ids = $this->get_user_linked_license_ids( $this->_user->id ); foreach ( $user_license_ids as $key => $license_id ) { if ( ! isset( $licenses_map[ $license_id ] ) ) { // Remove access to licenses that no longer exist. unset( $user_license_ids[ $key ] ); } } if ( ! empty( $user_license_ids ) ) { foreach ( $licenses_map as $license_id => $value ) { if ( ! isset( $all_licenses_map[ $license_id ] ) ) { // Associate new licenses with the user who triggered the license syncing. $user_license_ids[] = $license_id; } } $user_license_ids = array_unique( $user_license_ids ); } else { $user_license_ids = array_keys( $licenses_map ); } if ( ! $is_network_admin || ! is_null( $blog_id ) ) { $user_licenses = array(); foreach ( $licenses as $license ) { if ( ! in_array( $license->id, $user_license_ids ) ) { continue; } $user_licenses[] = $license; } $this->_licenses = $user_licenses; } else { $this->_licenses = $licenses; } $this->set_user_linked_license_ids( $this->_user->id, $user_license_ids ); $this->_store_licenses( true, $this->_module_id, $licenses ); } // Update current license. if ( is_object( $this->_license ) ) { $license = $this->_get_license_by_id( $this->_license->id ); if ( is_object( $license ) ) { /** * `$license` can be `false` in case a user change action has just been completed and this method * has synced the `$this->_licenses` collection for the new user. In this case, the * `$this->_licenses` collection may have only the newly activated license that is associated with * the new user. `set_license` will eventually be called in the same request by the logic that * follows outside this method which will detect that the install's license has been updated, and * then `_update_site_license` will be called which in turn will call `set_license`. * * @author Leo Fajardo (@leorw) * @since 2.3.2 */ $this->set_license( $license ); } } return $this->_licenses; } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param number $id * @param bool $sync_licenses * * @return FS_Plugin_License|false */ function _get_license_by_id( $id, $sync_licenses = true ) { $this->_logger->entrance(); if ( ! FS_Plugin_License::is_valid_id( $id ) ) { return false; } /** * When running from the network level admin and opted-in from the network, * check if the license exists in the network user licenses collection. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ if ( fs_is_network_admin() && $this->is_network_registered() && ( ! is_object( $this->_user ) || $this->_storage->network_user_id != $this->_user->id ) ) { $licenses = $this->get_user_licenses( $this->_storage->network_user_id ); foreach ( $licenses as $license ) { if ( $id == $license->id ) { return $license; } } } if ( ! $this->has_any_license() && $sync_licenses ) { $this->_sync_licenses( $id ); } if ( is_array( $this->_licenses ) ) { foreach ( $this->_licenses as $license ) { if ( $id == $license->id ) { return $license; } } } return false; } /** * Get license by ID. Unlike _get_license_by_id(), this method only checks the local storage and return any license, whether it's associated with the current context user/install or not. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $id * * @return FS_Plugin_License */ private function get_license_by_id( $id ) { $licenses = self::get_all_licenses( $this->_module_id ); if ( is_array( $licenses ) && ! empty( $licenses ) ) { foreach ( $licenses as $license ) { if ( $id == $license->id ) { return $license; } } } return null; } /** * Synchronize the site's context license by fetching the license form the API and updating the local data with it. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return \FS_Plugin_License|mixed */ private function sync_site_license() { $api = $this->get_api_user_scope(); $result = $api->get( "/licenses/{$this->_license->id}.json?license_key=" . urlencode( $this->_license->secret_key ), true ); if ( ! $this->is_api_result_entity( $result ) ) { return $result; } $license = $this->_update_site_license( new FS_Plugin_License( $result ) ); $this->_store_licenses(); return $license; } /** * Get all user's available licenses for the current module. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $user_id * * @return FS_Plugin_License[] */ private function get_user_licenses( $user_id ) { $all_licenses = self::get_all_licenses( $this->_module_id ); if ( empty( $all_licenses ) ) { return array(); } $user_license_ids = $this->get_user_linked_license_ids( $user_id ); if ( empty( $user_license_ids ) ) { return array(); } $licenses = array(); foreach ( $all_licenses as $license ) { if ( in_array( $license->id, $user_license_ids ) ) { $licenses[] = $license; } } return $licenses; } /** * Checks if the context license is network activated except on the given blog ID. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $except_blog_id * * @return bool */ private function is_license_network_active( $except_blog_id = 0 ) { $this->_logger->entrance(); if ( ! is_object( $this->_license ) ) { return false; } $sites = self::get_sites(); if ( $this->_license->total_activations() < ( count( $sites ) - 1 ) ) { // There are more sites than the number of activations, so license cannot be network activated. return false; } foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); if ( $except_blog_id == $blog_id ) { // Skip excluded blog. continue; } $install = $this->get_install_by_blog_id( $blog_id ); if ( is_object( $install ) && $install->license_id != $this->_license->id ) { return false; } } return true; } /** * Checks if license can be activated on all the network sites (opted-in or skipped) that are not yet associated with a license. If possible, try to make the activation, if not return false. * * Notice: On success, this method will also update the license activations counters (without updating the license in the storage). * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_User $user * @param \FS_Plugin_License $license * * @return bool */ private function try_activate_license_on_network( FS_User $user, FS_Plugin_License $license ) { $this->_logger->entrance(); $result = $this->can_activate_license_on_network( $license ); if ( false === $result ) { return false; } $installs_without_license = $result['installs']; if ( ! empty( $installs_without_license ) ) { $this->activate_license_on_many_installs( $user, $license->secret_key, $installs_without_license ); } $disconnected_site_ids = $result['sites']; if ( ! empty( $disconnected_site_ids ) ) { $this->activate_license_on_many_sites( $user, $license->secret_key, $disconnected_site_ids ); } $this->link_license_2_user( $license->id, $user->id ); // Sync license after activations. $license->activated += $result['production_count']; $license->activated_local += $result['localhost_count']; // $this->_store_licenses() return true; } /** * Checks if the given license can be activated on the whole network. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_Plugin_License $license * * @return false|array { * @type array[int]FS_Site $installs Blog ID to install map. * @type int[] $sites Non-connected blog IDs. * @type int $production_count Production sites count. * @type int $localhost_count Production sites count. * } */ private function can_activate_license_on_network( FS_Plugin_License $license ) { $sites = self::get_sites(); $production_count = 0; $localhost_count = 0; $installs_without_license = array(); $disconnected_site_ids = array(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); $install = $this->get_install_by_blog_id( $blog_id ); if ( is_object( $install ) ) { if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { // License already activated on the install. continue; } $url = $install->url; $installs_without_license[ $blog_id ] = $install; } else { $url = is_object( $site ) ? $site->siteurl : self::get_unfiltered_site_url( $blog_id ); $disconnected_site_ids[] = $blog_id; } if ( FS_Site::is_localhost_by_address( $url ) ) { $localhost_count ++; } else { $production_count ++; } } if ( ! $license->can_activate_bulk( $production_count, $localhost_count ) ) { return false; } return array( 'installs' => $installs_without_license, 'sites' => $disconnected_site_ids, 'production_count' => $production_count, 'localhost_count' => $localhost_count, ); } /** * Activate a given license on a collection of installs. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_User $user * @param string $license_key * @param array $blog_2_install_map { * @key int Blog ID. * @value FS_Site Blog's associated install. * } * * @return mixed|true */ private function activate_license_on_many_installs( FS_User $user, $license_key, array $blog_2_install_map ) { $params = array( array( 'license_key' => $this->apply_filters( 'license_key', $license_key ) ) ); $install_2_blog_map = array(); foreach ( $blog_2_install_map as $blog_id => $install ) { $params[] = array( 'id' => $install->id ); $install_2_blog_map[ $install->id ] = $blog_id; } $result = $this->get_api_user_scope_by_user( $user )->call( "plugins/{$this->_plugin->id}/installs.json", 'PUT', $params ); if ( ! $this->is_api_result_object( $result, 'installs' ) ) { return $result; } foreach ( $result->installs as $r_install ) { $install = new FS_Site( $r_install ); $install->is_disconnected = false; // Update install. $this->_store_site( true, $install_2_blog_map[ $r_install->id ], $install ); } return true; } /** * Activate a given license on a collection of blogs/sites that are not yet opted-in. * * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @param \FS_User $user * @param string $license_key * * @return true|mixed True if successful, otherwise, the API result. */ private function activate_license_on_site( FS_User $user, $license_key ) { return $this->activate_license_on_many_sites( $user, $license_key ); } /** * Activate a given license on a collection of blogs/sites that are not yet opted-in. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_User $user * @param string $license_key * @param int[] $site_ids * * @return true|mixed True if successful, otherwise, the API result. */ private function activate_license_on_many_sites( FS_User $user, $license_key, array $site_ids = array() ) { $sites = array(); foreach ( $site_ids as $site_id ) { $sites[] = $this->get_site_info( array( 'blog_id' => $site_id ) ); } // Install the plugin. $result = $this->create_installs_with_user( $user, $license_key, false, $sites, false, true ); if ( ! $this->is_api_result_entity( $result ) && ! $this->is_api_result_object( $result, 'installs' ) ) { return $result; } $installs = array(); if ( $this->is_api_result_entity( $result ) ) { $install = new FS_Site( $result ); $this->_user = $user; $this->_store_site( true, null, $install ); $this->_site = $install; $this->reset_anonymous_mode(); } else { foreach ( $result->installs as $install ) { $installs[] = new FS_Site( $install ); } // Map site addresses to their blog IDs. $address_to_blog_map = $this->get_address_to_blog_map(); $first_blog_id = null; foreach ( $installs as $install ) { $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); $blog_id = $address_to_blog_map[ $address ]; $this->_store_site( true, $blog_id, $install ); $this->reset_anonymous_mode( $blog_id ); if ( is_null( $first_blog_id ) ) { $first_blog_id = $blog_id; } } if ( ! FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ) { $this->_storage->network_install_blog_id = $first_blog_id; } } return true; } /** * Sync site's license with user licenses. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param FS_Plugin_License|null $new_license * * @return FS_Plugin_License|null */ function _update_site_license( $new_license ) { $this->_logger->entrance(); /** * In case this call will be removed in the future, the `_sync_licenses()` method needs to be updated * accordingly so that it will also handle the case when an ownership change is done via license * activation. * * @author Leo Fajardo (@leorw) * @since 2.3.2 */ $this->set_license( $new_license ); if ( ! is_object( $new_license ) ) { $this->_site->license_id = null; $this->_sync_site_subscription( null ); return $this->_license; } $this->_site->license_id = $this->_license->id; if ( ! is_array( $this->_licenses ) ) { $this->_licenses = array(); } $is_license_found = false; for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) { if ( $new_license->id == $this->_licenses[ $i ]->id ) { $this->_licenses[ $i ] = $new_license; $is_license_found = true; break; } } // If new license just append. if ( ! $is_license_found ) { $this->_licenses[] = $new_license; } $this->_sync_site_subscription( $new_license ); return $this->_license; } /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @param \FS_Plugin_License $license */ private function set_license( FS_Plugin_License $license = null ) { $this->_license = $license; $this->maybe_update_whitelabel_flag( $license ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @param FS_Plugin_License $license */ private function maybe_update_whitelabel_flag( $license ) { $is_whitelabeled = isset( $this->_storage->is_whitelabeled ) ? $this->_storage->is_whitelabeled : false; if ( is_object( $license ) ) { $license_user = self::_get_user_by_id( $license->user_id ); if ( ! is_object( $license_user ) ) { // If foreign license, do not update the `is_whitelabeled` flag. return; } if ( $this->is_addon() ) { /** * Store the last license data to the parent's storage since it's needed only when showing the * "Start Debug" dialog which is triggered from the "Account" page. This way, there's no need to * iterate over the add-ons just to get the last license data. */ $this->get_parent_instance()->store_last_activated_license_data( $license, $license_user ); } else { $this->store_last_activated_license_data( $license ); } if ( $license->is_whitelabeled ) { // Activated a developer license, data should be hidden. $is_whitelabeled = true; } else if ( $this->is_registered() && $this->_user->id == $license->user_id ) { // The account owner activated a regular license key, no need to hide the data. $is_whitelabeled = false; } } $this->_storage->is_whitelabeled = $is_whitelabeled; // Reset the whitelabeled status after update. $this->is_whitelabeled = null; if ( $this->is_addon() ) { $parent_fs = $this->get_parent_instance(); if ( is_object( $parent_fs ) ) { $parent_fs->is_whitelabeled = null; } } } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @param FS_Plugin_License $license * @param FS_User $license_user */ private function store_last_activated_license_data( FS_Plugin_License $license, $license_user = null ) { if ( ! is_object( $license_user ) ) { $this->_storage->last_license_key = md5( $license->secret_key ); $this->_storage->last_license_user_id = null; } else { $this->_storage->last_license_user_key = md5( $license_user->secret_key ); $this->_storage->last_license_user_id = $license_user->id; } } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @param bool $ignore_data_debug_mode * * @return bool */ function is_whitelabeled_by_flag( $ignore_data_debug_mode = false ) { if ( true !== $this->_storage->is_whitelabeled ) { return false; } else if ( $ignore_data_debug_mode ) { return true; } $fs = $this->is_addon() ? $this->get_parent_instance() : $this; return ! $fs->is_data_debug_mode(); } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @return number */ function get_last_license_user_id() { return ( FS_User::is_valid_id( $this->_storage->last_license_user_id ) ) ? $this->_storage->last_license_user_id : null; } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @param int $blog_id * @param bool $ignore_data_debug_mode * * @return bool */ function is_whitelabeled( $ignore_data_debug_mode = false, $blog_id = null ) { if ( ! is_null( $blog_id ) ) { $this->switch_to_blog( $blog_id ); } if ( ! is_null( $this->is_whitelabeled ) ) { $is_whitelabeled = $this->is_whitelabeled; } else { $is_whitelabeled = false; $is_whitelabeled_flag = $this->is_whitelabeled_by_flag( true ); if ( ! $this->has_addons() ) { $is_whitelabeled = $is_whitelabeled_flag; } else if ( $is_whitelabeled_flag ) { $is_whitelabeled = true; } else { 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(); } if ( ! empty( $addon_ids ) ) { $addon_ids = array_unique( $addon_ids ); $is_network_level = ( fs_is_network_admin() && $this->is_network_active() ); foreach ( $addon_ids as $addon_id ) { $addon = $this->get_addon( $addon_id ); if ( ! is_object( $addon ) ) { continue; } $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $addon->slug ); $fs_addon = $this->is_addon_activated( $addon_id ) ? self::get_addon_instance( $addon_id ) : null; $was_addon_network_activated = false; if ( is_object( $fs_addon ) ) { $was_addon_network_activated = $fs_addon->is_network_active(); } else if ( $is_network_level ) { $was_addon_network_activated = $addon_storage->get( 'was_plugin_loaded', false, true ); } $network_delegated_connection = ( $was_addon_network_activated && $addon_storage->get( 'is_delegated_connection', false, true ) ); if ( $is_network_level && ( ! $was_addon_network_activated || $network_delegated_connection ) ) { $sites = self::get_sites(); /** * If in network admin area and the add-on was not network-activated or network-activated * and network-delegated, find any add-on whose is_whitelabeled flag is true. */ foreach ( $sites as $site ) { $site_info = $this->get_site_info( $site ); if ( $addon_storage->get( 'is_whitelabeled', false, $site_info['blog_id'] ) ) { $is_whitelabeled = true; break; } } if ( $is_whitelabeled ) { break; } } else { /** * This will be executed when any of the following is met: * 1. Add-on was network-activated, not network-delegated, and in network admin area. * 2. Add-on was network-activated, network-delegated, and in site admin area. * 3. Add-on was not network-activated and in site admin area. */ if ( true === $addon_storage->is_whitelabeled ) { $is_whitelabeled = true; break; } } } } } $this->is_whitelabeled = $is_whitelabeled; if ( ! $is_whitelabeled || ! $this->is_data_debug_mode() ) { $this->_admin_notices->remove_sticky( 'data_debug_mode_enabled' ); } if ( ! is_null( $blog_id ) ) { $this->restore_current_blog(); } } return ( $is_whitelabeled && ( $ignore_data_debug_mode || ! $this->is_data_debug_mode() ) ); } /** * Sync site's subscription. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param FS_Plugin_License|null $license * * @return bool|\FS_Subscription */ private function _sync_site_subscription( $license ) { if ( ! is_object( $license ) ) { $this->delete_unused_subscriptions(); return false; } // Load subscription details if not lifetime. $subscription = $license->is_lifetime() ? false : $this->_fetch_site_license_subscription(); if ( is_object( $subscription ) && ! isset( $subscription->error ) ) { $this->store_subscription( $subscription ); } else { $this->delete_unused_subscriptions(); } return $subscription; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool|\FS_Plugin_License */ function _get_license() { if ( ! fs_is_network_admin() || is_object( $this->_license ) ) { return $this->_license; } return $this->_get_available_premium_license(); } /** * @param number $license_id * * @return null|\FS_Subscription */ function _get_subscription( $license_id ) { if ( ! isset( $this->_storage->subscriptions ) || empty( $this->_storage->subscriptions ) ) { return null; } foreach ( fs_get_entities( $this->_storage->subscriptions, FS_Subscription::get_class_name() ) as $subscription ) { if ( $subscription->license_id == $license_id ) { return $subscription; } } return null; } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param FS_Subscription $subscription */ function store_subscription( FS_Subscription $subscription ) { if ( ! isset( $this->_storage->subscriptions ) ) { $this->_storage->subscriptions = array(); } if ( empty( $this->_storage->subscriptions ) || ! is_multisite() ) { $this->_storage->subscriptions = array( $subscription ); return; } $subscriptions = fs_get_entities( $this->_storage->subscriptions, FS_Subscription::get_class_name() ); $updated_subscription = false; foreach ( $subscriptions as $key => $existing_subscription ) { if ( $existing_subscription->id == $subscription->id ) { $subscriptions[ $key ] = $subscription; $updated_subscription = true; break; } } if ( ! $updated_subscription ) { $subscriptions[] = $subscription; } $this->_storage->subscriptions = $subscriptions; } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 */ function delete_unused_subscriptions() { if ( ! isset( $this->_storage->subscriptions ) || empty( $this->_storage->subscriptions ) || // Clean up only if there are already at least 3 subscriptions. ( count( $this->_storage->subscriptions ) < 3 ) ) { return; } if ( ! is_multisite() ) { // If not multisite, there should only be 1 subscription, so just clear the array. $this->_storage->subscriptions = array(); return; } $subscriptions_to_keep_by_license_id_map = array(); $sites = self::get_sites(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); $install = $this->get_install_by_blog_id( $blog_id ); if ( ! is_object( $install ) || ! FS_Plugin_License::is_valid_id( $install->license_id ) ) { continue; } $subscriptions_to_keep_by_license_id_map[ $install->license_id ] = true; } if ( empty( $subscriptions_to_keep_by_license_id_map ) ) { $this->_storage->subscriptions = array(); return; } foreach ( $this->_storage->subscriptions as $key => $subscription ) { if ( ! isset( $subscriptions_to_keep_by_license_id_map[ $subscription->license_id ] ) ) { unset( $this->_storage->subscriptions[ $key ] ); } } } /** * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @param string $plan Plan name * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ function is_plan( $plan, $exact = false ) { $this->_logger->entrance(); if ( ! $this->is_registered() ) { return false; } $plan = strtolower( $plan ); $current_plan_name = $this->get_plan_name(); if ( $current_plan_name === $plan ) { // Exact plan. return true; } else if ( $exact ) { // Required exact, but plans are different. return false; } $current_plan_order = - 1; $required_plan_order = PHP_INT_MAX; for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { if ( $plan === $this->_plans[ $i ]->name ) { $required_plan_order = $i; } else if ( $current_plan_name === $this->_plans[ $i ]->name ) { $current_plan_order = $i; } } return ( $current_plan_order > $required_plan_order ); } /** * Check if module has only one plan. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param bool $double_check In some cases developers prefer to release their paid offering as premium-only, even though there is a free version. For those cases, looking at the 'is_premium_only' value isn't enough because the result will return false even when the product has only signle paid plan. * * @return bool */ function is_single_plan( $double_check = false ) { $this->_logger->entrance(); if ( ! $this->is_registered() || ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) { return true; } $has_free_plan = $this->has_free_plan(); if ( ! $has_free_plan && $double_check ) { foreach ( $this->_plans as $plan ) { if ( $plan->is_free() ) { $has_free_plan = true; break; } } } return ( 1 === ( count( $this->_plans ) - ( $has_free_plan ? 1 : 0 ) ) ); } /** * Check if plan based on trial. If not in trial mode, should return false. * * @since 1.0.9 * * @param string $plan Plan name * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ function is_trial_plan( $plan, $exact = false ) { $this->_logger->entrance(); if ( ! $this->is_registered() ) { return false; } if ( ! $this->is_trial() ) { return false; } $trial_plan = $this->get_trial_plan(); if ( $trial_plan->name === $plan ) { // Exact plan. return true; } else if ( $exact ) { // Required exact, but plans are different. return false; } $current_plan_order = - 1; $required_plan_order = - 1; for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { if ( $plan === $this->_plans[ $i ]->name ) { $required_plan_order = $i; } else if ( $trial_plan->name === $this->_plans[ $i ]->name ) { $current_plan_order = $i; } } return ( $current_plan_order > $required_plan_order ); } /** * Check if plugin has any paid plans. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool */ function has_paid_plan() { return $this->_has_paid_plans || FS_Plan_Manager::instance()->has_paid_plan( $this->_plans ); } /** * Check if plugin has any plan with a trail. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function has_trial_plan() { /** * @author Vova Feldman(@svovaf) * @since 1.2.1.5 * * Allow setting a trial from the SDK without calling the API. * But, if the user did opt-in, continue using the real data from the API. */ if ( $this->_trial_days >= 0 ) { return true; } return $this->_storage->get( 'has_trial_plan', false ); } /** * Check if plugin has any free plan, or is it premium only. * * Note: If no plans configured, assume plugin is free. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool */ function has_free_plan() { return ! $this->is_only_premium(); } /** * Displays a license activation dialog box when the user clicks on the "Activate License" * or "Change License" link on the plugins * page. * * @author Leo Fajardo (@leorw) * @since 1.1.9 */ function _add_license_activation_dialog_box() { $vars = array( 'id' => $this->_module_id, ); fs_require_template( 'forms/license-activation.php', $vars ); 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. * * @author Leo Fajardo (@leorw) * @since 2.3.2 * * @return number[] */ function get_installs_ids_with_foreign_licenses() { $installs = array(); if ( is_object( $this->_license ) && $this->_site->user_id != $this->_license->user_id ) { $installs[] = $this->_site->id; } /** * Also try to get foreign licenses for the context product's add-ons. */ $installs_by_slug_map = $this->get_parent_and_addons_installs_info(); foreach ( $installs_by_slug_map as $slug => $install_info ) { if ( $slug == $this->get_slug() ) { continue; } $install = $install_info['install']; $license = $install_info['license']; if ( is_object( $license ) && $install->user_id != $license->user_id ) { $installs[] = $install->id; } } return $installs; } /** * Displays the "Change User" dialog box when the user clicks on the "Change User" button on the "Account" page. * * @author Leo Fajardo (@leorw) * @since 2.3.2 * * @param number[] $install_ids */ function _add_user_change_dialog_box( $install_ids ) { $vars = array( 'id' => $this->_module_id, 'license_owners' => $this->fetch_installs_licenses_owners_data( $install_ids ) ); fs_require_template( 'forms/user-change.php', $vars ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 */ function _add_data_debug_mode_dialog_box() { $vars = array( 'id' => $this->_module_id, ); fs_require_template( 'forms/data-debug-mode.php', $vars ); } /** * Displays a subscription cancellation dialog box when the user clicks on the "Deactivate License" * link on the "Account" page or deactivates a plugin and there's an active subscription that is * either associated with a non-lifetime single-site license or non-lifetime multisite license that * is only activated on a single production site. * * @author Leo Fajardo (@leorw) * @since 2.2.1 * * @param bool $is_license_deactivation * * @return array */ function _get_subscription_cancellation_dialog_box_template_params( $is_license_deactivation = false ) { if ( fs_is_network_admin() ) { // Subscription cancellation dialog box is currently not supported for multisite networks. return array(); } if ( $this->is_whitelabeled() ) { return array(); } $license = $this->_get_license(); /** * If the installation is associated with a non-lifetime license, which is either a single-site or only activated on a single production site (or zero), and connected to an active subscription, suggest the customer to cancel the subscription upon deactivation. * * @author Leo Fajardo (@leorw) (Comment added by Vova Feldman @svovaf) * @since 2.2.1 */ if ( ! is_object( $license ) || $license->is_lifetime() || ( ! $license->is_single_site() && $license->activated > 1 ) ) { return array(); } /** * @var FS_Subscription $subscription */ $subscription = $this->_get_subscription( $license->id ); if ( ! is_object( $subscription ) || ! $subscription->is_active() ) { return array(); } return array( 'id' => $this->_module_id, 'license' => $license, 'has_trial' => $this->is_paid_trial(), 'is_license_deactivation' => $is_license_deactivation, ); } /** * @author Leo Fajardo (@leorw) * @since 2.0.2 */ function _add_premium_version_upgrade_selection_dialog_box() { $modules_update = get_site_transient( $this->is_theme() ? 'update_themes' : 'update_plugins' ); if ( ! isset( $modules_update->response[ $this->_plugin_basename ] ) ) { return; } $vars = array( 'id' => $this->_module_id, 'new_version' => is_object( $modules_update->response[ $this->_plugin_basename ] ) ? $modules_update->response[ $this->_plugin_basename ]->new_version : $modules_update->response[ $this->_plugin_basename ]['new_version'] ); fs_require_template( 'forms/premium-versions-upgrade-metadata.php', $vars ); fs_require_once_template( 'forms/premium-versions-upgrade-handler.php', $vars ); } /** * Displays the opt-out dialog box when the user clicks on the "Opt Out" link on the "Plugins" * page. * * @author Leo Fajardo (@leorw) * @since 1.2.1.5 */ function _add_optout_dialog() { if ( $this->is_theme() ) { $vars = null; fs_require_once_template( '/js/jquery.content-change.php', $vars ); } $vars = array( 'id' => $this->_module_id ); fs_require_template( 'forms/optout.php', $vars ); } /** * Prepare page to include all required UI and logic for the license activation dialog. * * @author Vova Feldman (@svovaf) * @since 1.2.0 */ function _add_license_activation() { if ( $this->is_migration() ) { return; } if ( ! $this->is_user_admin() ) { // Only admins can activate a license. return; } if ( ! $this->has_paid_plan() ) { // Module doesn't have any paid plans. return; } if ( $this->has_premium_version() && ! $this->is_premium() && /** * Also handle the case when an upgrade was made using the free version. * * @author Leo Fajardo (@leorw) * @since 2.3.2 */ ! is_object( $this->_get_license() ) ) { // Only add license activation logic to the premium version, or in case of a serviceware plugin, also in the free version. return; } // Add license activation link and AJAX request handler. if ( self::is_plugins_page() ) { $is_network_admin = fs_is_network_admin(); if ( ( $is_network_admin && $this->is_network_active() && ! $this->is_network_delegated_connection() ) || ( ! $is_network_admin && ( ! $this->is_network_active() || $this->is_delegated_connection() ) ) ) { 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(); } } } // Add license activation AJAX callback. $this->add_ajax_action( 'activate_license', array( &$this, '_activate_license_ajax_action' ) ); // Add resend license AJAX callback. $this->add_ajax_action( 'resend_license_key', array( &$this, '_resend_license_key_ajax_action' ) ); } /** * Prepares page to include all required UI and logic for the "Change User" dialog. * * @author Leo Fajardo (@leorw) * @since 2.3.2 */ function _add_user_change_option() { if ( ! $this->should_handle_user_change() ) { return; } $installs_ids_with_foreign_licenses = $this->get_installs_ids_with_foreign_licenses(); if ( empty( $installs_ids_with_foreign_licenses ) ) { // Handle user change only when the parent product or one of its add-ons is activated with a foreign license. return; } // Add user change AJAX handler. $this->add_ajax_action( 'change_user', array( &$this, '_user_change_ajax_action' ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.2 */ function should_handle_user_change() { if ( ! $this->is_user_admin() ) { // Only admins can change user. return false; } if ( $this->is_addon() ) { return false; } if ( ! $this->is_registered() ) { return false; } if ( $this->is_network_active() && ( fs_is_network_admin() || ! $this->is_site_delegated_connection() ) ) { // Handle only on site-level "Account" section for now. return false; } return true; } /** * @author Leo Fajardo (@leorw) * @since 2.0.2 */ function _add_premium_version_upgrade_selection() { if ( ! $this->is_user_admin() ) { return; } if ( ! $this->is_premium() || $this->has_any_active_valid_license() ) { // This is relevant only to the free versions and premium versions without an active license. return; } if ( self::is_updates_page() || ( $this->is_plugin() && self::is_plugins_page() ) ) { $this->_add_premium_version_upgrade_selection_action(); } } /** * @author Edgar Melkonyan * @since 2.4.1 * * @throws Freemius_Exception */ function _toggle_whitelabel_mode_ajax_handler() { $this->_logger->entrance(); $this->check_ajax_referer( 'toggle_whitelabel_mode' ); if ( ! $this->is_user_admin() ) { // Only for admins. self::shoot_ajax_failure(); } $license = $this->get_api_user_scope()->call( "/licenses/{$this->_site->license_id}.json", 'put', array( 'is_whitelabeled' => ! $this->_license->is_whitelabeled ) ); if ( ! $this->is_api_result_entity( $license ) ) { self::shoot_ajax_failure( FS_Api::is_api_error_object( $license ) ? $license->error->message : fs_text_inline( "An unknown error has occurred while trying to toggle the license's white-label mode.", 'unknown-error-occurred', $this->get_slug() ) ); } $this->_license->is_whitelabeled = $license->is_whitelabeled; $this->_store_licenses(); $this->_sync_license(); if ( ! $license->is_whitelabeled ) { $this->_admin_notices->remove_sticky( 'license_whitelabeled' ); } else { $this->_admin_notices->add_sticky( sprintf( $this->get_text_inline( 'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.', 'license_whitelabeled' ), "{$this->get_plugin_title()}", sprintf( '%s', $this->get_text_inline( 'User Dashboard', 'user-dashboard' ) ), sprintf( '%s', $this->get_text_inline( 'revert it now', 'revert-it-now' ) ) ), 'license_whitelabeled' ); } self::shoot_ajax_response( array( 'success' => true ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 */ function _add_beta_mode_update_handler() { if ( ! $this->is_user_admin() ) { return; } if ( ! $this->is_premium() ) { return; } $this->add_ajax_action( 'set_beta_mode', array( &$this, '_set_beta_mode_ajax_handler' ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 */ function _set_beta_mode_ajax_handler() { $this->_logger->entrance(); $this->check_ajax_referer( 'set_beta_mode' ); if ( ! $this->is_user_admin() ) { // Only for admins. self::shoot_ajax_failure(); } $is_beta = trim( fs_request_get( 'is_beta', '', 'post' ) ); if ( empty( $is_beta ) || ! in_array( $is_beta, array( 'true', 'false' ) ) ) { self::shoot_ajax_failure(); } $site = $this->api_site_call( '', 'put', array( 'is_beta' => ( 'true' == $is_beta ), 'fields' => 'is_beta' ) ); if ( ! $this->is_api_result_entity( $site ) ) { self::shoot_ajax_failure( FS_Api::is_api_error_object( $site ) ? $site->error->message : fs_text_inline( "An unknown error has occurred while trying to set the user's beta mode.", 'unknown-error-occurred', $this->get_slug() ) ); } $this->_site->is_beta = $site->is_beta; $this->_store_site(); self::shoot_ajax_response( array( 'success' => true ) ); } /** * License activation WP AJAX handler. * * @author Leo Fajardo (@leorw) * @since 1.1.9 * * @uses Freemius::activate_license() */ function _activate_license_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'activate_license' ); $license_key = trim( fs_request_get_raw( 'license_key' ) ); if ( empty( $license_key ) ) { exit; } $sites = fs_is_network_admin() ? fs_request_get( 'sites', array(), 'post' ) : array(); $result = $this->activate_license( $license_key, $sites, fs_request_get_bool( 'is_marketing_allowed', null ), 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_diagnostic_tracking_allowed', null ) ); if ( $result['success'] && $this->is_bundle_license_auto_activation_enabled() ) { $license = new FS_Plugin_License(); $license->secret_key = $license_key; $this->maybe_activate_bundle_license( $license, $sites ); } echo json_encode( $result ); exit; } /** * User change WP AJAX handler. * * @author Leo Fajardo (@leorw) * @since 2.3.2 */ function _user_change_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'change_user' ); $new_email_address = trim( fs_request_get( 'email_address', '' ) ); $new_user_id = fs_request_get( 'user_id' ); if ( empty( $new_email_address ) && ! FS_User::is_valid_id( $new_user_id ) ) { self::shoot_ajax_failure( fs_text_inline( 'Invalid new user ID or email address.', 'invalid-new-user-id-or-email', $this->get_slug() ) ); } $params = array(); if ( ! empty( $new_email_address ) ) { $params['user_email'] = $new_email_address; } else { $params['user_id'] = $new_user_id; } $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_ids[ $slug ] = $install_info['install']->id; } $params['install_ids'] = implode( ',', array_values( $install_ids ) ); $install = $this->get_api_site_scope()->call( $this->add_show_pending( '/' ), 'put', $params ); if ( FS_Api::is_api_error( $install ) ) { $error = ''; if ( is_object( $install ) ) { switch ( $install->error->code ) { case 'user_exist': $error = ( $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...' . $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_address . '' ) . sprintf( '', $this->get_account_url( 'change_owner', array( 'state' => 'init', 'candidate_email' => $new_email_address ) ), $this->get_text_inline( 'Change Ownership', 'change-ownership' ) ) ); break; } } if ( empty( $error ) ) { $error = FS_Api::is_api_error_object( $install ) ? $install->error->message : var_export( $install->error, true ); } self::shoot_ajax_failure( $error ); } else { if ( // If successful ownership change. $this->get_user()->id != $install->user_id || ! empty( $new_email_address ) ) { $this->complete_ownership_change_by_license( $install->user_id, $install_ids ); } } self::shoot_ajax_success(); } /** * @author Leo Fajardo (@leorw) * @since 2.3.2.14 */ function starting_migration() { if ( ! empty( $this->_storage->license_migration ) ) { // Do not overwrite the data if already set. return; } $this->_storage->license_migration = array( 'is_migrating' => true, 'start_timestamp' => time() ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.2.14 */ function is_migration() { if ( $this->is_addon() ) { return $this->get_parent_instance()->is_migration(); } if ( empty( $this->_storage->license_migration ) ) { return false; } if ( ! $this->_storage->license_migration['is_migrating'] ) { return false; } return ( // Return `true` if the migration is within 5 minutes from the starting time. ( time() - $this->_storage->license_migration['start_timestamp'] ) <= WP_FS__TIME_5_MIN_IN_SEC ); } /** * * A helper method to activate migrated licenses. If the product is network activated and integrated, the method will network activate the license. * * @author Vova Feldman (@svovaf) * @since 2.3.0 * * @param string $license_key * @param null|bool $is_marketing_allowed * @param null|number $plugin_id * @param array $sites * @param int $blog_id * * @return array { * @var bool $success * @var string $error * @var string $next_page * } * * @uses Freemius::activate_license() */ function activate_migrated_license( $license_key, $is_marketing_allowed = null, $plugin_id = null, $sites = array(), $blog_id = null ) { $this->_logger->entrance(); $result = $this->activate_license( $license_key, ( empty( $sites ) && is_null( $blog_id ) && $this->is_network_active() ) ? $this->get_sites_for_network_level_optin() : $sites, $is_marketing_allowed, $blog_id, $plugin_id ); // No need to show the sticky after license activation notice after migrating a license. $this->_admin_notices->remove_sticky( 'plan_upgraded' ); return $result; } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @return string */ function get_pricing_js_path() { if ( ! isset( $this->_pricing_js_path ) ) { $pricing_js_path = $this->apply_filters( 'freemius_pricing_js_path', '' ); if ( empty( $pricing_js_path ) ) { global $fs_active_plugins; foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { if ( $data->plugin_path == $this->get_plugin_basename() ) { $plugin_or_theme_root_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) ); $pricing_js_path = $plugin_or_theme_root_dir . '/' // The basename will be `plugins`, `themes`, or the basename of a custom plugins or themes directory. . str_replace( '../' . basename( $plugin_or_theme_root_dir ) . '/', '', $sdk_path ) . '/includes/freemius-pricing/freemius-pricing.js'; break; } } } $this->_pricing_js_path = $pricing_js_path; } return $this->_pricing_js_path; } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @return bool */ function should_use_external_pricing() { if ( is_null( $this->_use_external_pricing ) ) { $pricing_js_path = $this->get_pricing_js_path(); $this->_use_external_pricing = ( empty( $pricing_js_path ) || ! file_exists( $pricing_js_path ) ); } return $this->_use_external_pricing; } /** * The implementation of this method was previously in `_activate_license_ajax_action()`. * * @author Vova Feldman (@svovaf) * @since 2.2.4 * @since 2.0.0 When a super-admin that hasn't connected before is network activating a license and excluding some of the sites for the license activation, go over the unselected sites in the network and if a site is not connected, skipped, nor delegated, if it's a freemium product then just skip the connection for the site, if it's a premium only product, delegate the connection and license activation to the site admin (Vova Feldman @svovaf). * @param string $license_key * @param array $sites * @param null|bool $is_marketing_allowed * @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 * @var string $error * @var string $next_page * } */ private function activate_license( $license_key, $sites = array(), $is_marketing_allowed = null, $blog_id = null, $plugin_id = null, $license_owner_id = null, $is_extensions_tracking_allowed = null, $is_diagnostic_tracking_allowed = null ) { $this->_logger->entrance(); $license_key = trim( $license_key ); $is_network_activation_or_migration = ( fs_is_network_admin() || ( ! empty( $sites ) && $this->is_migration() ) ); if ( ! $is_network_activation_or_migration ) { // If the license activation is executed outside the context of a network admin, ignore the sites collection. $sites = array(); } $fs = ( empty($plugin_id) || $plugin_id == $this->_module_id ) ? $this : $this->get_addon_instance( $plugin_id ); 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; $has_valid_blog_id = is_numeric( $blog_id ); $user = null; if ( $fs->is_addon() && $fs->get_parent_instance()->is_registered() ) { /** * When activating an add-on's license and the parent is opted-in, activate the license with the parent's opted-in user context. * * @author Vova Feldman (@svovaf) */ $user = $fs->get_parent_instance()->get_current_or_network_user(); } else if ( $fs->is_registered() ) { $user = $fs->get_current_or_network_user(); } if ( $has_valid_blog_id ) { /** * If a specific blog ID was provided, activate the license only on the specific blog that is associated with the given blog ID. * * @author Leo Fajardo (@leorw) */ $fs->switch_to_blog( $blog_id ); } 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(); $site_ids = array(); foreach ( $sites as $site ) { if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) { continue; } $install = $fs->get_install_by_blog_id( $site['blog_id'] ); if ( is_object( $install ) ) { $blog_2_install_map[ $site['blog_id'] ] = $install; } else { $site_ids[] = $site['blog_id']; } } if ( ! empty( $blog_2_install_map ) ) { $result = $fs->activate_license_on_many_installs( $user, $license_key, $blog_2_install_map ); } if ( true === $result && ! empty( $site_ids ) ) { $result = $fs->activate_license_on_many_sites( $user, $license_key, $site_ids ); } } else { if ( $fs->is_registered() ) { $params = array( 'license_key' => $fs->apply_filters( 'license_key', $license_key ) ); $install_ids = array(); $change_owner = FS_User::is_valid_id( $license_owner_id ); if ( $change_owner ) { $params['user_id'] = $license_owner_id; $installs_info_by_slug_map = $fs->get_parent_and_addons_installs_info(); foreach ( $installs_info_by_slug_map as $slug => $install_info ) { $install_ids[ $slug ] = $install_info['install']->id; } $params['install_ids'] = implode( ',', array_values( $install_ids ) ); } $api = $fs->get_api_site_scope(); $result = $api->call( $fs->add_show_pending( '/' ), 'put', $params ); if ( ! FS_Api::is_api_error( $result ) ) { $install = $result; $fs->reconnect_locally( $has_valid_blog_id ); if ( $change_owner && // If successful ownership change. $fs->get_user()->id != $install->user_id ) { $fs->complete_ownership_change_by_license( $install->user_id, $install_ids ); } } } else /* ( $fs->is_addon() && $fs->get_parent_instance()->is_registered() ) */ { $result = $fs->activate_license_on_site( $user, $license_key ); } } 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; if ( fs_is_network_admin() && ! $has_valid_blog_id ) { $fs->_site = $fs->get_network_install(); } $fs->_sync_license( true, $has_valid_blog_id ); $this->maybe_sync_install_user(); $next_page = $fs->is_addon() ? $fs->get_parent_instance()->get_account_url() : $fs->get_after_activation_url( 'after_connect_url' ); } } else { $next_page = $fs->opt_in( false, false, false, $license_key, false, false, false, $is_marketing_allowed, $sites ); if ( isset( $next_page->error ) ) { $error = $next_page->error; } else { if ( $is_network_activation_or_migration ) { /** * Get the list of sites that were just opted-in (and license activated). * This is an optimization for the next part below saving some DB queries. */ $connected_sites = array(); foreach ( $sites as $site ) { if ( isset( $site['blog_id'] ) && is_numeric( $site['blog_id'] ) ) { $connected_sites[ $site['blog_id'] ] = true; } } $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. * * @author Vova Feldman (@svovaf) */ foreach ( $all_sites as $site ) { $blog_id = self::get_site_blog_id( $site ); if ( isset( $connected_sites[ $blog_id ] ) ) { // Site was just connected. continue; } if ( $fs->is_installed_on_site( $blog_id ) ) { // Site was already connected before. continue; } if ( $fs->is_site_delegated_connection( $blog_id ) ) { // Site's connection was delegated. continue; } if ( $fs->is_anonymous_site( $blog_id ) ) { // Site connection was already skipped. continue; } $pending_blog_ids[] = $blog_id; } if ( ! empty( $pending_blog_ids ) ) { if ( $fs->is_freemium() && $fs->is_enable_anonymous() ) { $fs->skip_connection( $pending_blog_ids ); } else { $fs->delegate_connection( $pending_blog_ids ); } } } } } if ( false === $error && true === $fs->_storage->require_license_activation ) { $fs->_storage->require_license_activation = false; } $result = array( 'success' => ( false === $error ) ); if ( false !== $error ) { $result['error'] = $fs->apply_filters( 'opt_in_error_message', $error ); } else { if ( $fs->is_addon() || $fs->has_addons() ) { /** * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, * an updated valid user licenses collection will be fetched from the server which is used to also * update the account add-ons (add-ons the user has licenses for). * * @author Leo Fajardo (@leorw) * @since 2.2.4 */ $fs->purge_valid_user_licenses_cache(); } $result['next_page'] = $next_page; } return $result; } /** * @author Leo Fajardo (@leorw) * @since 2.3.2 * * @return array { * @key string Product slug. * @value array { * @property FS_Site $site * @property FS_Plugin_License $license * } * } */ private function get_parent_and_addons_installs_info() { $fs = $this->is_addon() ? $this->get_parent_instance() : $this; $installed_addons_ids = array(); $installed_addons_instances = $fs->get_installed_addons(); foreach ( $installed_addons_instances as $instance ) { $installed_addons_ids[] = $instance->get_id(); } $addons_ids = array_unique( array_merge( $installed_addons_ids, $fs->get_updated_account_addons() ) ); // Add parent product info. $installs_info_by_slug_map = array( $fs->get_slug() => array( 'install' => $fs->get_site(), 'license' => $fs->_get_license() ) ); foreach ( $addons_ids as $addon_id ) { $is_installed = isset( $installed_addons_ids_map[ $addon_id ] ); $addon_info = $fs->_get_addon_info( $addon_id, $is_installed ); if ( ! isset( $addon_info['is_connected'] ) || ! $addon_info['is_connected'] ) { // Add-on is not associated with an install entity. continue; } $installs_info_by_slug_map[ $addon_info['slug'] ] = array( 'install' => $addon_info['site'], 'license' => isset( $addon_info['license'] ) ? $addon_info['license'] : null ); } return $installs_info_by_slug_map; } /** * @author Leo Fajardo (@leorw) * @since 1.2.3.1 */ function _network_activate_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'network_activate' ); $plugin_id = fs_request_get( 'module_id', '', 'post' ); $fs = ( $plugin_id == $this->_module_id ) ? $this : $this->get_addon_instance( $plugin_id ); $error = false; $sites = fs_request_get( 'sites', array(), 'post' ); if ( is_array( $sites ) && ! empty( $sites ) ) { $sites_by_action = array( 'allow' => array(), 'delegate' => array(), 'skip' => array() ); foreach ( $sites as $site ) { $sites_by_action[ $site['action'] ][] = $site; } $total_sites = count( $sites ); $total_sites_to_delegate = count( $sites_by_action['delegate'] ); $next_page = ''; $has_any_install = fs_request_get_bool( 'has_any_install' ); if ( $total_sites === $total_sites_to_delegate && ! $this->is_network_upgrade_mode() && ! $has_any_install ) { $this->delegate_connection(); } else { if ( ! empty( $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( self::get_sites_blog_ids( $sites_by_action['skip'] ) ); } if ( empty( $sites_by_action['allow'] ) ) { if ( $has_any_install ) { $first_install = $fs->find_first_install(); if ( ! is_null( $first_install ) ) { $fs->_site = $first_install['install']; $fs->_storage->network_install_blog_id = $first_install['blog_id']; $fs->_user = self::_get_user_by_id( $fs->_site->user_id ); $fs->_storage->network_user_id = $fs->_user->id; } } } else { if ( ! $fs->is_registered() || ! $this->_is_network_active ) { $next_page = $fs->opt_in( false, false, false, false, false, false, false, fs_request_get_bool( 'is_marketing_allowed', null ), $sites_by_action['allow'] ); } else { $next_page = $fs->install_with_user( $this->get_network_user(), false, false, false, true, $sites_by_action['allow'] ); } if ( is_object( $next_page ) && isset( $next_page->error ) ) { $error = $next_page->error; } } } if ( empty( $next_page ) ) { $next_page = $this->get_after_activation_url( 'after_network_activation_url' ); } } else { $error = $this->get_text_inline( 'Invalid site details collection.', 'invalid_site_details_collection' ); } $result = array( 'success' => ( false === $error ) ); if ( false !== $error ) { $result['error'] = $error; } else { $result['next_page'] = $next_page; } echo json_encode( $result ); exit; } /** * Billing update AJAX callback. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 */ function _update_billing_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'update_billing' ); if ( ! $this->is_user_admin() ) { // Only for admins. self::shoot_ajax_failure(); } $billing = fs_request_get( 'billing' ); $api = $this->get_api_user_scope(); $result = $api->call( '/billing.json', 'put', array_merge( $billing, array( 'plugin_id' => $this->get_parent_id(), ) ) ); if ( ! $this->is_api_result_entity( $result ) ) { self::shoot_ajax_failure(); } // Purge cached billing. $this->get_api_user_scope()->purge_cache( 'billing.json' ); self::shoot_ajax_success(); } /** * Trial start for anonymous users (AJAX callback). * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 */ function _start_trial_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'start_trial' ); if ( ! $this->is_user_admin() ) { // Only for admins. self::shoot_ajax_failure(); } $trial_data = fs_request_get( 'trial' ); $next_page = $this->opt_in( false, false, false, false, false, $trial_data['plan_id'] ); if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) { self::shoot_ajax_failure( isset( $next_page->error ) ? $next_page->error->message : var_export( $next_page, true ) ); } $this->shoot_ajax_success( array( 'next_page' => $next_page, ) ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.0 */ function _resend_license_key_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'resend_license_key' ); $email_address = sanitize_email( trim( fs_request_get( 'email', '', 'post' ) ) ); if ( empty( $email_address ) ) { exit; } $error = false; $api = $this->get_api_plugin_scope(); $result = $api->call( '/licenses/resend.json', 'post', array( 'email' => $email_address, 'url' => home_url(), ) ); if ( is_object( $result ) && isset( $result->error ) ) { $error = $result->error; if ( in_array( $error->code, array( 'invalid_email', 'no_user' ) ) ) { $error = $this->get_text_inline( "We couldn't find your email address in the system, are you sure it's the right address?", 'email-not-found' ); } else if ( 'no_license' === $error->code ) { $error = $this->get_text_inline( "We can't see any active licenses associated with that email address, are you sure it's the right address?", 'no-active-licenses' ); } else { $error = $error->message; } } $licenses = array( 'success' => ( false === $error ) ); if ( false !== $error ) { $licenses['error'] = sprintf( '%s... %s', $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ), strtolower( $error ) ); } echo json_encode( $licenses ); exit; } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.8 * * @var string */ private static $_pagenow; /** * Get current page or the referer if executing a WP AJAX request. * * @author Vova Feldman (@svovaf) * @since 1.2.1.8 * * @return string */ static function get_current_page() { if ( ! isset( self::$_pagenow ) ) { global $pagenow; if ( empty( $pagenow ) && is_admin() && is_multisite() ) { /** * It appears that `$pagenow` is not yet initialized in some network admin pages when this method * is called, so initialize it here using some pieces of code from `wp-includes/vars.php`. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ if ( is_network_admin() ) { preg_match( '#/wp-admin/network/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); } else if ( is_user_admin() ) { preg_match( '#/wp-admin/user/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); } else { preg_match( '#/wp-admin/?(.*?)$#i', $_SERVER['PHP_SELF'], $self_matches ); } $pagenow = $self_matches[1]; $pagenow = trim( $pagenow, '/' ); $pagenow = preg_replace( '#\?.*?$#', '', $pagenow ); if ( '' === $pagenow || 'index' === $pagenow || 'index.php' === $pagenow ) { $pagenow = 'index.php'; } else { preg_match( '#(.*?)(/|$)#', $pagenow, $self_matches ); $pagenow = strtolower( $self_matches[1] ); if ( '.php' !== substr($pagenow, -4, 4) ) $pagenow .= '.php'; // for Options +Multiviews: /wp-admin/themes/index.php (themes.php is queried) } } self::$_pagenow = $pagenow; if ( self::is_ajax() && 'admin-ajax.php' === $pagenow ) { $referer = fs_get_raw_referer(); if ( is_string( $referer ) ) { $parts = explode( '?', $referer ); self::$_pagenow = basename( $parts[0] ); } } } return self::$_pagenow; } /** * Helper method to check if user in the plugins page. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @return bool */ static function is_plugins_page() { return ( 'plugins.php' === self::get_current_page() ); } /** * @author Leo Fajardo (@leorw) * @since 2.2.3 * * @return bool */ static function is_plugin_install_page() { return ( 'plugin-install.php' === self::get_current_page() ); } /** * @author Leo Fajardo (@leorw) * @since 2.0.2 * * @return bool */ static function is_updates_page() { return ( 'update-core.php' === self::get_current_page() ); } /** * Helper method to check if user in the themes page. * * @author Vova Feldman (@svovaf) * @since 1.2.2.6 * * @return bool */ static function is_themes_page() { return ( 'themes.php' === self::get_current_page() ); } #---------------------------------------------------------------------------------- #region Affiliation #---------------------------------------------------------------------------------- /** * @author Leo Fajardo (@leorw) * @since 1.2.3 * * @return bool */ function has_affiliate_program() { if ( ! is_object( $this->_plugin ) ) { return false; } 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 ) ) { /** * 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; } $this->plugin_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms ); } } /** * @author Leo Fajardo (@leorw) * @since 1.2.4 */ private function fetch_affiliate_and_custom_terms() { if ( ! empty( $this->_storage->affiliate_application_data ) ) { $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/{$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] ); if ( ! isset( $application_data['status'] ) || $application_data['status'] !== $affiliate->status ) { $application_data['status'] = $affiliate->status; $this->_storage->affiliate_application_data = $application_data; } if ( $affiliate->is_using_custom_terms ) { $affiliate_terms = $users_api->get( "/plugins/{$this->_plugin->id}/affiliates/{$affiliate->id}/aff/{$affiliate->custom_affiliate_terms_id}.json", $flush ); if ( $this->is_api_result_entity( $affiliate_terms ) ) { $this->custom_affiliate_terms = new FS_AffiliateTerms( $affiliate_terms ); } } $this->affiliate = $affiliate; } } } } /** * @author Leo Fajardo (@leorw) * @since 1.2.3 */ private function fetch_affiliate_and_terms() { $this->_logger->entrance(); $this->fetch_affiliate_terms(); $this->fetch_affiliate_and_custom_terms(); } /** * @author Leo Fajardo (@leorw) * @since 1.2.3 * * @return FS_Affiliate */ function get_affiliate() { return $this->affiliate; } /** * @author Leo Fajardo (@leorw) * @since 1.2.3 * * @return FS_AffiliateTerms */ function get_affiliate_terms() { return is_object( $this->custom_affiliate_terms ) ? $this->custom_affiliate_terms : $this->plugin_affiliate_terms; } /** * @author Leo Fajardo (@leorw) * @since 1.2.3 */ function _submit_affiliate_application() { $this->_logger->entrance(); $this->check_ajax_referer( 'submit_affiliate_application' ); if ( ! $this->is_user_admin() ) { // Only for admins. self::shoot_ajax_failure(); } $affiliate = fs_request_get( 'affiliate' ); if ( empty( $affiliate['promotion_methods'] ) ) { unset( $affiliate['promotion_methods'] ); } if ( ! empty( $affiliate['additional_domains'] ) ) { $affiliate['additional_domains'] = array_unique( $affiliate['additional_domains'] ); } if ( ! $this->is_registered() ) { // Opt in but don't track usage. $next_page = $this->opt_in( false, false, false, false, false, false, true ); if ( is_object( $next_page ) && $this->is_api_error( $next_page ) ) { self::shoot_ajax_failure( isset( $next_page->error ) ? $next_page->error->message : var_export( $next_page, true ) ); } else if ( $this->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/{$plugin_id_for_affiliate}/aff/{$this->plugin_affiliate_terms->id}/affiliates.json" ), 'post', $affiliate ); if ( $this->is_api_error( $result ) ) { self::shoot_ajax_failure( isset( $result->error ) ? $result->error->message : var_export( $result, true ) ); } else { if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) { $this->_admin_notices->remove_sticky( 'affiliate_program' ); } $affiliate_application_data = array( 'status' => 'pending', 'stats_description' => $affiliate['stats_description'], 'promotion_method_description' => $affiliate['promotion_method_description'], ); if ( ! empty( $affiliate['promotion_methods'] ) ) { $affiliate_application_data['promotion_methods'] = $affiliate['promotion_methods']; } if ( ! empty( $affiliate['domain'] ) ) { $affiliate_application_data['domain'] = $affiliate['domain']; } if ( ! empty( $affiliate['additional_domains'] ) ) { $affiliate_application_data['additional_domains'] = $affiliate['additional_domains']; } $this->_storage->affiliate_application_data = $affiliate_application_data; } // Purge cached affiliate. $api->purge_cache( 'affiliate.json' ); self::shoot_ajax_success( $result ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.3 * * @return array|null */ function get_affiliate_application_data() { if ( empty( $this->_storage->affiliate_application_data ) ) { return null; } return $this->_storage->affiliate_application_data; } #endregion Affiliation ------------------------------------------------------------ #---------------------------------------------------------------------------------- #region URL Generators #---------------------------------------------------------------------------------- /** * Alias to pricing_url(). * * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @uses pricing_url() * * @param string $period Billing cycle * @param bool $is_trial * * @return string */ function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) { return $this->pricing_url( $period, $is_trial ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @uses get_upgrade_url() * * @return string */ function get_trial_url() { return $this->get_upgrade_url( WP_FS__PERIOD_ANNUALLY, true ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.4 * * @param string $new_version * * @return string */ function version_upgrade_checkout_link( $new_version ) { if ( ! is_object( $this->_license ) ) { $url = $this->pricing_url(); $purchase_license_text = $this->get_text_inline( 'Buy a license now', 'buy-license-now' ); } else { $subscription = $this->_get_subscription( $this->_license->id ); $url = $this->checkout_url( is_object( $subscription ) ? ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : WP_FS__PERIOD_LIFETIME, false, array( 'licenses' => $this->_license->quota ) ); $purchase_license_text = $this->get_text_inline( 'Renew your license now', 'renew-license-now' ); } return sprintf( $this->get_text_inline( '%s to access version %s security & feature updates, and support.', 'x-for-updates-and-support' ), sprintf( '%s', $this->apply_filters( 'update_notice_checkout_url', $url ), $purchase_license_text ), $new_version ); } /** * Plugin's pricing URL. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $billing_cycle Billing cycle * * @param bool $is_trial * * @return string */ function pricing_url( $billing_cycle = WP_FS__PERIOD_ANNUALLY, $is_trial = false ) { $this->_logger->entrance(); $params = array( 'billing_cycle' => $billing_cycle ); if ( $is_trial ) { $params['trial'] = 'true'; } $url = $this->is_addon() ? $this->_parent->addon_url( $this->_slug ) : $this->_get_admin_page_url( 'pricing', $params ); return $this->apply_filters( 'pricing_url', $url ); } /** * Checkout page URL. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param string $billing_cycle Billing cycle * @param bool $is_trial * @param array $extra (optional) Extra parameters, override other query params. * @param bool|null $network * * @return string */ function checkout_url( $billing_cycle = WP_FS__PERIOD_ANNUALLY, $is_trial = false, $extra = array(), $network = null ) { $this->_logger->entrance(); $params = array( 'checkout' => 'true', 'billing_cycle' => $billing_cycle, ); if ( $is_trial ) { $params['trial'] = 'true'; } /** * Params in extra override other params. */ $params = array_merge( $params, $extra ); return $this->apply_filters( 'checkout_url', $this->_get_admin_page_url( 'pricing', $params, $network ) ); } /** * Add-on checkout URL. * * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @param number $addon_id * @param number $pricing_id * @param string $billing_cycle * @param bool $is_trial * @param bool|null $network * * @return string */ function addon_checkout_url( $addon_id, $pricing_id, $billing_cycle = WP_FS__PERIOD_ANNUALLY, $is_trial = false, $network = null ) { return $this->checkout_url( $billing_cycle, $is_trial, array( 'plugin_id' => $addon_id, 'pricing_id' => $pricing_id, ), $network ); } #endregion #endregion ------------------------------------------------------------------ /** * Check if plugin has any add-ons. * * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @since 1.1.7.3 Base logic only on the parameter provided by the developer in the init function. * * @return bool */ function has_addons() { $this->_logger->entrance(); return $this->_has_addons; } /** * Check if plugin can work in anonymous mode. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool * * @deprecated Please use is_enable_anonymous() instead. */ function enable_anonymous() { return $this->_enable_anonymous; } /** * Check if plugin can work in anonymous mode. * * @author Vova Feldman (@svovaf) * @since 1.1.9 * * @return bool */ function is_enable_anonymous() { return $this->_enable_anonymous; } /** * Check if plugin is premium only (no free plans). * * @author Vova Feldman (@svovaf) * @since 1.1.9 * * @return bool */ function is_only_premium() { return $this->_is_premium_only; } /** * Checks if the plugin's type is "plugin". The other type is "theme". * * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool */ function is_plugin() { return ( WP_FS__MODULE_TYPE_PLUGIN === $this->_module_type ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return string */ function get_module_type() { if ( ! isset( $this->_module_type ) ) { $id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array() ); $this->_module_type = $id_slug_type_path_map[ $this->_module_id ]['type']; } return $this->_module_type; } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return string */ function get_plugin_main_file_path() { return $this->_plugin_main_file_path; } /** * Check if module has a premium code version. * * Serviceware module might be freemium without any * premium code version, where the paid features * are all part of the service. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @return bool */ function has_premium_version() { return $this->_has_premium_version; } /** * Check if feature supported with current site's plan. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @todo IMPLEMENT * * @param number $feature_id * * @throws Exception */ function is_feature_supported( $feature_id ) { throw new Exception( 'not implemented' ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @return bool Is running in SSL/HTTPS */ function is_ssl() { return WP_FS__IS_HTTPS; } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool Is running in AJAX call. * * @link http://wordpress.stackexchange.com/questions/70676/how-to-check-if-i-am-in-admin-ajax */ static function is_ajax() { return ( defined( 'DOING_AJAX' ) && DOING_AJAX ); } /** * Check if it's an AJAX call targeted for the current module. * * @author Vova Feldman (@svovaf) * @since 1.2.0 * * @param array|string $actions Collection of AJAX actions. * * @return bool */ function is_ajax_action( $actions ) { // Verify it's an ajax call. if ( ! self::is_ajax() ) { return false; } // Verify the call is relevant for the plugin. if ( $this->_module_id != fs_request_get( 'module_id' ) ) { return false; } // Verify it's one of the specified actions. if ( is_string( $actions ) ) { $actions = explode( ',', $actions ); } if ( is_array( $actions ) && 0 < count( $actions ) ) { $ajax_action = fs_request_get( 'action' ); foreach ( $actions as $action ) { if ( $ajax_action === $this->get_action_tag( $action ) ) { return true; } } } return false; } /** * Check if it's an AJAX call targeted for current request. * * @author Vova Feldman (@svovaf) * @since 1.2.0 * * @param array|string $actions Collection of AJAX actions. * @param number|null $module_id * * @return bool */ static function is_ajax_action_static( $actions, $module_id = null ) { // Verify it's an ajax call. if ( ! self::is_ajax() ) { return false; } if ( ! empty( $module_id ) ) { // Verify the call is relevant for the plugin. if ( $module_id != fs_request_get( 'module_id' ) ) { return false; } } // Verify it's one of the specified actions. if ( is_string( $actions ) ) { $actions = explode( ',', $actions ); } if ( is_array( $actions ) && 0 < count( $actions ) ) { $ajax_action = fs_request_get( 'action' ); foreach ( $actions as $action ) { if ( $ajax_action === self::get_ajax_action_static( $action, $module_id ) ) { return true; } } } return false; } /** * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @return bool */ 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. * * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @return bool */ function is_user_in_admin() { return ( is_admin() && ! self::is_ajax() && ! self::is_cron() && ! self::is_admin_post() ); } /** * Check if a real user is in the customizer view. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return bool */ static function is_customizer() { return is_customize_preview(); } /** * Check if running in HTTPS and if site's plan matching the specified plan. * * @param string $plan * @param bool $exact * * @return bool */ function is_ssl_and_plan( $plan, $exact = false ) { return ( $this->is_ssl() && $this->is_plan( $plan, $exact ) ); } /** * Construct plugin's settings page URL. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $page * @param array $params * @param bool|null $network * * @return string */ function _get_admin_page_url( $page = '', $params = array(), $network = null ) { if ( is_null( $network ) ) { $network = ( $this->_is_network_active && ( fs_is_network_admin() || ! $this->is_delegated_connection() ) ); } if ( 0 < count( $params ) ) { foreach ( $params as $k => $v ) { $params[ $k ] = urlencode( $v ); } } $page_param = $this->_menu->get_slug( $page ); if ( empty( $page ) && // Show the opt-in as an overlay for free wp.org themes or themes without any settings page. $this->show_opt_in_on_themes_page() ) { $params[ $this->get_unique_affix() . '_show_optin' ] = 'true'; return add_query_arg( $params, $this->admin_url( 'themes.php', 'admin', $network ) ); } if ( ! $this->has_settings_menu() ) { if ( ! empty( $page ) ) { // Module doesn't have a setting page, but since the request is for // a specific Freemius page, use the admin.php path. return add_query_arg( array_merge( $params, array( 'page' => $page_param, ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); } else { if ( $this->is_activation_mode() ) { /** * @author Vova Feldman * @since 1.2.1.6 * * If plugin doesn't have a settings page, create one for the opt-in screen. */ return add_query_arg( array_merge( $params, array( 'page' => $this->_slug, ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); } else { // Plugin without a settings page. return add_query_arg( $params, $this->admin_url( 'plugins.php', 'admin', $network ) ); } } } // Module has a submenu settings page. if ( ! $this->_menu->is_top_level() ) { $parent_slug = $this->_menu->get_parent_slug(); $menu_file = ( false !== strpos( $parent_slug, '.php' ) ) ? $parent_slug : 'admin.php'; return add_query_arg( array_merge( $params, array( 'page' => $page_param, ) ), $this->admin_url( $menu_file, 'admin', $network ) ); } // Module has a top level CPT settings page. if ( $this->_menu->is_cpt() ) { if ( empty( $page ) && $this->is_activation_mode() ) { return add_query_arg( array_merge( $params, array( 'page' => $page_param ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); } else { if ( ! empty( $page ) ) { $params['page'] = $page_param; } return add_query_arg( $params, $this->admin_url( $this->_menu->get_raw_slug(), 'admin', $network ) ); } } // Module has a custom top level settings page. return add_query_arg( array_merge( $params, array( 'page' => $page_param, ) ), $this->admin_url( 'admin.php', 'admin', $network ) ); } #-------------------------------------------------------------------------------- #region Multisite #-------------------------------------------------------------------------------- /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @return bool */ function is_network_active() { return $this->_is_network_active; } /** * Delegate activation for the given sites in the network (or all sites if `null`) to site admins. * * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param bool|int[] $all_or_blog_ids */ private function delegate_connection( $all_or_blog_ids = true ) { $this->_logger->entrance(); $this->_admin_notices->remove_sticky( 'connect_account' ); if ( true === $all_or_blog_ids ) { // All sites delegation. $this->_storage->store( 'is_delegated_connection', true, true ); } else { // Specified sites delegation. foreach ( $all_or_blog_ids as $blog_id ) { $this->delegate_site_connection( $blog_id ); } } $this->network_upgrade_mode_completed(); } /** * Delegate specific network site conncetion to the site admin. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id */ private function delegate_site_connection( $blog_id ) { $this->_storage->store( 'is_delegated_connection', true, $blog_id ); } /** * Check if super-admin delegated the connection of ALL sites to the site admins. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return bool */ function is_network_delegated_connection() { if ( ! $this->_is_network_active ) { return false; } return $this->_storage->get( 'is_delegated_connection', false, true ); } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param int $blog_id * * @return bool */ function is_site_delegated_connection( $blog_id = 0 ) { if ( ! $this->_is_network_active ) { return false; } if ( 0 == $blog_id ) { $blog_id = get_current_blog_id(); } return $this->_storage->get( 'is_delegated_connection', false, $blog_id ); } /** * 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. * * If executed outside the the admin, check if delegated the connection * for the current context site OR the whole network. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id If set, checks if network delegated or blog specific delegated. * * @return bool */ function is_delegated_connection( $blog_id = 0 ) { if ( ! $this->_is_network_active ) { return false; } if ( fs_is_network_admin() && 0 == $blog_id ) { return $this->is_network_delegated_connection(); } return ( $this->is_network_delegated_connection() || $this->is_site_delegated_connection( $blog_id ) ); } /** * Check if the current module is active for the site. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id * * @return bool */ function is_active_for_site( $blog_id ) { if ( ! is_multisite() ) { // Not a multisite and this code is executed, means that the plugin is active. return true; } if ( $this->is_theme() ) { // All themes are site level activated. return true; } if ( $this->_is_network_active ) { // Plugin was network activated so it's active. return true; } return in_array( $this->_plugin_basename, (array) get_blog_option( $blog_id, 'active_plugins', array() ) ); } /** * @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( $limit = 1000, $offset = 0 ) { if ( ! is_multisite() ) { return array(); } /** * For consistency with get_blog_list() which only return active public sites. * * @author Vova Feldman (@svovaf) */ $args = array( /** * Commented out in order to handle the migration of site options whether the site is public or not. * * @author Leo Fajardo (@leorw) * @since 2.2.1 */ // 'public' => 1, 'archived' => 0, 'mature' => 0, 'spam' => 0, 'deleted' => 0, 'number' => $limit, 'offset' => $offset, ); return get_sites( $args ); } /** * Checks if a given blog is active. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param $blog_id * * @return bool */ private static function is_site_active( $blog_id ) { global $wpdb; $blog_info = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->blogs} WHERE blog_id = %d", $blog_id ) ); if ( ! is_object( $blog_info ) ) { return false; } return ( true == $blog_info->public && false == $blog_info->archived && false == $blog_info->mature && false == $blog_info->spam && false == $blog_info->deleted ); } /** * Get a mapping between the site addresses to their blog IDs. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return array { * @key string Site address without protocol with a trailing slash. * @value int Site's blog ID. * } */ private function get_address_to_blog_map() { $sites = self::get_sites(); // Map site addresses to their blog IDs. $address_to_blog_map = array(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); $address = self::get_unfiltered_site_url( $blog_id, true, true ); $address_to_blog_map[ $address ] = $blog_id; } return $address_to_blog_map; } /** * Get a mapping between the site addresses to their blog IDs. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return array { * @key int Site's blog ID. * @value FS_Site Associated install. * } */ function get_blog_install_map() { $sites = self::get_sites(); // Map site blog ID to its install. $install_map = array(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); $install = $this->get_install_by_blog_id( $blog_id ); if ( is_object( $install ) ) { $install_map[ $blog_id ] = $install; } } 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. * * @author Leo Fajardo (@leorw) * @since 2.1.0 * * @param number $fs_user_id * * @return array { * @key number $plugin_id * @value bool Always true. * } */ private static function get_user_opted_in_module_ids_map( $fs_user_id ) { self::$_static_logger->entrance(); if ( ! is_multisite() ) { $installs = array_merge( self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN ), self::get_all_sites( WP_FS__MODULE_TYPE_THEME ) ); } else { $sites = self::get_sites(); $installs = array(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); $installs = array_merge( $installs, self::get_all_sites( WP_FS__MODULE_TYPE_PLUGIN, $blog_id ), self::get_all_sites( WP_FS__MODULE_TYPE_THEME, $blog_id ) ); } } $module_ids_map = array(); foreach ( $installs as $install ) { if ( is_object( $install ) && FS_Site::is_valid_id( $install->id ) && FS_User::is_valid_id( $install->user_id ) && ( $install->user_id == $fs_user_id ) ) { $module_ids_map[ $install->plugin_id ] = true; } } return $module_ids_map; } /** * @author Leo Fajardo (@leorw) * * @return null|array { * 'install' => FS_Site Module's install, * 'blog_id' => string The associated blog ID. * } */ function find_first_install() { $sites = self::get_sites(); foreach ( $sites as $site ) { $blog_id = self::get_site_blog_id( $site ); $install = $this->get_install_by_blog_id( $blog_id ); if ( is_object( $install ) ) { return array( 'install' => $install, 'blog_id' => $blog_id ); } } return null; } /** * Switches the Freemius site level context to a specified blog. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @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, $flush = false ) { if ( ! is_numeric( $blog_id ) ) { return false; } if ( ! $flush && $blog_id == $this->_context_is_network_or_blog_id ) { return false; } switch_to_blog( $blog_id ); $this->_context_is_network_or_blog_id = $blog_id; self::$_accounts->set_site_blog_context( $blog_id ); $this->_storage->set_site_blog_context( $blog_id ); $this->_storage->set_network_active( $this->_is_network_active, $this->is_delegated_connection( $blog_id ) ); $this->_site = is_object( $install ) ? $install : $this->get_install_by_blog_id( $blog_id ); $this->_user = false; $this->_licenses = false; $this->_license = null; $this->is_whitelabeled = null; if ( is_object( $this->_site ) ) { // Try to fetch user from install. $this->_user = self::_get_user_by_id( $this->_site->user_id ); if ( ! is_object( $this->_user ) && FS_User::is_valid_id( $this->_storage->prev_user_id ) ) { // Try to fetch previously saved user. $this->_user = self::_get_user_by_id( $this->_storage->prev_user_id ); if ( ! is_object( $this->_user ) ) { // Fallback to network's user. $this->_user = $this->get_network_user(); } } $all_plugin_licenses = self::get_all_licenses( $this->_module_id ); if ( ! empty( $all_plugin_licenses ) ) { if ( ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) ) { $this->_license = null; } else { $license_found = false; foreach ( $all_plugin_licenses as $license ) { if ( $license->id == $this->_site->license_id ) { // License found. $this->_license = $license; $license_found = true; break; } } if ( $license_found ) { $this->link_license_2_user( $this->_license->id, $this->_user->id ); } } $this->_licenses = $this->get_user_licenses( $this->_user->id ); } } unset( $this->_site_api ); unset( $this->_user_api ); return true; } /** * Restore the blog context to the blog that originally loaded the module. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function restore_current_blog() { $this->switch_to_blog( $this->_blog_id ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param array|WP_Site $site * * @return int */ static function get_site_blog_id( &$site ) { return ( $site instanceof WP_Site ) ? $site->blog_id : ( is_object( $site ) && isset( $site->userblog_id ) ? $site->userblog_id : $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, $load_registration = false ) { $this->_logger->entrance(); $switched = false; $registration_date = null; if ( is_null( $site ) ) { $url = self::get_unfiltered_site_url(); $name = get_bloginfo( 'name' ); $blog_id = null; } else { $blog_id = self::get_site_blog_id( $site ); if ( get_current_blog_id() != $blog_id ) { switch_to_blog( $blog_id ); $switched = true; } if ( $site instanceof WP_Site ) { $url = $site->siteurl; $name = $site->blogname; $registration_date = $site->registered; } else { $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, ); // 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(); } return $info; } /** * Load the module's install based on the blog ID. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int|null $blog_id * * @return FS_Site */ function get_install_by_blog_id( $blog_id = null ) { $installs = self::get_all_sites( $this->_module_type, $blog_id ); $install = isset( $installs[ $this->_slug ] ) ? $installs[ $this->_slug ] : null; if ( is_object( $install ) && is_numeric( $install->id ) && is_numeric( $install->user_id ) && FS_Plugin_Plan::is_valid_id( $install->plan_id ) ) { // Load site. $install = clone $install; } return $install; } /** * Check if module is installed on a specified site. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int|null $blog_id * * @return bool */ function is_installed_on_site( $blog_id = null ) { $installs = self::get_all_sites( $this->_module_type, $blog_id ); $install = isset( $installs[ $this->_slug ] ) ? $installs[ $this->_slug ] : null; return ( is_object( $install ) && is_numeric( $install->id ) && is_numeric( $install->user_id ) && FS_Plugin_Plan::is_valid_id( $install->plan_id ) ); } /** * Check if super-admin connected at least one site via the network opt-in. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return bool */ function is_network_registered() { if ( ! $this->_is_network_active ) { return false; } return FS_User::is_valid_id( $this->_storage->network_user_id ); } /** * Returns the main user associated with the network. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return FS_User */ function get_network_user() { if ( ! $this->_is_network_active ) { return null; } return FS_User::is_valid_id( $this->_storage->network_user_id ) ? self::_get_user_by_id( $this->_storage->network_user_id ) : null; } /** * Returns the current context user or the network's main user. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return FS_User */ function get_current_or_network_user() { return ( $this->_user instanceof FS_User ) ? $this->_user : $this->get_network_user(); } /** * Returns the main install associated with the network. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return FS_Site */ function get_network_install() { if ( ! $this->_is_network_active ) { return null; } return FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ? $this->get_install_by_blog_id( $this->_storage->network_install_blog_id ) : null; } /** * Returns the blog ID that is associated with the main install. * * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @return int|null */ function get_network_install_blog_id() { if ( ! $this->_is_network_active ) { return null; } return FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ? $this->_storage->network_install_blog_id : null; } /** * Returns the current context install or the network's main install. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return FS_Site */ function get_current_or_network_install() { return ( $this->_site instanceof FS_Site ) ? $this->_site : $this->get_network_install(); } /** * Check if executing a site level action from the network level admin. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return false|int If yes, return the requested blog ID. */ private function is_network_level_site_specific_action() { if ( ! $this->_is_network_active ) { return false; } if ( ! fs_is_network_admin() ) { return false; } $blog_id = fs_request_get( 'blog_id', '' ); return is_numeric( $blog_id ) ? $blog_id : false; } /** * Check if executing an action from the network level admin. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return bool */ private function is_network_level_action() { return ( $this->_is_network_active && fs_is_network_admin() ); } /** * Needs to be executed after site deactivation, archive, deletion, or flag as spam. * The logic updates the network level user and blog, and reschedule the crons if the cron executing site matching the site that is no longer publicly active. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $context_blog_id */ private function update_multisite_data_after_site_deactivation( $context_blog_id = 0 ) { $this->_logger->entrance(); if ( $this->_is_network_active ) { if ( $context_blog_id == $this->_storage->network_install_blog_id ) { $installs_map = $this->get_blog_install_map(); foreach ( $installs_map as $blog_id => $install ) { /** * @var FS_Site $install */ if ( $context_blog_id == $blog_id ) { continue; } if ( $install->user_id != $this->_storage->network_user_id ) { continue; } // Switch reference to a blog that is opted-in and belong to the same super-admin. $this->_storage->network_install_blog_id = $blog_id; break; } } } if ( ! $this->is_registered() ) { return; } if ( $this->is_sync_cron_scheduled() && $context_blog_id == $this->get_sync_cron_blog_id() ) { $this->schedule_sync_cron( WP_FS__SCRIPT_START_TIME, true, $context_blog_id ); } if ( $this->is_install_sync_scheduled() && $context_blog_id == $this->get_install_sync_cron_blog_id() ) { $this->schedule_install_sync( $context_blog_id ); } } /** * Executed after site deactivation, archive, or flag as spam. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $context_blog_id */ public function _after_site_deactivated_callback( $context_blog_id = 0 ) { $this->_logger->entrance(); $install = $this->get_install_by_blog_id( $context_blog_id ); if ( ! is_object( $install ) ) { // Site not connected. return; } $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 ); // Send deactivation event. $this->sync_install( array( 'is_active' => false, ) ); $this->switch_to_blog( $current_blog_id ); } /** * Executed after site deletion. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $context_blog_id * @param bool $drop True if site's database tables should be dropped. Default is false. */ public function _after_site_deleted_callback( $context_blog_id = 0, $drop = false ) { $this->_logger->entrance(); $install = $this->get_install_by_blog_id( $context_blog_id ); if ( ! is_object( $install ) ) { // Site not connected. return; } $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 ); if ( $drop ) { // Delete install if dropping site DB. $this->delete_account_event(); } else { // Send deactivation event. $this->sync_install( array( 'is_active' => false, ) ); } $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. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $context_blog_id */ public function _after_site_reactivated_callback( $context_blog_id = 0 ) { $this->_logger->entrance(); $install = $this->get_install_by_blog_id( $context_blog_id ); if ( ! is_object( $install ) ) { // Site not connected. return; } if ( ! self::is_site_active( $context_blog_id ) ) { // Site not yet active (can be in spam mode, archived, deleted...). return; } $current_blog_id = get_current_blog_id(); $this->switch_to_blog( $context_blog_id ); // Send re-activation event. $this->sync_install( array( 'is_active' => true, ) ); $this->switch_to_blog( $current_blog_id ); } #endregion Multisite /** * @author Leo Fajardo (@leorw) * * @param string $path * @param string $scheme * @param bool $network * * @return string */ private function admin_url( $path = '', $scheme = 'admin', $network = true ) { return ( $this->_is_network_active && $network ) ? network_admin_url( $path, $scheme ) : admin_url( $path, $scheme ); } /** * Check if currently in a specified admin page. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @param string $page * * @return bool */ function is_admin_page( $page ) { return ( $this->_menu->get_slug( $page ) === fs_request_get( 'page', '', 'get' ) ); } /** * Check if currently in the product's main admin page. * * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @return bool */ function is_main_admin_page() { return $this->is_admin_page( '' ); } /** * Get module's main admin setting page URL. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return string */ function main_menu_url() { return $this->_menu->main_menu_url(); } /** * Check if currently on the theme's setting page or * on any of the Freemius added pages (via tabs). * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return bool * * @deprecated Please use is_product_settings_page() instead; */ function is_theme_settings_page() { return $this->is_product_settings_page(); } /** * Check if currently on the product's main setting page or on any of the Freemius added pages (via tabs). * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @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( // e.g., {$menu_slug}-account, {$menu_slug}-affiliation, etc. $page, ( $menu_slug . '-' ) ); } /** * Plugin's account page + sync license URL. * * @author Vova Feldman (@svovaf) * @since 1.1.9.1 * * @param bool|number $plugin_id * @param bool $add_action_nonce * @param array $params * * @return string */ function _get_sync_license_url( $plugin_id = false, $add_action_nonce = true, $params = array() ) { if ( is_numeric( $plugin_id ) ) { $params['plugin_id'] = $plugin_id; } return $this->get_account_url( $this->get_unique_affix() . '_sync_license', $params, $add_action_nonce ); } /** * Plugin's account URL. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param bool|string $action * @param array $params * * @param bool $add_action_nonce * * @return string */ function get_account_url( $action = false, $params = array(), $add_action_nonce = true ) { if ( is_string( $action ) ) { $params['fs_action'] = $action; } self::require_pluggable_essentials(); return ( $add_action_nonce && is_string( $action ) ) ? fs_nonce_url( $this->_get_admin_page_url( 'account', $params ), $action ) : $this->_get_admin_page_url( 'account', $params ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.0 * * @param string $tab * @param bool $action * @param array $params * @param bool $add_action_nonce * * @return string * * @uses get_account_url() */ function get_account_tab_url( $tab, $action = false, $params = array(), $add_action_nonce = true ) { $params['tab'] = $tab; return $this->get_account_url( $action, $params, $add_action_nonce ); } /** * Plugin's account URL. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @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, $summary = false ) { $params = array(); if ( is_string( $topic ) ) { $params['topic'] = $topic; } if ( is_string( $message ) ) { $params['message'] = $message; } if ( is_string( $summary ) ) { $params['summary'] = $summary; } if ( $this->is_addon() ) { $params['addon_id'] = $this->get_id(); return $this->get_parent_instance()->_get_admin_page_url( 'contact', $params ); } else { return $this->_get_admin_page_url( 'contact', $params ); } } /** * Add-on direct info URL. * * @author Vova Feldman (@svovaf) * @since 1.1.0 * * @param string $slug * * @return string */ function addon_url( $slug ) { return $this->_get_admin_page_url( 'addons', array( 'slug' => $slug ) ); } /** * Add-ons URL. * * @author Vova Feldman (@svovaf) * @since 2.4.5 * * @return string */ function get_addons_url() { return $this->_get_admin_page_url( 'addons' ); } /* Logger ------------------------------------------------------------------------------------------------------------------*/ /** * @param string $id * @param bool $prefix_slug * * @return FS_Logger */ function get_logger( $id = '', $prefix_slug = true ) { return FS_Logger::get_logger( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id ); } /** * Note: This method is used externally so don't delete it. * * @param $id * @param bool $load_options * @param bool $prefix_slug * * @return FS_Option_Manager */ function get_options_manager( $id, $load_options = false, $prefix_slug = true ) { return FS_Option_Manager::get_manager( ( $prefix_slug ? $this->_slug : '' ) . ( ( ! $prefix_slug || empty( $id ) ) ? '' : '_' ) . $id, $load_options ); } /* Security ------------------------------------------------------------------------------------------------------------------*/ private static function _encrypt( $str ) { if ( is_null( $str ) ) { return null; } /** * The encrypt/decrypt functions are used to protect * the user from messing up with some of the sensitive * data stored for the module as a JSON in the database. * * I used the same suggested hack by the theme review team. * For more details, look at the function `Base64UrlDecode()` * in `./sdk/FreemiusBase.php`. * * @todo Remove this hack once the base64 error is removed from the Theme Check. * * @author Vova Feldman (@svovaf) * @since 1.2.2 */ $fn = 'base64' . '_encode'; return $fn( $str ); } static function _decrypt( $str ) { if ( is_null( $str ) ) { return null; } /** * The encrypt/decrypt functions are used to protect * the user from messing up with some of the sensitive * data stored for the module as a JSON in the database. * * I used the same suggested hack by the theme review team. * For more details, look at the function `Base64UrlDecode()` * in `./sdk/FreemiusBase.php`. * * @todo Remove this hack once the base64 error is removed from the Theme Check. * * @author Vova Feldman (@svovaf) * @since 1.2.2 */ $fn = 'base64' . '_decode'; return $fn( $str ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param FS_Entity $entity * * @return FS_Entity Return an encrypted clone entity. */ private static function _encrypt_entity( FS_Entity $entity ) { $clone = clone $entity; $props = get_object_vars( $entity ); foreach ( $props as $key => $val ) { $clone->{$key} = self::_encrypt( $val ); } return $clone; } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param FS_Entity $entity * * @return FS_Entity Return an decrypted clone entity. */ private static function decrypt_entity( FS_Entity $entity ) { $clone = clone $entity; $props = get_object_vars( $entity ); foreach ( $props as $key => $val ) { $clone->{$key} = self::_decrypt( $val ); } return $clone; } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $email * * @return FS_User|false */ static function _get_user_by_email( $email ) { self::$_static_logger->entrance(); $email = trim( strtolower( $email ) ); $users = self::get_all_users(); if ( is_array( $users ) ) { foreach ( $users as $user ) { if ( $email === trim( strtolower( $user->email ) ) ) { return $user; } } } return false; } #---------------------------------------------------------------------------------- #region Account (Loading, Updates & Activation) #---------------------------------------------------------------------------------- /*** * Load account information (user + site). * * @author Vova Feldman (@svovaf) * @since 1.0.1 */ private function _load_account() { $this->_logger->entrance(); $this->do_action( 'before_account_load' ); $users = self::get_all_users(); $plans = self::get_all_plans( $this->_module_type ); if ( $this->_logger->is_on() && is_admin() ) { $this->_logger->log( 'users = ' . var_export( $users, true ) ); $this->_logger->log( 'plans = ' . var_export( $plans, true ) ); } $site = fs_is_network_admin() ? $this->get_network_install() : $this->get_install_by_blog_id(); if ( fs_is_network_admin() && $this->is_network_active() && ! is_object( $site ) && FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ) { $first_install = $this->find_first_install(); if ( is_null( $first_install ) ) { unset( $this->_storage->network_install_blog_id ); } else { $site = $first_install['install']; $this->_storage->network_install_blog_id = $first_install['blog_id']; } } if ( is_object( $site ) && is_numeric( $site->id ) && is_numeric( $site->user_id ) && FS_Plugin_Plan::is_valid_id( $site->plan_id ) ) { // Load site. $this->_site = $site; } $user = null; if ( fs_is_network_admin() && $this->_is_network_active ) { $user = $this->get_network_user(); } if ( is_object( $user ) ) { $this->_user = clone $user; } else if ( $this->_site ) { $user = self::_get_user_by_id( $this->_site->user_id ); if ( ! is_object( $user ) && FS_User::is_valid_id( $this->_storage->prev_user_id ) ) { /** * Try to load the previous owner. This recovery is used for the following use-case: * 1. Opt-in * 2. Cloning site1 to site2 * 3. Ownership switch in site1 (same applies for site2) * 4. Install data sync on site2 * 5. Now site2's install is associated with the new owner which does not exists locally. */ $user = self::_get_user_by_id( $this->_storage->prev_user_id ); } if ( ! is_object( $user ) ) { /** * This is a special fault tolerance mechanism to handle a scenario that the user data is missing. */ 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 ) ? clone $user : null; } if ( is_object( $this->_user ) ) { // Load licenses. $this->_licenses = $this->get_user_licenses( $this->_user->id ); } 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() ) { // If stored install version is different than current installed plugin version, // then update plugin version event. $this->update_plugin_version_event(); } } if ( true === $this->_storage->require_license_activation && ! fs_request_get_bool( 'require_license', true ) ) { $this->_storage->require_license_activation = false; } 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(); } } /** * Special user recovery mechanism. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number|null $site_user_id * * @return \FS_User|mixed */ private function sync_user_by_current_install( $site_user_id = null ) { $site_user_id = FS_Site::is_valid_id( $site_user_id ) ? $site_user_id : $this->_site->user_id; $api = $this->get_api_site_scope(); $uid = $this->get_anonymous_id(); $request_path = "/users/{$site_user_id}.json?uid={$uid}"; $result = $api->get( $request_path, false, WP_FS__TIME_10_MIN_IN_SEC ); if ( $this->is_api_result_entity( $result ) ) { $user = new FS_User( $result ); $this->_user = $user; $this->_store_user(); return $user; } $error_code = FS_Api::get_error_code( $result ); if ( in_array( $error_code, array( 'invalid_unique_id', 'user_cannot_be_recovered' ) ) ) { /** * Those API errors will continue coming and are not recoverable with the * current site's data. Therefore, extend the API call's cached result to 7 days. */ $api->update_cache_expiration( $request_path, WP_FS__TIME_WEEK_IN_SEC ); } return $result; } /** * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param FS_User $user * @param FS_Site $site * @param bool|array $plans */ private function _set_account( FS_User $user, FS_Site $site, $plans = false ) { $site->user_id = $user->id; $this->_site = $site; $this->_user = $user; if ( false !== $plans ) { $this->_plans = $plans; } $this->send_install_update(); $this->_store_account(); } /** * Get a sanitized array with the WordPress version, SDK version, and PHP version. * Each version is trimmed after the 16th char. * * @author Vova Feldman (@svovaf) * @since 2.2.1 * * @return array */ private function get_versions() { $versions = array(); $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 ) { $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 * * @return bool */ function has_beta_update() { return ( ! empty( $this->_storage->beta_data ) && ( true === $this->_storage->beta_data['is_beta'] ) && version_compare( $this->_storage->beta_data['version'], $this->get_plugin_version(), '>' ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @return bool */ function is_beta() { return ( ! empty( $this->_storage->beta_data ) && ( true === $this->_storage->beta_data['is_beta'] ) && ( $this->get_plugin_version() === $this->_storage->beta_data['version'] ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7.4 * * @param array $override_with * @param bool|int|null $network_level_or_blog_id If true, return params for network level opt-in. If integer, get params for specified blog in the network. * * @return array */ function get_opt_in_params( $override_with = array(), $network_level_or_blog_id = null ) { $this->_logger->entrance(); $current_user = self::_get_current_wp_user(); $activation_action = $this->get_unique_affix() . '_activate_new'; $return_url = $this->is_anonymous() ? // If skipped already, then return to the account page. $this->get_account_url( $activation_action, array(), false ) : // Return to the module's main page. $this->get_after_activation_url( 'after_connect_url', array( 'fs_action' => $activation_action ) ); $versions = $this->get_versions(); $params = array_merge( $versions, array( '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_localhost' => WP_FS__IS_LOCALHOST, ) ); if ( $this->is_addon() ) { $parent_fs = $this->get_parent_instance(); $params['parent_plugin_slug'] = $parent_fs->_slug; $params['parent_plugin_id'] = $parent_fs->get_id(); } if ( true === $network_level_or_blog_id ) { if ( ! isset( $override_with['sites'] ) ) { $params['sites'] = $this->get_sites_for_network_level_optin(); } } else { $site = is_numeric( $network_level_or_blog_id ) ? array( 'blog_id' => $network_level_or_blog_id ) : null; $site = $this->get_site_info( $site ); $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'], ) ); } if ( $this->is_pending_activation() && ! empty( $this->_storage->pending_license_key ) ) { $params['license_key'] = $this->_storage->pending_license_key; } if ( WP_FS__SKIP_EMAIL_ACTIVATION && $this->has_secret_key() ) { // Even though rand() is known for its security issues, // the timestamp adds another layer of protection. // It would be very hard for an attacker to get the secret key form here. // Plus, this should never run in production since the secret should never // be included in the production version. $params['ts'] = WP_FS__SCRIPT_START_TIME; $params['salt'] = md5( uniqid( rand() ) ); $params['secure'] = md5( $params['ts'] . $params['salt'] . $this->get_secret_key() ); } if ( is_multisite() && function_exists( 'get_network' ) ) { $params['network_uid'] = $this->get_anonymous_network_id(); } return array_merge( $params, $override_with ); } /** * 1. If successful opt-in or pending activation returns the next page that the user should be redirected to. * 2. If there was an API error, return the API result. * * @author Vova Feldman (@svovaf) * @since 1.1.7.4 * * @param string|bool $email * @param string|bool $first * @param string|bool $last * @param string|bool $license_key * @param bool $is_uninstall If "true", this means that the module is currently being uninstalled. * 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 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 */ function opt_in( $email = false, $first = false, $last = false, $license_key = false, $is_uninstall = false, $trial_plan_id = false, $is_disconnected = false, $is_marketing_allowed = null, $sites = array(), $redirect = true ) { $this->_logger->entrance(); if ( false === $email ) { $current_user = self::_get_current_wp_user(); $email = $current_user->user_email; } /** * @since 1.2.1 If activating with license key, ignore the context-user * since the user will be automatically loaded from the license. */ if ( empty( $license_key ) ) { // Clean up pending license if opt-ing in again. $this->_storage->remove( 'pending_license_key' ); if ( ! $is_uninstall ) { $fs_user = Freemius::_get_user_by_email( $email ); if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) { return $this->install_with_user( $fs_user, false, $trial_plan_id, $redirect, true, $sites ); } } } $user_info = array(); if ( ! empty( $email ) ) { $user_info['user_email'] = $email; } if ( ! empty( $first ) ) { $user_info['user_firstname'] = $first; } if ( ! empty( $last ) ) { $user_info['user_lastname'] = $last; } if ( ! empty( $sites ) ) { $is_network = true; $user_info['sites'] = $sites; } else { $is_network = false; } $params = $this->get_opt_in_params( $user_info, $is_network ); $filtered_license_key = false; if ( is_string( $license_key ) ) { $filtered_license_key = $this->apply_filters( 'license_key', $license_key ); $params['license_key'] = $filtered_license_key; } else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) { $params['trial_plan_id'] = $trial_plan_id; } if ( $is_uninstall ) { $params['uninstall_params'] = array( 'reason_id' => $this->_storage->uninstall_reason->id, 'reason_info' => $this->_storage->uninstall_reason->info ); } if ( isset( $params['license_key'] ) ) { $fs_user = Freemius::_get_user_by_email( $email ); if ( is_object( $fs_user ) ) { /** * If opting in with a context license and the context WP Admin user already opted in * before from the current site, add the user context security params to avoid the * unnecessary email activation when the context license is owned by the same context user. * * @author Leo Fajardo (@leorw) * @since 1.2.3 */ $params = array_merge( $params, FS_Security::instance()->get_context_params( $fs_user, false, 'install_with_existing_user' ) ); } } if ( is_bool( $is_marketing_allowed ) ) { $params['is_marketing_allowed'] = $is_marketing_allowed; } $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' => 60, ); $url = $this->add_show_pending( WP_FS__ADDRESS . '/action/service/user/install/' ); $response = self::safe_remote_post( $url, $request ); if ( is_wp_error( $response ) ) { /** * @var WP_Error $response */ $result = new stdClass(); $error_code = $response->get_error_code(); $error_type = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $error_code ) ) ); $result->error = (object) array( 'type' => $error_type, 'message' => $response->get_error_message(), 'code' => $error_code, 'http' => 402 ); $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; } /** * When json_decode() executed on PHP 5.2 with an invalid JSON, it will throw a PHP warning. Unfortunately, the new Theme Check doesn't allow PHP silencing and the theme review team isn't open to change that, therefore, instead of using `@json_decode()` we had to use the method without the `@` directive. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * @link https://themes.trac.wordpress.org/ticket/46134#comment:5 * @link https://themes.trac.wordpress.org/ticket/46134#comment:9 * @link https://themes.trac.wordpress.org/ticket/46134#comment:12 * @link https://themes.trac.wordpress.org/ticket/46134#comment:14 */ $decoded = is_string( $response['body'] ) ? json_decode( $response['body'] ) : null; if ( empty( $decoded ) ) { return false; } if ( ! $this->is_api_result_object( $decoded ) ) { if ( ! empty( $params['license_key'] ) ) { // Pass the fully entered license key to the failure handler. $params['license_key'] = $license_key; } return $is_uninstall ? $decoded : $this->apply_filters( 'after_install_failure', $decoded, $params ); } else if ( isset( $decoded->pending_activation ) && $decoded->pending_activation ) { if ( $is_network ) { $site_ids = array(); foreach ( $sites as $site ) { $site_ids[] = $site['blog_id']; } /** * Store the sites so that they can be installed once the user has clicked on the activation link * in the email. * * @author Leo Fajardo (@leorw) */ $this->_storage->pending_sites_info = array( 'blog_ids' => $site_ids, 'license_key' => $license_key, 'trial_plan_id' => $trial_plan_id ); } // Pending activation, add message. return $this->set_pending_confirmation( ( isset( $decoded->email ) ? $decoded->email : true ), false, $filtered_license_key, ! 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( $decoded->user_id, $decoded->user_public_key, $decoded->user_secret_key, ( isset( $decoded->is_marketing_allowed ) && ! is_null( $decoded->is_marketing_allowed ) ? $decoded->is_marketing_allowed : null ), ( 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, false ); } else if ( is_array( $decoded->installs ) ) { return $this->install_many_with_new_user( $decoded->user_id, $decoded->user_public_key, $decoded->user_secret_key, ( isset( $decoded->is_marketing_allowed ) && ! is_null( $decoded->is_marketing_allowed ) ? $decoded->is_marketing_allowed : null ), ( 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 ); } return $decoded; } /** * Set user and site identities. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param FS_User $user * @param FS_Site $site * @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. * * @return string If redirect is `false`, returns the next page the user should be redirected to. */ function setup_account( FS_User $user, FS_Site $site, $redirect = true, $auto_install = false ) { return $this->setup_network_account( $user, array( $site ), $redirect, $auto_install, false ); } /** * Set user and site identities. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param FS_User $user * @param FS_Site[] $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. * @param bool $is_network_level_opt_in * * @return string If redirect is `false`, returns the next page the user should be redirected to. */ function setup_network_account( FS_User $user, array $installs, $redirect = true, $auto_install = false, $is_network_level_opt_in = true ) { $first_install = $installs[0]; $this->_user = $user; $this->_site = $first_install; $this->_sync_plans(); if ( $this->_storage->handle_gdpr_admin_notice && $this->should_handle_gdpr_admin_notice() && FS_GDPR_Manager::instance()->should_show_opt_in_notice() ) { /** * Clear user lock after an opt-in. */ require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; FS_User_Lock::instance()->unlock(); } if ( 1 < count( $installs ) ) { // 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(); $this->handle_account_connection( $installs, ( ! $this->_is_network_active || ! $is_network_level_opt_in ) ); if ( is_numeric( $first_install->license_id ) ) { $this->set_license( $this->_get_license_by_id( $first_install->license_id ) ); } $this->_admin_notices->remove_sticky( 'connect_account' ); if ( $this->is_pending_activation() || ! $this->has_settings_menu() ) { $this->clear_pending_activation_mode(); if ( ! $this->is_paying_or_trial() ) { $this->_admin_notices->add_sticky( sprintf( $this->get_text_inline( '%s opt-in was successfully completed.', 'plugin-x-activation-message' ), '' . $this->get_plugin_name() . '' ), 'activation_complete' ); } } if ( $this->is_paying_or_trial() ) { if ( ! $this->is_premium() || ! $this->has_premium_version() || ! $this->has_settings_menu() ) { if ( $this->is_paying() ) { $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() ), 'plan_upgraded' ); } else { $trial_plan = $this->get_trial_plan(); $this->add_complete_upgrade_instructions_notice( sprintf( $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), '' . $this->get_plugin_name() . '' ), 'trial_started', $trial_plan->title ); } } $this->_admin_notices->remove_sticky( array( 'trial_promotion', ) ); } $plugin_id = fs_request_get( 'plugin_id', false ); // Store activation time ONLY for plugins & themes (not add-ons). if ( ! is_numeric( $plugin_id ) || ( $plugin_id == $this->_plugin->id ) ) { if ( empty( $this->_storage->activation_timestamp ) ) { $this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME; } } $next_page = ''; $extra = array(); if ( $auto_install ) { $extra['auto_install'] = 'true'; } if ( is_numeric( $plugin_id ) ) { /** * @author Leo Fajardo (@leorw) * @since 1.2.1.6 * * Also sync the license after an anonymous user subscribes. */ if ( $this->is_anonymous() || $plugin_id != $this->_plugin->id ) { // Add-on was installed - sync license right after install. $next_page = $this->_get_sync_license_url( $plugin_id, true, $extra ); } } else { /** * @author Vova Feldman (@svovaf) * @since 1.1.9 If site installed with a valid license, sync license. */ if ( $this->is_paying() ) { $this->_sync_plugin_license( true, // Installs data is already synced in the beginning of this method directly or via _set_account(). false ); } // Reload the page with the keys. $next_page = $this->is_anonymous() ? // If user previously skipped, redirect to account page. $this->get_account_url( false, $extra ) : $this->get_after_activation_url( 'after_connect_url', array(), $is_network_level_opt_in ); } if ( ! empty( $next_page ) && $redirect ) { fs_redirect( $next_page ); } return $next_page; } /** * Install plugin with new user information after approval. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ function _install_with_new_user() { $this->_logger->entrance(); if ( $this->is_registered() ) { 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() ) { // check_admin_referer( $this->_slug . '_activate_new' ); if ( fs_request_has( 'user_secret_key' ) ) { if ( fs_is_network_admin() && isset( $this->_storage->pending_sites_info ) ) { $pending_sites_info = $this->_storage->pending_sites_info; $this->install_many_pending_with_user( fs_request_get( 'user_id' ), 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'] ); } else { $this->install_with_new_user( fs_request_get( 'user_id' ), 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_raw( 'install_public_key' ), fs_request_get_raw( 'install_secret_key' ), true, fs_request_get_bool( 'auto_install' ) ); } } 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' ) ); } } } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $id * @param string $public_key * @param string $secret_key * * @return \FS_User */ private function setup_user( $id, $public_key, $secret_key ) { $user = self::_get_user_by_id( $id ); if ( is_object( $user ) ) { $this->_user = $user; } else { $user = new FS_User(); $user->id = $id; $user->public_key = $public_key; $user->secret_key = $secret_key; $this->_user = $user; $user_result = $this->get_api_user_scope()->get(); $user = new FS_User( $user_result ); $this->_user = $user; $this->_store_user(); } return $user; } /** * Install plugin with new user. * * @author Vova Feldman (@svovaf) * @since 1.1.7.4 * * @param number $user_id * @param string $user_public_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 * @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. * * @return string If redirect is `false`, returns the next page the user should be redirected to. */ private function install_with_new_user( $user_id, $user_public_key, $user_secret_key, $is_marketing_allowed, $is_extensions_tracking_allowed, $is_diagnostic_tracking_allowed, $install_id, $install_public_key, $install_secret_key, $redirect = true, $auto_install = false ) { /** * This method is also executed after opting in with a license key since the * license can be potentially associated with a different owner. * * @since 2.0.0 */ $user = self::_get_user_by_id( $user_id ); if ( ! is_object( $user ) ) { $user = new FS_User(); $user->id = $user_id; $user->public_key = $user_public_key; $user->secret_key = $user_secret_key; $this->_user = $user; $user_result = $this->get_api_user_scope()->get(); $user = new FS_User( $user_result ); } $this->_user = $user; $site = new FS_Site(); $site->id = $install_id; $site->public_key = $install_public_key; $site->secret_key = $install_secret_key; $this->_site = $site; $site_result = $this->get_api_site_scope( true )->get(); $site = new FS_Site( $site_result ); $this->_site = $site; if ( ! is_null( $is_marketing_allowed ) ) { $this->disable_opt_in_notice_and_lock_user(); } 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, $this->_site, $redirect, $auto_install ); } /** * Install plugin with user. * * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param number $user_id * @param string $user_public_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 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. */ private function install_many_pending_with_user( $user_id, $user_public_key, $user_secret_key, $is_marketing_allowed, $is_extensions_tracking_allowed, $is_diagnostic_tracking_allowed, $site_ids, $license_key = false, $trial_plan_id = false, $redirect = true ) { $user = $this->setup_user( $user_id, $user_public_key, $user_secret_key ); if ( ! is_null( $is_marketing_allowed ) ) { $this->disable_opt_in_notice_and_lock_user(); } 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 ) { $sites[] = $this->get_site_info( array( 'blog_id' => $site_id ) ); } $this->install_with_user( $user, $license_key, $trial_plan_id, $redirect, true, $sites ); } /** * Multi-site install with a new user. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $user_id * @param string $user_public_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 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. * * @return string If redirect is `false`, returns the next page the user should be redirected to. */ private function install_many_with_new_user( $user_id, $user_public_key, $user_secret_key, $is_marketing_allowed, $is_extensions_tracking_allowed, $is_diagnostic_tracking_allowed, array $installs, $redirect = true, $auto_install = false ) { $this->setup_user( $user_id, $user_public_key, $user_secret_key ); if ( ! is_null( $is_marketing_allowed ) ) { $this->disable_opt_in_notice_and_lock_user(); } 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(); foreach ( $installs as $install ) { $install_ids[] = $install->id; } $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, $items_per_request ) ) ); if ( ! $this->is_api_result_object( $result, 'installs' ) ) { // @todo Handle API error. } $installs = array_merge( $installs, $result->installs ); $left -= $items_per_request; $offset += $items_per_request; } foreach ( $installs as &$install ) { $install = new FS_Site( $install ); } return $this->setup_network_account( $this->_user, $installs, $redirect, $auto_install ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7.4 * * @param string|bool $email * @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. */ private function set_pending_confirmation( $email = false, $redirect = true, $license_key = false, $is_pending_trial = false, $is_suspicious_email = false, $has_upgrade_context = false, $support_email_address = false ) { $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 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( $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, $is_suspicious_email, $has_upgrade_context, $support_email_address ); } if ( ! empty( $license_key ) ) { $this->_storage->pending_license_key = $license_key; } // Remove the opt-in sticky notice. $this->_admin_notices->remove_sticky( array( 'connect_account', 'trial_promotion', ) ); $next_page = $this->get_after_activation_url( 'after_pending_connect_url' ); if ( $redirect ) { // Reload the page with a pending activation message. fs_redirect( $next_page ); } return $next_page; } /** * Install plugin with current logged WP user info. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ function _install_with_current_user() { $this->_logger->entrance(); if ( $this->is_registered() ) { return; } if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) && fs_request_is_post() ) { 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_raw( 'license_secret_key' ); 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 ); } } /** * @author Vova Feldman (@svovaf) * @since 1.1.7.4 * * @param string|bool $license_key * @param number|bool $trial_plan_id * @param array $sites Since 2.0.0 * @param bool $redirect * * @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. */ function install_with_current_user( $license_key = false, $trial_plan_id = false, $sites = array(), $redirect = true ) { // Get current logged WP user. $current_user = self::_get_current_wp_user(); // Find the relevant FS user by the email. $user = self::_get_user_by_email( $current_user->user_email ); return $this->install_with_user( $user, $license_key, $trial_plan_id, $redirect, true, $sites ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_User $user * @param string|bool $license_key * @param number|bool $trial_plan_id * @param bool $redirect * @param bool $setup_account Since 2.0.0. When set to FALSE, executes a light installation without setting up the account as if it's the first opt-in. * @param array $sites Since 2.0.0. If not empty, should be a collection of site details for the bulk install API request. * * @return \FS_Site|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. If $setup_account is set to `false`, return the newly created install. */ function install_with_user( FS_User $user, $license_key = false, $trial_plan_id = false, $redirect = true, $setup_account = true, $sites = array() ) { // We have to set the user before getting user scope API handler. $this->_user = $user; // Install the plugin. $result = $this->create_installs_with_user( $user, $license_key, $trial_plan_id, $sites, $redirect ); if ( ! $this->is_api_result_entity( $result ) && ! $this->is_api_result_object( $result, 'installs' ) ) { // @todo Handler potential API error of the $result } if ( empty( $sites ) ) { $site = new FS_Site( $result ); $this->_site = $site; if ( ! $setup_account ) { $this->_store_site(); $this->sync_plan_if_not_exist( $site->plan_id ); if ( ! empty( $license_key ) && FS_Plugin_License::is_valid_id( $site->license_id ) ) { $this->sync_license_if_not_exist( $site->license_id, $license_key ); } $this->_admin_notices->remove_sticky( 'connect_account', false ); return $site; } return $this->setup_account( $this->_user, $this->_site, $redirect ); } else { $installs = array(); foreach ( $result->installs as $install ) { $installs[] = new FS_Site( $install ); } return $this->setup_network_account( $user, $installs, $redirect ); } } /** * Initiate an API request to create a collection of installs. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_User $user * @param bool $license_key * @param bool $trial_plan_id * @param array $sites * @param bool $redirect * @param bool $silent * * @return object|mixed */ private function create_installs_with_user( FS_User $user, $license_key = false, $trial_plan_id = false, $sites = array(), $redirect = false, $silent = false ) { $extra_install_params = array( 'uid' => $this->get_anonymous_id(), 'is_disconnected' => false, ); if ( ! empty( $license_key ) ) { $extra_install_params['license_key'] = $this->apply_filters( 'license_key', $license_key ); if ( $silent ) { $extra_install_params['ignore_license_owner'] = true; } } else if ( FS_Plugin_Plan::is_valid_id( $trial_plan_id ) ) { $extra_install_params['trial_plan_id'] = $trial_plan_id; } if ( ! empty( $sites ) ) { $extra_install_params['sites'] = $sites; } $args = $this->get_install_data_for_api( $extra_install_params, false, false ); // Install the plugin. $result = $this->get_api_user_scope_by_user( $user )->call( "/plugins/{$this->get_id()}/installs.json", 'post', $args ); if ( ! $this->is_api_result_entity( $result ) && ! $this->is_api_result_object( $result, 'installs' ) ) { if ( ! empty( $args['license_key'] ) ) { // Pass the fully entered license key to the failure handler. $args['license_key'] = $license_key; } $result = $this->apply_filters( 'after_install_failure', $result, $args ); if ( ! $silent ) { $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $result->error->message . '', $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', 'error' ); } if ( $redirect ) { /** * We set the user before getting the user scope API handler, so the user became temporarily * registered (`is_registered() = true`). Since the API returned an error and we will redirect, * we have to set the user to `null`, otherwise, the user will be redirected to the wrong * activation page based on the return value of `is_registered()`. In addition, in case the * context plugin doesn't have a settings menu and the default page is the `Plugins` page, * misleading plugin activation errors will be shown on the `Plugins` page. * * @author Leo Fajardo (@leorw) */ $this->_user = null; fs_redirect( $this->get_activation_url( array( 'error' => $result->error->message ) ) ); } } return $result; } /** * Tries to activate add-on account based on parent plugin info. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param Freemius $parent_fs * @param bool|int|null $network_level_or_blog_id True for network level opt-in and integer for opt-in for specified blog in the network. * @param FS_Plugin_License $bundle_license Since 2.4.0. If provided, this license will be activated for the add-on. */ private function _activate_addon_account( Freemius $parent_fs, $network_level_or_blog_id = null, FS_Plugin_License $bundle_license = null ) { if ( $this->is_registered() ) { // Already activated. 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. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $uid_param_to_override = ( true === $network_level_or_blog_id ) ? array() : array( 'uid' => $this->get_anonymous_id() ); $params = $this->get_install_data_for_api( $uid_param_to_override, false, false, /** * Do not include the data for the current blog if network-level opt-in since the call to `get_sites_for_network_level_optin` * already includes the data for it. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ ( true !== $network_level_or_blog_id ) ); if ( true === $network_level_or_blog_id ) { $params['sites'] = $this->get_sites_for_network_level_optin(); if ( empty( $params['sites'] ) ) { return; } } if ( is_object( $bundle_license ) ) { $params['license_key'] = $bundle_license->secret_key; } // Activate add-on with parent plugin credentials. $result = $parent_fs->get_api_site_scope()->call( "/addons/{$this->_plugin->id}/installs.json", 'post', $params ); if ( ! $this->is_api_result_object( $result, 'installs' ) ) { if ( is_object( $bundle_license ) ) { /** * When a license object is provided, it's an attempt by the SDK to activate a bundle license and not a user-initiated action, therefore, do not show any admin notice to avoid confusion (e.g.: the notice will show up just above the opt-in link). If the license activation fails, the admin will see an opt-in link instead. * * @author Leo Fajardo (@leorw) * @since 2.4.0 */ } else { $error_message = FS_Api::is_api_error_object( $result ) ? $result->error->message : $this->get_text_inline( 'An unknown error has occurred.', 'unknown-error' ); $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $error_message . '', $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', 'error' ); } return; } $addon_installs = $result->installs; foreach ( $addon_installs as $key => $addon_install ) { $addon_installs[ $key ] = new FS_Site( $addon_install ); } $first_install = $addon_installs[0]; // Get user information based on parent's plugin. $user = $parent_fs->get_user(); // First of all, set site and user info - otherwise we won't // be able to invoke API calls. $this->_site = $first_install; $this->_user = $user; // Sync add-on plans. $this->_sync_plans(); $this->handle_account_connection( $addon_installs, ! fs_is_network_admin() ); // Get site's current plan. //$this->_site->plan = $this->_get_plan_by_id( $this->_site->plan->id ); // Sync licenses. $this->_sync_licenses(); if ( ! fs_is_network_admin() ) { // Try to activate premium license. $this->_activate_license( true, $bundle_license ); if ( is_object( $bundle_license ) ) { $this->maybe_activate_bundle_license( $bundle_license ); } } else { if ( is_object( $bundle_license ) ) { $premium_license = $bundle_license; } else { $license_id = fs_request_get( 'license_id' ); if ( is_object( $this->_site ) && FS_Plugin_License::is_valid_id( $license_id ) && $license_id == $this->_site->license_id ) { // License is already activated. return; } $premium_license = FS_Plugin_License::is_valid_id( $license_id ) ? $this->_get_license_by_id( $license_id ) : $this->_get_available_premium_license(); } if ( is_object( $premium_license ) ) { $this->maybe_network_activate_addon_license( $premium_license ); } } } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param FS_Site[] $installs * @param bool $is_site_level */ private function handle_account_connection( $installs, $is_site_level ) { $first_install = $installs[0]; if ( $is_site_level ) { $this->_set_account( $this->_user, $first_install ); $this->do_action( 'after_account_connection', $this->_user, $first_install ); } else { $this->_store_user(); // Map site addresses to their blog IDs. $address_to_blog_map = $this->get_address_to_blog_map(); $first_blog_id = null; $blog_2_install_map = array(); foreach ( $installs as $install ) { $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); $blog_id = $address_to_blog_map[ $address ]; $this->_store_site( true, $blog_id, $install ); if ( is_null( $first_blog_id ) ) { $first_blog_id = $blog_id; } $blog_2_install_map[ $blog_id ] = $install; } if ( ! FS_User::is_valid_id( $this->_storage->network_user_id ) || ! is_object( self::_get_user_by_id( $this->_storage->network_user_id ) ) ) { // Store network user. $this->_storage->network_user_id = $this->_user->id; } if ( ! FS_Site::is_valid_id( $this->_storage->network_install_blog_id ) ) { $this->_storage->network_install_blog_id = $first_blog_id; } if ( count( $installs ) === count( $address_to_blog_map ) ) { // Super admin opted in for all sites in the network. $this->_storage->is_network_connected = true; } $this->_store_licenses( false ); self::$_accounts->store(); // Don't sync the installs data on network upgrade if ( ! $this->network_upgrade_mode_completed() ) { $this->send_installs_update(); } $current_blog = get_current_blog_id(); foreach ( $blog_2_install_map as $blog_id => $install ) { $this->switch_to_blog( $blog_id ); $this->do_action( 'after_account_connection', $this->_user, $install ); } // 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 ); } } /** * Tries to activate parent account based on add-on's info. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @param Freemius $parent_fs */ private function activate_parent_account( Freemius $parent_fs ) { if ( ! $this->is_addon() ) { // This is not an add-on. return; } if ( $parent_fs->is_registered() ) { // Already activated. return; } // Activate parent with add-on's user credentials. $parent_install = $this->get_api_user_scope()->call( "/plugins/{$parent_fs->_plugin->id}/installs.json", 'post', $parent_fs->get_install_data_for_api( array( 'uid' => $parent_fs->get_anonymous_id(), ), false, false ) ); if ( isset( $parent_install->error ) ) { $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' . $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '' . $parent_install->error->message . '', $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', 'error' ); return; } $parent_fs->_admin_notices->remove_sticky( 'connect_account' ); if ( $parent_fs->is_pending_activation() ) { $parent_fs->clear_pending_activation_mode(); } // Get user information based on parent's plugin. $user = $this->get_user(); // First of all, set site info - otherwise we won't // be able to invoke API calls. $parent_fs->_site = new FS_Site( $parent_install ); $parent_fs->_user = $user; // Sync add-on plans. $parent_fs->_sync_plans(); $parent_fs->update_license_required_permissions_if_anonymous(); $parent_fs->_set_account( $user, $parent_fs->_site ); } #endregion #---------------------------------------------------------------------------------- #region Admin Menu Items #---------------------------------------------------------------------------------- private $_menu_items = array(); /** * @author Vova Feldman (@svovaf) * @since 1.2.1.8 * * @return array */ function get_menu_items() { return $this->_menu_items; } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return string */ function get_menu_slug() { return $this->_menu->get_slug(); } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 */ function _prepare_admin_menu() { // if ( ! $this->is_on() ) { // 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 * site level settings. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ $should_hide_site_admin_settings = ( $this->_is_network_active && ! fs_is_network_admin() && ! $this->is_delegated_connection() && ! $this->is_anonymous() && ! $this->is_registered() ); $should_hide_site_admin_settings = $this->apply_filters( 'should_hide_site_admin_settings_on_network_activation_mode', $should_hide_site_admin_settings ); 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 ); } else { $this->do_action( fs_is_network_admin() ? 'before_network_admin_menu_init' : 'before_admin_menu_init' ); $this->add_menu_action(); $this->add_network_menu_when_missing(); $this->add_submenu_items(); } } /** * Admin dashboard menu items modifications. * * NOTE: admin_menu action executed before admin_init. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * */ private function add_menu_action() { if ( $this->is_activation_mode() ) { if ( $this->show_opt_in_on_setting_page() ) { $this->override_plugin_menu_with_activation(); } else { /** * Handle theme opt-in when the opt-in form shows as a dialog box in the themes page. */ if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) { add_action( 'load-themes.php', array( &$this, '_install_with_current_user' ) ); } else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) || fs_request_get_bool( 'pending_activation' ) ) { add_action( 'load-themes.php', array( &$this, '_install_with_new_user' ) ); } } } else { if ( ! $this->is_registered() ) { // If not registered try to install user. if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) { $this->_install_with_new_user(); } } else if ( fs_request_is_action( 'sync_user' ) && ( ! $this->has_settings_menu() || $this->show_opt_in_on_themes_page() ) ) { $this->_handle_account_user_sync(); } } } /** * @author Vova Feldman (@svovaf) * @since 1.0.1 */ function _redirect_on_clicked_menu_link() { $this->_logger->entrance(); $page = fs_request_get('page'); $page = is_string($page) ? strtolower($page) : ''; $this->_logger->log( 'page = ' . $page ); foreach ( $this->_menu_items as $priority => $items ) { foreach ( $items as $item ) { if ( isset( $item['url'] ) ) { if ( $page === $this->_menu->get_slug( strtolower( $item['menu_slug'] ) ) ) { $this->_logger->log( 'Redirecting to ' . $item['url'] ); fs_redirect( $item['url'] ); } } } } } /** * Remove plugin's all admin menu items & pages, and replace with activation page. * * @author Vova Feldman (@svovaf) * @since 1.0.1 */ private function override_plugin_menu_with_activation() { $this->_logger->entrance(); $hook = false; if ( ! $this->has_settings_menu() ) { // Add the opt-in page without a menu item. $hook = FS_Admin_Menu_Manager::add_subpage( '', $this->get_plugin_name(), $this->get_plugin_name(), 'manage_options', $this->_slug, array( &$this, '_connect_page_render' ) ); } else if ( $this->_menu->is_top_level() ) { if ( $this->_menu->is_override_exact() ) { // Make sure the current page is matching the activation page. if ( ! $this->is_matching_url( $this->get_activation_url() ) ) { return; } } $hook = $this->_menu->override_menu_item( array( &$this, '_connect_page_render' ) ); if ( false === $hook ) { // Create new menu item just for the opt-in. $hook = FS_Admin_Menu_Manager::add_page( $this->get_plugin_name(), $this->get_plugin_name(), 'manage_options', $this->_menu->get_slug(), array( &$this, '_connect_page_render' ) ); } } else { $menus = array( $this->_menu->get_parent_slug() ); if ( $this->_menu->is_override_exact() ) { // Make sure the current page is matching the activation page. if ( ! $this->is_matching_url( $this->get_activation_url() ) ) { return; } } foreach ( $menus as $parent_slug ) { $hook = $this->_menu->override_submenu_action( $parent_slug, $this->_menu->get_raw_slug(), array( &$this, '_connect_page_render' ) ); if ( false !== $hook ) { // Found plugin's submenu item. break; } } } if ( $this->is_activation_page() ) { // Clean admin page from distracting content. self::_clean_admin_content_section(); } if ( false !== $hook ) { if ( fs_request_is_action( $this->get_unique_affix() . '_activate_existing' ) ) { $this->_install_with_current_user(); } else if ( fs_request_is_action( $this->get_unique_affix() . '_activate_new' ) ) { $this->_install_with_new_user(); } } } /** * If a plugin was network activated and connected but don't have a network * level settings, then add an artificial menu item for the Account and other * Freemius settings. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ private function add_network_menu_when_missing() { $this->_logger->entrance(); if ( ! $this->_is_network_active ) { // Plugin wasn't activated on the network level. return; } if ( ! fs_is_network_admin() ) { // The context is not the network admin. return; } if ( $this->_menu->has_network_menu() ) { // Plugin already has a network level menu. return; } if ( $this->is_network_activation_mode() ) { /** * Do not add during activation mode, otherwise, there will be duplicate menus while the opt-in * screen is being shown. * * @author Leo Fajardo (@leorw) */ return; } if ( ! WP_FS__SHOW_NETWORK_EVEN_WHEN_DELEGATED ) { if ( $this->is_network_delegated_connection() ) { // Super-admin delegated the connection to the site admins. return; } } if ( ! $this->_menu->has_menu() || $this->_menu->is_top_level() ) { if ( $this->_menu->has_menu() || ! $this->is_addon() || $this->is_activation_mode() ) { $this->_dynamically_added_top_level_page_hook_name = $this->_menu->add_page_and_update( $this->get_plugin_name(), $this->get_plugin_name(), 'manage_options', $this->_menu->has_menu() ? $this->_menu->get_slug() : $this->_slug ); } } else { $this->_menu->add_subpage_and_update( $this->_menu->get_parent_slug(), $this->get_plugin_name(), $this->get_plugin_name(), 'manage_options', $this->_menu->get_slug() ); } } /** * @author Leo Fajardo (@leorw) * @since 1.2.1 * * return string */ function get_top_level_menu_capability() { global $menu; $top_level_menu_slug = $this->get_top_level_menu_slug(); foreach ( $menu as $menu_info ) { /** * The second element in the menu info array is the capability/role that has access to the menu and the * third element is the menu slug. */ if ( $menu_info[2] === $top_level_menu_slug ) { return $menu_info[1]; } } return 'read'; } /** * @author Vova Feldman (@svovaf) * @since 1.0.0 * * @return string */ private function get_top_level_menu_slug() { return ( $this->is_addon() ? $this->get_parent_instance()->_menu->get_top_level_menu_slug() : $this->_menu->get_top_level_menu_slug() ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return string */ function get_pricing_cta_label() { $label = $this->get_text_inline( 'Upgrade', 'upgrade' ); if ( $this->is_in_trial_promotion() && ! $this->is_paying_or_trial() ) { // If running a trial promotion, modify the pricing to load the trial. $label = $this->get_text_inline( 'Start Trial', 'start-trial' ); } else if ( $this->is_paying() ) { $label = $this->get_text_inline( 'Pricing', 'pricing' ); } return $label; } /** * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return bool */ function is_pricing_page_visible() { return ( // Has at least one paid plan. $this->has_paid_plan() && // Didn't ask to hide the pricing page. $this->is_page_visible( 'pricing' ) && // Don't have a valid active license or has more than one plan. ( ! $this->is_paying() || ! $this->is_single_plan( true ) ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param bool $is_activation_mode * * @return bool */ private function should_add_submenu_or_action_links( $is_activation_mode ) { if ( $this->is_addon() ) { // No submenu items or action links for add-ons. return false; } if ( $this->show_opt_in_on_themes_page() ) { if ( ! fs_is_network_admin() ) { // Also add action links or submenu items when running in a free .org theme so the tabs will be visible. return true; } } else if ( $is_activation_mode ) { // Don't show submenu-items/tabs in activation mode, unless it's a wp.org theme. return false; } if ( fs_is_network_admin() ) { /** * Add submenu items or action links to network level when plugin was network activated and the super * admin did NOT delegate the connection of all sites to site admins. */ return ( $this->_is_network_active && ( WP_FS__SHOW_NETWORK_EVEN_WHEN_DELEGATED || ! $this->is_network_delegated_connection() ) ); } return ( ! $this->_is_network_active || $this->is_delegated_connection() ); } /** * Add default Freemius menu items. * * @author Vova Feldman (@svovaf) * @since 1.0.0 * @since 1.2.2.7 Also add submenu items when running in a free .org theme so the tabs will be visible. */ private function add_submenu_items() { $this->_logger->entrance(); $is_activation_mode = $this->is_activation_mode(); $add_submenu_items = $this->should_add_submenu_or_action_links( $is_activation_mode ); if ( $add_submenu_items ) { if ( $this->has_affiliate_program() ) { // Add affiliation page. $this->add_submenu_item( $this->get_text_inline( 'Affiliation', 'affiliation' ), array( &$this, '_affiliation_page_render' ), $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Affiliation', 'affiliation' ), 'manage_options', 'affiliation', 'Freemius::_clean_admin_content_section', WP_FS__DEFAULT_PRIORITY, $this->is_submenu_item_visible( 'affiliation' ) ); } } if ( $add_submenu_items || ( $is_activation_mode && $this->is_only_premium() && $this->is_admin_page( 'account' ) && fs_request_is_action( $this->get_unique_affix() . '_sync_license' ) ) ) { if ( ! WP_FS__DEMO_MODE && $this->is_registered() ) { $show_account = ( $this->is_submenu_item_visible( 'account' ) && /** * @since 1.2.2.7 Don't show the Account for free WP.org themes without any paid plans. */ ( ! $this->is_free_wp_org_theme() || $this->has_paid_plan() ) ); // Add user account page. $this->add_submenu_item( $this->get_text_inline( 'Account', 'account' ), array( &$this, '_account_page_render' ), $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Account', 'account' ), 'manage_options', 'account', array( &$this, '_account_page_load' ), WP_FS__DEFAULT_PRIORITY, ( $add_submenu_items && $show_account ) ); } } if ( $add_submenu_items ) { if (! WP_FS__DEMO_MODE && ! $this->is_whitelabeled() ) { // Add contact page. $this->add_submenu_item( $this->get_text_inline( 'Contact Us', 'contact-us' ), array( &$this, '_contact_page_render' ), $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Contact Us', 'contact-us' ), 'manage_options', 'contact', 'Freemius::_clean_admin_content_section', WP_FS__DEFAULT_PRIORITY, $this->is_submenu_item_visible( 'contact' ) ); } if ( $this->has_addons() ) { $this->add_submenu_item( $this->get_text_inline( 'Add-Ons', 'add-ons' ), array( &$this, '_addons_page_render' ), $this->get_plugin_name() . ' – ' . $this->get_text_inline( 'Add-Ons', 'add-ons' ), 'manage_options', 'addons', array( &$this, '_addons_page_load' ), WP_FS__LOWEST_PRIORITY - 1, $this->is_submenu_item_visible( 'addons' ) ); } } if ( $add_submenu_items || ( $is_activation_mode && $this->is_only_premium() && $this->is_admin_page( 'pricing' ) ) ) { if (! WP_FS__DEMO_MODE && ! $this->is_whitelabeled() ) { $show_pricing = ( $this->is_submenu_item_visible( 'pricing' ) && $this->is_pricing_page_visible() ); $pricing_cta_text = $this->get_pricing_cta_label(); $pricing_class = 'upgrade-mode'; if ( $show_pricing ) { if ( $this->is_in_trial_promotion() && ! $this->is_paying_or_trial() ) { // If running a trial promotion, modify the pricing to load the trial. $pricing_class = 'trial-mode'; } else if ( $this->is_paying() ) { $pricing_class = ''; } } // Add upgrade/pricing page. $this->add_submenu_item( $pricing_cta_text . '  ' . ( is_rtl() ? $this->get_text_x_inline( '←', 'ASCII arrow left icon', 'symbol_arrow-left' ) : $this->get_text_x_inline( '➤', 'ASCII arrow right icon', 'symbol_arrow-right' ) ), array( &$this, '_pricing_page_render' ), $this->get_plugin_name() . ' – ' . $this->get_text_x_inline( 'Pricing', 'noun', 'pricing' ), 'manage_options', 'pricing', 'Freemius::_clean_admin_content_section', WP_FS__LOWEST_PRIORITY, ( $add_submenu_items && $show_pricing ), $pricing_class ); } } if ( ! $is_activation_mode || ( true !== $this->_storage->require_license_activation ) ) { /** * Add the other menu items if there are any when not in activation mode or license activation is not * required (license activation is required for registered or anonymous users after activating the * premium version when the site is not in trial mode or there's no active valid license). * * @author Leo Fajardo (@leorw) * @since 2.2.1 */ if ( 0 < count( $this->_menu_items ) ) { if ( ! $this->_menu->is_top_level() ) { fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); // Append submenu items right after the plugin's submenu item. $this->order_sub_submenu_items(); } else { // Append submenu items. $this->embed_submenu_items(); } } } } /** * Moved the actual submenu item additions to a separated function, * in order to support sub-submenu items when the plugin's settings * only have a submenu and not top-level menu item. * * @author Vova Feldman (@svovaf) * @since 1.1.4 */ private function embed_submenu_items() { $item_template = $this->_menu->is_top_level() ? '%s' : '%s'; $top_level_menu_capability = $this->get_top_level_menu_capability(); ksort( $this->_menu_items ); $is_first_submenu_item = true; foreach ( $this->_menu_items as $priority => $items ) { foreach ( $items as $item ) { $capability = ( ! empty( $item['capability'] ) ? $item['capability'] : $top_level_menu_capability ); $menu_item = sprintf( $item_template, $this->get_unique_affix(), $item['menu_slug'], ! empty( $item['class'] ) ? $item['class'] : '', $item['menu_title'] ); $top_level_menu_slug = $this->get_top_level_menu_slug(); $menu_slug = $this->_menu->get_slug( $item['menu_slug'] ); if ( ! isset( $item['url'] ) ) { $hook = FS_Admin_Menu_Manager::add_subpage( $item['show_submenu'] ? $top_level_menu_slug : '', $item['page_title'], $menu_item, $capability, $menu_slug, $item['render_function'] ); if ( false !== $item['before_render_function'] ) { add_action( "load-$hook", $item['before_render_function'] ); } } else { FS_Admin_Menu_Manager::add_subpage( $item['show_submenu'] ? $top_level_menu_slug : '', $item['page_title'], $menu_item, $capability, $menu_slug, array( $this, '' ) ); } if ( $item['show_submenu'] && $is_first_submenu_item ) { if ( $this->_is_network_active && ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { /** * If the top-level menu has been dynamically created, remove the first submenu item that * WordPress automatically creates when there's no submenu item whose slug matches the * parent's. In the following example, the `Awesome Plugin` submenu item will be removed. * * Awesome Plugin * - Awesome Plugin <-- we want to remove this since there's no real setting page for the top-level * * @author Leo Fajardo (@leorw) */ remove_submenu_page( $top_level_menu_slug, $top_level_menu_slug ); } $is_first_submenu_item = false; } } } } /** * Re-order the submenu items so all Freemius added new submenu items * are added right after the plugin's settings submenu item. * * @author Vova Feldman (@svovaf) * @since 1.1.4 */ private function order_sub_submenu_items() { global $submenu; $menu_slug = $this->_menu->get_top_level_menu_slug(); /** * Before "admin_menu" fires, WordPress will loop over the default submenus and remove pages for which the user * does not have permissions. So in case a plugin does not have top-level menu but does have submenus under any * of the default menus, only users that have the right role can access its sub-submenus (Account, Contact Us, * Support Forum, etc.) since $submenu[ $menu_slug ] will be empty if the user doesn't have permission. * * In case a plugin does not have submenus under any of the default menus but does have submenus under the menu * of another plugin, only users that have the right role can access its sub-submenus since we will use the * capability needed to access the parent menu as the capability for the submenus that we will add. */ if ( empty( $submenu[ $menu_slug ] ) ) { return; } $top_level_menu = &$submenu[ $menu_slug ]; $all_submenu_items_after = array(); $found_submenu_item = false; foreach ( $top_level_menu as $submenu_id => $meta ) { if ( $found_submenu_item ) { // Remove all submenu items after the plugin's submenu item. $all_submenu_items_after[] = $meta; unset( $top_level_menu[ $submenu_id ] ); } if ( $this->_menu->get_raw_slug() === $meta[2] ) { // Found the submenu item, put all below. $found_submenu_item = true; continue; } } // Embed all plugin's new submenu items. $this->embed_submenu_items(); // Start with specially high number to make sure it's appended. $i = max( 10000, max( array_keys( $top_level_menu ) ) + 1 ); foreach ( $all_submenu_items_after as $meta ) { $top_level_menu[ $i ] = $meta; $i ++; } // Sort submenu items. ksort( $top_level_menu ); } /** * Helper method to return the module's support forum URL. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return string */ function get_support_forum_url() { return $this->apply_filters( 'support_forum_url', "https://wordpress.org/support/{$this->_module_type}/{$this->_slug}" ); } /** * Displays the Support Forum link when enabled. * * Can be filtered like so: * * function _fs_show_support_menu( $is_visible, $menu_id ) { * if ( 'support' === $menu_id ) { * return _fs->is_registered(); * } * return $is_visible; * } * _fs()->add_filter('is_submenu_visible', '_fs_show_support_menu', 10, 2); * */ function _add_default_submenu_items() { if ( ! $this->is_on() ) { return; } if ( ! $this->is_activation_mode() && ( ( $this->_is_network_active && fs_is_network_admin() ) || ( ! $this->_is_network_active && is_admin() ) ) ) { $this->add_submenu_link_item( $this->apply_filters( 'support_forum_submenu', $this->get_text_inline( 'Support Forum', 'support-forum' ) ), $this->get_support_forum_url(), 'wp-support-forum', null, 50, $this->is_submenu_item_visible( 'support' ) ); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param string $menu_title * @param callable $render_function * @param bool|string $page_title * @param string $capability * @param bool|string $menu_slug * @param bool|callable $before_render_function * @param int $priority * @param bool $show_submenu * @param string $class Since 1.2.1.5 can add custom classes to menu items. */ function add_submenu_item( $menu_title, $render_function, $page_title = false, $capability = 'manage_options', $menu_slug = false, $before_render_function = false, $priority = WP_FS__DEFAULT_PRIORITY, $show_submenu = true, $class = '' ) { $this->_logger->entrance( 'Title = ' . $menu_title ); if ( $this->is_addon() ) { $parent_fs = $this->get_parent_instance(); if ( is_object( $parent_fs ) ) { $parent_fs->add_submenu_item( $menu_title, $render_function, $page_title, $capability, $menu_slug, $before_render_function, $priority, $show_submenu, $class ); return; } } if ( ! isset( $this->_menu_items[ $priority ] ) ) { $this->_menu_items[ $priority ] = array(); } $this->_menu_items[ $priority ][] = array( 'page_title' => is_string( $page_title ) ? $page_title : $menu_title, 'menu_title' => $menu_title, 'capability' => $capability, 'menu_slug' => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ), 'render_function' => $render_function, 'before_render_function' => $before_render_function, 'show_submenu' => $show_submenu, 'class' => $class, ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param string $menu_title * @param string $url * @param bool $menu_slug * @param string $capability * @param int $priority * @param bool $show_submenu */ function add_submenu_link_item( $menu_title, $url, $menu_slug = false, $capability = 'read', $priority = WP_FS__DEFAULT_PRIORITY, $show_submenu = true ) { $this->_logger->entrance( 'Title = ' . $menu_title . '; Url = ' . $url ); if ( $this->is_addon() ) { $parent_fs = $this->get_parent_instance(); if ( is_object( $parent_fs ) ) { $parent_fs->add_submenu_link_item( $menu_title, $url, $menu_slug, $capability, $priority, $show_submenu ); return; } } if ( ! isset( $this->_menu_items[ $priority ] ) ) { $this->_menu_items[ $priority ] = array(); } $this->_menu_items[ $priority ][] = array( 'menu_title' => $menu_title, 'capability' => $capability, 'menu_slug' => is_string( $menu_slug ) ? $menu_slug : strtolower( $menu_title ), 'url' => $url, 'page_title' => $menu_title, 'render_function' => 'fs_dummy', 'before_render_function' => '', 'show_submenu' => $show_submenu, ); } #endregion ------------------------------------------------------------------ #-------------------------------------------------------------------------------- #region Admin Notices #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @param string|string[] $ids * @param int|null $network_level_or_blog_id * * @uses FS_Admin_Notices::remove_sticky() */ function remove_sticky( $ids, $network_level_or_blog_id = null ) { $this->_admin_notices->remove_sticky( $ids, $network_level_or_blog_id ); } #endregion #-------------------------------------------------------------------------------- #region Actions / Hooks / Filters #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @param string $tag * * @return string */ public function get_action_tag( $tag ) { return self::get_action_tag_static( $tag, $this->_slug, $this->is_plugin() ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @param string $tag * @param string $slug * @param bool $is_plugin * * @return string */ static function get_action_tag_static( $tag, $slug = '', $is_plugin = true ) { $action = "fs_{$tag}"; if ( ! empty( $slug ) ) { $action .= '_' . self::get_module_unique_affix( $slug, $is_plugin ); } return $action; } /** * Returns a string that can be used to generate a unique action name, * option name, HTML element ID, or HTML element class. * * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return string */ public function get_unique_affix() { return self::get_module_unique_affix( $this->_slug, $this->is_plugin() ); } /** * Returns a string that can be used to generate a unique action name, * option name, HTML element ID, or HTML element class. * * @author Vova Feldman (@svovaf) * @since 1.2.2.5 * * @param string $slug * @param bool $is_plugin * * @return string */ static function get_module_unique_affix( $slug, $is_plugin = true ) { $affix = $slug; if ( ! $is_plugin ) { $affix .= '-' . WP_FS__MODULE_TYPE_THEME; } return $affix; } /** * @author Vova Feldman (@svovaf) * @since 1.2.1 * @since 1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are * based on the slug for backward compatibility. * * @param string $tag * * @return string */ function get_ajax_action( $tag ) { return self::get_ajax_action_static( $tag, $this->_module_id ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $tag * * @return string */ function get_ajax_security( $tag ) { return wp_create_nonce( $this->get_ajax_action( $tag ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $tag */ function check_ajax_referer( $tag ) { check_ajax_referer( $this->get_ajax_action( $tag ), 'security' ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * @since 1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are * based on the slug for backward compatibility. * * @param string $tag * @param number|null $module_id * * @return string */ static function get_ajax_action_static( $tag, $module_id = null ) { $action = "fs_{$tag}"; if ( ! empty( $module_id ) ) { $action .= "_{$module_id}"; } return $action; } /** * Do action, specific for the current context plugin. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param string $tag The name of the action to be executed. * @param mixed $arg,... Optional. Additional arguments which are passed on to the * functions hooked to the action. Default empty. * * @uses do_action() */ function do_action( $tag, $arg = '' ) { $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 ) ) ); } /** * Add action, specific for the current context plugin. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param string $tag * @param callable $function_to_add * @param int $priority * @param int $accepted_args * * @uses add_action() */ function add_action( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY, $accepted_args = 1 ) { $this->_logger->entrance( $tag ); add_action( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args ); } /** * Add AJAX action, specific for the current context plugin. * * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @param string $tag * @param callable $function_to_add * @param int $priority * * @uses add_action() * * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching. */ function add_ajax_action( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY ) { $this->_logger->entrance( $tag ); return self::add_ajax_action_static( $tag, $function_to_add, $priority, $this->_module_id ); } /** * Add AJAX action. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @param string $tag * @param callable $function_to_add * @param int $priority * @param number|null $module_id * * @return bool True if action added, false if no need to add the action since the AJAX call isn't matching. * @uses add_action() * */ static function add_ajax_action_static( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY, $module_id = null ) { self::$_static_logger->entrance( $tag ); if ( ! self::is_ajax_action_static( $tag, $module_id ) ) { return false; } add_action( 'wp_ajax_' . self::get_ajax_action_static( $tag, $module_id ), $function_to_add, $priority, 0 ); self::$_static_logger->info( "$tag AJAX callback action added." ); return true; } /** * Send a JSON response back to an Ajax request. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $response */ static function shoot_ajax_response( $response ) { wp_send_json( $response ); } /** * Send a JSON response back to an Ajax request, indicating success. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $data Data to encode as JSON, then print and exit. */ static function shoot_ajax_success( $data = null ) { wp_send_json_success( $data ); } /** * Send a JSON response back to an Ajax request, indicating failure. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $error Optional error message. */ static function shoot_ajax_failure( $error = '' ) { $result = array( 'success' => false ); if ( ! empty( $error ) ) { $result['error'] = $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. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string $tag The name of the filter hook. * @param mixed $value The value on which the filters hooked to `$tag` are applied on. * * @return mixed The filtered value after all hooked functions are applied to it. * * @uses apply_filters() */ function apply_filters( $tag, $value ) { $args = func_get_args(); $this->_logger->entrance( $tag ); array_unshift( $args, $this->get_unique_affix() ); return call_user_func_array( 'fs_apply_filter', $args ); } /** * Add filter, specific for the current context plugin. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string $tag * @param callable $function_to_add * @param int $priority * @param int $accepted_args * * @uses add_filter() */ function add_filter( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY, $accepted_args = 1 ) { $this->_logger->entrance( $tag ); add_filter( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args ); } /** * Check if has filter. * * @author Vova Feldman (@svovaf) * @since 1.1.4 * * @param string $tag * @param callable|bool $function_to_check Optional. The callback to check for. Default false. * * @return false|int * * @uses has_filter() */ function has_filter( $tag, $function_to_check = false ) { $this->_logger->entrance( $tag ); return has_filter( $this->get_action_tag( $tag ), $function_to_check ); } #endregion /** * Override default i18n text phrases. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string[] string $key_value * * @uses fs_override_i18n() */ function override_i18n( $key_value ) { fs_override_i18n( $key_value, $this->_slug ); } /* Account Page ------------------------------------------------------------------------------------------------------------------*/ /** * Update site information. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param bool $store Flush to Database if true. * @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, $is_backup = false ) { $this->_logger->entrance(); if ( is_null( $site ) ) { $site = $this->_site; } if ( !isset( $site ) || !is_object($site) || empty( $site->id ) ) { $this->_logger->error( "Empty install ID, can't store site." ); return; } $site_clone = clone $site; $sites = self::get_all_sites( $this->_module_type, $network_level_or_blog_id, $is_backup ); 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 ); if ( empty( $prev_stored_user_id ) && is_object($this->_user) && $this->_user->id != $site->user_id ) { /** * Store the current user ID as the previous user ID so that the previous user can be used * as the install's owner while the new owner's details are not yet available. * * This will be executed only in the `replica` site. For example, there are 2 sites, namely `original` * and `replica`, then an ownership change was initiated and completed in the `original`, the `replica` * will be using the previous user until it is updated again (e.g.: until the next clone of `original` * into `replica`. * * @author Leo Fajardo (@leorw) */ $this->_storage->store( 'prev_user_id', $sites[ $this->_slug ]->user_id, $network_level_or_blog_id ); } } $sites[ $this->_slug ] = $site_clone; $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 ); } /** * Update plugin's plans information. * * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @param bool $store Flush to Database if true. */ private function _store_plans( $store = true ) { $this->_logger->entrance(); $plans = self::get_all_plans( $this->_module_type ); // Copy plans. $encrypted_plans = array(); for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) { $encrypted_plans[] = self::_encrypt_entity( $this->_plans[ $i ] ); } $plans[ $this->_slug ] = $encrypted_plans; $this->set_account_option( 'plans', $plans, $store ); } /** * Update user's plugin licenses. * * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param bool $store * @param number|bool $module_id * @param FS_Plugin_License[] $licenses */ private function _store_licenses( $store = true, $module_id = false, $licenses = array() ) { $this->_logger->entrance(); $all_licenses = self::get_all_licenses(); if ( ! FS_Plugin::is_valid_id( $module_id ) ) { $module_id = $this->_module_id; $user_licenses = is_array( $this->_licenses ) ? $this->_licenses : array(); if ( empty( $user_licenses ) ) { // If the context user doesn't have any license, don't update the licenses collection. return; } $new_user_licenses_map = array(); foreach ( $user_licenses as $user_license ) { $new_user_licenses_map[ $user_license->id ] = $user_license; } self::store_user_id_license_ids_map( array_keys( $new_user_licenses_map ), $this->_module_id, $this->_user->id ); // Update user licenses. $licenses_to_update_count = count( $new_user_licenses_map ); foreach ( $all_licenses[ $module_id ] as $key => $license ) { if ( 0 === $licenses_to_update_count ) { break; } if ( isset( $new_user_licenses_map[ $license->id ] ) ) { // Update license. $all_licenses[ $module_id ][ $key ] = $new_user_licenses_map[ $license->id ]; unset( $new_user_licenses_map[ $license->id ] ); $licenses_to_update_count --; } } if ( ! empty( $new_user_licenses_map ) ) { // Add new licenses. $all_licenses[ $module_id ] = array_merge( array_values( $new_user_licenses_map ), $all_licenses[ $module_id ] ); } $licenses = $all_licenses[ $module_id ]; } if ( ! isset( $all_licenses[ $module_id ] ) ) { $all_licenses[ $module_id ] = array(); } $all_licenses[ $module_id ] = $licenses; self::$_accounts->set_option( 'all_licenses', $all_licenses, $store ); } /** * Update user information. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param bool $store Flush to Database if true. */ private function _store_user( $store = true ) { $this->_logger->entrance(); if ( empty( $this->_user->id ) ) { $this->_logger->error( "Empty user ID, can't store user." ); return; } $users = self::get_all_users(); $users[ $this->_user->id ] = $this->_user; self::$_accounts->set_option( 'users', $users, $store ); } /** * Update new updates information. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param FS_Plugin_Tag|null $update * @param bool $store Flush to Database if true. * @param bool|number $plugin_id */ private function _store_update( $update, $store = true, $plugin_id = false ) { $this->_logger->entrance(); if ( $update instanceof FS_Plugin_Tag ) { $update->updated = time(); } if ( ! is_numeric( $plugin_id ) ) { $plugin_id = $this->_plugin->id; } $updates = self::get_all_updates(); $updates[ $plugin_id ] = $update; self::$_accounts->set_option( 'updates', $updates, $store ); } /** * Update new updates information. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param FS_Plugin[] $plugin_addons * @param bool $store Flush to Database if true. */ private function _store_addons( $plugin_addons, $store = true ) { $this->_logger->entrance(); $addons = self::get_all_addons(); $addons[ $this->_plugin->id ] = $plugin_addons; self::$_accounts->set_option( 'addons', $addons, $store ); } /** * Delete plugin's associated add-ons. * * @author Vova Feldman (@svovaf) * @since 1.0.8 * * @param bool $store * * @return bool */ private function _delete_account_addons( $store = true ) { $all_addons = self::get_all_account_addons(); if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) { return false; } unset( $all_addons[ $this->_plugin->id ] ); self::$_accounts->set_option( 'account_addons', $all_addons, $store ); return true; } /** * Update account add-ons list. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param FS_Plugin[] $addons * @param bool $store Flush to Database if true. */ private function _store_account_addons( $addons, $store = true ) { $this->_logger->entrance(); $all_addons = self::get_all_account_addons(); $all_addons[ $this->_plugin->id ] = $addons; self::$_accounts->set_option( 'account_addons', $all_addons, $store ); } /** * Purges the cache for the valid user licenses API call so that when the `Account` or `Add-Ons` page is loaded, * the valid user licenses will be fetched again and the account add-ons may be updated. * * @author Leo Fajardo (@leorw) * @since 2.2.4 */ private function purge_valid_user_licenses_cache() { if ( ! $this->is_registered() ) { return; } $this->get_api_user_scope()->purge_cache( $this->get_valid_user_licenses_endpoint() ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param array $all_licenses * @param number|null $site_license_id * @param bool $include_parent_licenses * * @return array */ private function get_foreign_licenses_info( $all_licenses, $site_license_id = null, $include_parent_licenses = false ) { $foreign_licenses = array( 'ids' => array(), 'license_keys' => array() ); $parent_license_ids_map = array(); foreach ( $all_licenses as $license ) { if ( $license->user_id == $this->_user->id || $license->id == $site_license_id ) { continue; } $foreign_licenses['ids'][] = $license->id; $foreign_licenses['license_keys'][] = $license->secret_key; if ( $include_parent_licenses && is_object( $this->_license ) && FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ) && ! isset( $parent_license_ids_map[ $this->_license->parent_license_id ] ) ) { /** * Include the parent license's info only if it has not been included before since child licenses * can have the same parent license. */ $foreign_licenses['ids'][] = $this->_license->parent_license_id; $foreign_licenses['license_keys'][] = $license->secret_key; $parent_license_ids_map[ $this->_license->parent_license_id ] = true; } } if ( empty( $foreign_licenses['ids'] ) ) { $foreign_licenses = array(); } return $foreign_licenses; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @return string */ private function get_valid_user_licenses_endpoint() { $user_licenses_endpoint = '/licenses.json?type=active' . ( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? '&is_enriched=true' : '' ); $foreign_licenses = $this->get_foreign_licenses_info( self::get_all_licenses( $this->_module_id ), null, true ); if ( ! empty ( $foreign_licenses ) ) { $foreign_licenses = array( // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) ); $user_licenses_endpoint = add_query_arg( $foreign_licenses, $user_licenses_endpoint ); } return $user_licenses_endpoint; } /** * Fetches active licenses that are enriched with product type if there's a context `bundle_id` and bundle * licenses enriched with product IDs if there are any. From the licenses, the `get_updated_account_addons` * method filters out non–add-on product IDs and stores the add-on IDs. * * @author Leo Fajardo (@leorw) * @since 2.2.4 * * @return stdClass[] array */ private function fetch_valid_user_licenses() { $this->_logger->entrance(); $result = $this->get_api_user_scope()->get( $this->get_valid_user_licenses_endpoint() ); if ( ! $this->is_api_result_object( $result, 'licenses' ) || ! is_array( $result->licenses ) ) { return array(); } return $result->licenses; } /** * @author Leo Fajardo (@leorw) * @since 2.2.4 * * @return number[] Account add-on IDs. */ function get_updated_account_addons() { $addons = $this->get_addons(); if ( empty( $addons ) ) { return array(); } $account_addons = $this->get_account_addons(); if ( ! is_array( $account_addons ) ) { $account_addons = array(); } $user_licenses = $this->is_registered() ? $this->fetch_valid_user_licenses() : array(); if ( empty( $user_licenses ) ) { return $account_addons; } $addon_ids = array(); foreach ( $addons as $addon ) { $addon_ids[] = $addon->id; } $license_product_ids = array(); foreach ( $user_licenses as $license ) { if ( isset( $license->plugin_type ) && 'bundle' === $license->plugin_type ) { $license_product_ids = array_merge( $license_product_ids, $license->products ); } else { $license_product_ids[] = $license->plugin_id; } } // Filter out non–add-on IDs. $new_account_addons = array_intersect( $addon_ids, $license_product_ids ); if ( count( $new_account_addons ) !== count( $account_addons ) ) { $this->_store_account_addons( array_unique( $new_account_addons ) ); } return $new_account_addons; } /** * Store account params in the Database. * * @author Vova Feldman (@svovaf) * @since 1.0.1 * * @param null|int $blog_id Since 2.0.0 */ private function _store_account( $blog_id = null ) { $this->_logger->entrance(); $this->_store_site( false, $blog_id ); $this->_store_user( false ); $this->_store_plans( false ); $this->_store_licenses( false ); self::$_accounts->store( $blog_id ); } /** * Sync user's information. * * @author Vova Feldman (@svovaf) * @since 1.0.3 * @uses FS_Api */ private function _handle_account_user_sync() { $this->_logger->entrance(); $api = $this->get_api_user_scope(); // Get user's information. $user = $api->get( '/', true ); if ( isset( $user->id ) ) { $this->_user->first = $user->first; $this->_user->last = $user->last; $this->_user->email = $user->email; $is_menu_item_account_visible = $this->is_submenu_item_visible( 'account' ); if ( $user->is_verified && ( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified ) ) { $this->_user->is_verified = true; $this->do_action( 'account_email_verified', $user->email ); $this->_admin_notices->add( $this->get_text_inline( 'Your email has been successfully verified - you are AWESOME!', 'email-verified-message' ), $this->get_text_x_inline( 'Right on', 'a positive response', 'right-on' ) . '!', 'success', // Make admin sticky if account menu item is invisible, // since the page will be auto redirected to the plugin's // main settings page, and the non-sticky message // will disappear. ! $is_menu_item_account_visible, 'email_verified' ); } // Flush user details to DB. $this->_store_user(); $this->do_action( 'after_account_user_sync', $user ); /** * If account menu item is hidden, redirect to plugin's main settings page. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @link https://github.com/Freemius/wordpress-sdk/issues/6 */ if ( ! $is_menu_item_account_visible ) { fs_redirect( $this->_get_admin_page_url() ); } } } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 * @uses FS_Api * * @param number|bool $license_id * * @return FS_Subscription|object|bool */ private function _fetch_site_license_subscription( $license_id = false ) { $this->_logger->entrance(); $api = $this->get_api_site_scope(); if ( ! is_numeric( $license_id ) ) { $license_id = FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ) ? $this->_license->parent_license_id : $this->_license->id; } $result = $api->get( "/licenses/{$license_id}/subscriptions.json", true ); return ! isset( $result->error ) ? ( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ? new FS_Subscription( $result->subscriptions[0] ) : false ) : $result; } /** * @author Vova Feldman (@svovaf) * @since 1.0.4 * @uses FS_Api * * @param number|bool $plan_id * * @return FS_Plugin_Plan|object */ private function _fetch_site_plan( $plan_id = false ) { $this->_logger->entrance(); $api = $this->get_api_site_scope(); if ( ! is_numeric( $plan_id ) ) { $plan_id = $this->_site->plan_id; } $plan = $api->get( "/plans/{$plan_id}.json", true ); return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan; } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * @uses FS_Api * * @return FS_Plugin_Plan[]|object */ private function _fetch_plugin_plans() { $this->_logger->entrance(); $api = $this->get_current_or_network_user_api_scope(); /** * @since 1.2.3 When running in DEV mode, retrieve pending plans as well. */ $result = $api->get( $this->add_show_pending( "/plugins/{$this->_module_id}/plans.json" ), true ); if ( $this->is_api_result_object( $result, 'plans' ) && is_array( $result->plans ) ) { for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) { $result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] ); } $result = $result->plans; } return $result; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $plan_id * * @return \FS_Plugin_Plan|object */ private function fetch_plan_by_id( $plan_id ) { $this->_logger->entrance(); $api = $this->get_current_or_network_user_api_scope(); $result = $api->get( "/plugins/{$this->_module_id}/plans/{$plan_id}.json", true ); return $this->is_api_result_entity( $result ) ? new FS_Plugin_Plan( $result ) : $result; } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * @uses FS_Api * * @param number|bool $plugin_id * @param number|bool $site_license_id * @param array $foreign_licenses @since 2.0.0. This is used by network-activated plugins. * @param number|null $blog_id * * @return FS_Plugin_License[]|object */ private function _fetch_licenses( $plugin_id = false, $site_license_id = false, $foreign_licenses = array(), $blog_id = null ) { $this->_logger->entrance(); $api = $this->get_api_user_scope(); if ( ! is_numeric( $plugin_id ) ) { $plugin_id = $this->_plugin->id; } $user_licenses_endpoint = "/plugins/{$plugin_id}/licenses.json?is_enriched=true"; if ( ! empty ( $foreign_licenses ) ) { $foreign_licenses = array( // Prefix with `+` to tell the server to include foreign licenses in the licenses collection. 'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ), 'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) ) ); $user_licenses_endpoint = add_query_arg( $foreign_licenses, $user_licenses_endpoint ); } $result = $api->get( $user_licenses_endpoint, true ); $is_site_license_synced = false; $api_errors = array(); if ( $this->is_api_result_object( $result, 'licenses' ) && is_array( $result->licenses ) ) { for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) { $result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] ); if ( ( ! $is_site_license_synced ) && is_numeric( $site_license_id ) ) { $is_site_license_synced = ( $site_license_id == $result->licenses[ $i ]->id ); } } $result = $result->licenses; } else { $api_errors[] = $result; $result = array(); } if ( ! $is_site_license_synced ) { if ( ! is_null( $blog_id ) ) { /** * If blog ID is not null, the request is for syncing of the license of a single site via the * network-level "Account" page. * * @author Leo Fajardo (@leorw) */ $this->switch_to_blog( $blog_id ); } $api = $this->get_api_site_scope(); if ( is_numeric( $site_license_id ) ) { // Try to retrieve a foreign license that is linked to the install. $api_result = $api->call( '/licenses.json?is_enriched=true' ); if ( $this->is_api_result_object( $api_result, 'licenses' ) && is_array( $api_result->licenses ) ) { $licenses = $api_result->licenses; if ( ! empty( $licenses ) ) { $result[] = new FS_Plugin_License( $licenses[0] ); } } else { $api_errors[] = $api_result; } } else if ( is_object( $this->_license ) && /** * Sync only if the license belongs to the context plugin. `$plugin_id` can be an add-on ID while * the FS instance that does the syncing is the parent FS instance. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $this->_license->plugin_id == $plugin_id ) { $is_license_in_result = false; if ( ! empty( $result ) ) { foreach ( $result as $license ) { if ( $license->id == $this->_license->id ) { $is_license_in_result = true; break; } } } if ( ! $is_license_in_result ) { // Fetch foreign license by ID and license key. $license = $api->get( "/licenses/{$this->_license->id}.json?license_key=" . urlencode( $this->_license->secret_key ) . '&is_enriched=true' ); if ( $this->is_api_result_entity( $license ) ) { $result[] = new FS_Plugin_License( $license ); } else { $api_errors[] = $license; } } } if ( ! is_null( $blog_id ) ) { $this->switch_to_blog( $this->_storage->network_install_blog_id ); } } if ( is_array( $result ) && 0 < count( $result ) ) { // If found at least one license, return license collection even if there are errors. return $result; } if ( ! empty( $api_errors ) ) { // If found any errors and no licenses, return first error. return $api_errors[0]; } // Fallback to empty licenses list. return $result; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param number $license_id * @param string $license_key * * @return \FS_Plugin_License|object */ private function fetch_license_by_key( $license_id, $license_key ) { $this->_logger->entrance(); $api = $this->get_current_or_network_user_api_scope(); $result = $api->get( "/licenses/{$license_id}.json?license_key=" . urlencode( $license_key ) ); return $this->is_api_result_entity( $result ) ? new FS_Plugin_License( $result ) : $result; } /** * @author Vova Feldman (@svovaf) * @since 1.2.0 * @uses FS_Api * * @param number|bool $plugin_id * @param bool $flush * * @return FS_Payment[]|object */ function _fetch_payments( $plugin_id = false, $flush = false ) { $this->_logger->entrance(); $api = $this->get_api_user_scope(); if ( ! is_numeric( $plugin_id ) ) { $plugin_id = $this->_plugin->id; } $include_bundles = ( is_object( $this->_plugin ) && FS_Plugin::is_valid_id( $this->_plugin->bundle_id ) ); $result = $api->get( "/plugins/{$plugin_id}/payments.json?include_addons=true" . ($include_bundles ? '&include_bundles=true' : ''), $flush ); if ( ! isset( $result->error ) ) { for ( $i = 0, $len = count( $result->payments ); $i < $len; $i ++ ) { $result->payments[ $i ] = new FS_Payment( $result->payments[ $i ] ); } $result = $result->payments; } return $result; } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * @uses FS_Api * * @param bool $flush * * @return \FS_Billing|mixed */ function _fetch_billing( $flush = false ) { require_once WP_FS__DIR_INCLUDES . '/entities/class-fs-billing.php'; $billing = $this->get_api_user_scope()->get( 'billing.json', $flush ); if ( $this->is_api_result_entity( $billing ) ) { $billing = new FS_Billing( $billing ); } return $billing; } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param FS_Plugin_License[] $licenses * @param number $module_id */ private function _update_licenses( $licenses, $module_id ) { $this->_logger->entrance(); if ( is_array( $licenses ) ) { for ( $i = 0, $len = count( $licenses ); $i < $len; $i ++ ) { $licenses[ $i ]->updated = time(); } } $this->_store_licenses( true, $module_id, $licenses ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param bool|number $plugin_id * @param bool $flush Since 1.1.7.3 * @param int $expiration Since 1.2.2.7 * @param bool|string $newer_than Since 2.2.1 * * @return object|false New plugin tag info if exist. */ private function _fetch_newer_version( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) { $latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration, $newer_than ); if ( ! is_object( $latest_tag ) ) { return false; } $plugin_version = $this->get_plugin_version(); // Check if version is actually newer. $has_new_version = // If it's an non-installed add-on then always return latest. ( $this->_is_addon_id( $plugin_id ) && ! $this->is_addon_activated( $plugin_id ) ) || // Compare versions. version_compare( $plugin_version, $latest_tag->version, '<' ); $this->_logger->departure( $has_new_version ? 'Found newer plugin version ' . $latest_tag->version : 'No new version' ); $is_latest_version_beta = ( 'beta' === $latest_tag->release_mode ); $this->_storage->beta_data = array( 'is_beta' => $is_latest_version_beta, 'version' => $latest_tag->version ); return $has_new_version ? $latest_tag : false; } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param bool|number $plugin_id * @param bool $flush Since 1.1.7.3 * @param int $expiration Since 1.2.2.7 * @param bool|string $newer_than Since 2.2.1 * * @return bool|FS_Plugin_Tag */ function get_update( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) { $this->_logger->entrance(); if ( ! is_numeric( $plugin_id ) ) { $plugin_id = $this->_plugin->id; } $this->check_updates( true, $plugin_id, $flush, $expiration, $newer_than ); $updates = $this->get_all_updates(); return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false; } /** * Check if site assigned with active license. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @deprecated Please use has_active_valid_license() instead because license can be cancelled. */ function has_active_license() { return ( is_object( $this->_license ) && is_numeric( $this->_license->id ) && ! $this->_license->is_expired() ); } /** * Check if site assigned with active & valid (not expired) license. * * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @param bool $check_expiration */ function has_active_valid_license( $check_expiration = true ) { return self::is_active_valid_license( $this->_license, $check_expiration ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 */ function is_data_debug_mode() { if ( is_null( $this->is_whitelabeled ) || ! $this->is_whitelabeled ) { return false; } $fs = $this->is_addon() ? $this->get_parent_instance() : $this; if ( $fs->is_network_active() && fs_is_network_admin() ) { $is_developer_license_debug_mode = get_site_transient( "fs_{$this->get_id()}_data_debug_mode" ); } else { $is_developer_license_debug_mode = get_transient( "fs_{$this->get_id()}_data_debug_mode" ); } return ( 'true' === $is_developer_license_debug_mode ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 */ function _set_data_debug_mode() { if ( ! $this->is_whitelabeled( true ) ) { return; } $license_or_user_key = fs_request_get_raw( 'license_or_user_key' ); $transient_value = ( ! empty( $license_or_user_key ) ) ? 'true' : 'false'; if ( 'true' === $transient_value ) { $stored_key = $this->_storage->get( ! FS_User::is_valid_id( $this->_storage->last_license_user_id ) ? 'last_license_key' : 'last_license_user_key' ); if ( md5( $license_or_user_key ) !== $stored_key ) { $this->shoot_ajax_failure( sprintf( '%s... %s', $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ), $this->get_text_inline( 'seems like the key you entered doesn\'t match our records.', 'developer-or-license-not-found' ) ) ); } } if ( $this->is_network_active() && fs_is_network_admin() ) { set_site_transient( "fs_{$this->get_id()}_data_debug_mode", $transient_value, WP_FS__TIME_24_HOURS_IN_SEC / 24 ); } else { set_transient( "fs_{$this->get_id()}_data_debug_mode", $transient_value, WP_FS__TIME_24_HOURS_IN_SEC / 24 ); } if ( 'true' === $transient_value ) { $this->_admin_notices->add_sticky( $this->get_text_inline( 'Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.', 'data_debug_mode_enabled' ), 'data_debug_mode_enabled' ); } $this->shoot_ajax_success(); } /** * Check if a given license is active & valid (not expired). * * @author Vova Feldman (@svovaf) * @since 2.1.3 * * @param FS_Plugin_License $license * @param bool $check_expiration * * @return bool */ private static function is_active_valid_license( $license, $check_expiration = true ) { return ( is_object( $license ) && FS_Plugin_License::is_valid_id( $license->id ) && $license->is_active() && ( ! $check_expiration || $license->is_valid() ) ); } /** * Checks if there's any site that is associated with an active & valid license. * This logic is used to determine if the admin can download the premium code base from a network level admin. * * @author Vova Feldman (@svovaf) * @since 2.1.3 * * @return bool */ function has_any_active_valid_license() { if ( ! fs_is_network_admin() ) { return $this->has_active_valid_license(); } $installs = $this->get_blog_install_map(); $all_plugin_licenses = self::get_all_licenses( $this->_module_id ); foreach ( $installs as $blog_id => $install ) { if ( ! FS_Plugin_License::is_valid_id( $install->license_id ) ) { continue; } foreach ( $all_plugin_licenses as $license ) { if ( $license->id == $install->license_id ) { if ( self::is_active_valid_license( $license ) ) { return true; } } } } return false; } /** * Check if site assigned with license with enabled features. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool */ function has_features_enabled_license() { return ( is_object( $this->_license ) && is_numeric( $this->_license->id ) && $this->_license->is_features_enabled() ); } /** * Checks if the product is activated with a bundle license. * * @author Leo Fajardo (@leorw) * @since 2.4.0 * * @return bool */ function is_activated_with_bundle_license() { if ( ! $this->has_features_enabled_license() ) { return false; } return FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ); } /** * Check if user is a trial or have feature enabled license. * * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @return bool */ function can_use_premium_code() { return $this->is_trial() || $this->has_features_enabled_license(); } /** * Checks if the current user can activate plugins or switch themes. Note that this method should only be used * after the `init` action is triggered because it is using `current_user_can()` which is only functional after * the context user is authenticated. * * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool */ function is_user_admin() { /** * Require a super-admin when network activated, running from the network level OR if * running from the site level but not delegated the opt-in. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ if ( $this->_is_network_active && ( fs_is_network_admin() || ! $this->is_delegated_connection() ) ) { return is_super_admin(); } return ( $this->is_plugin() && current_user_can( is_multisite() ? 'manage_options' : 'activate_plugins' ) ) || ( $this->is_theme() && current_user_can( 'switch_themes' ) ); } /** * Sync site's plan. * * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @uses FS_Api * * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by * the admin. * @param bool $is_context_single_site @since 2.0.0. This is used when syncing a license for a single install from the * network-level "Account" page. * @param int|null $current_blog_id @since 2.2.3. This is passed from the `execute_cron` method and used by the * `_sync_plugin_license` method in order to switch to the previous blog when sending * updates for a single site in case `execute_cron` has switched to a different blog. */ private function _sync_license( $background = false, $is_context_single_site = false, $current_blog_id = null ) { $this->_logger->entrance(); $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); $is_addon_sync = ( ! $this->_plugin->is_addon() && $plugin_id != $this->get_id() ); if ( $is_addon_sync ) { $this->_sync_addon_license( $plugin_id, $background ); } else { $this->_sync_plugin_license( $background, true, $is_context_single_site, $current_blog_id ); } $this->do_action( 'after_account_plan_sync', $this->get_plan_name() ); } /** * Sync plugin's add-on license. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * @uses FS_Api * * @param number $addon_id * @param bool $background */ private function _sync_addon_license( $addon_id, $background ) { $this->_logger->entrance(); if ( $this->is_addon_activated( $addon_id ) ) { // If already installed, use add-on sync. $fs_addon = self::get_instance_by_id( $addon_id ); if ( // Add-on is network activated and network integrated. $fs_addon->is_network_active() || // Background sync cron. self::is_cron() || // Add-on is not network activated or not network integrated. ! fs_is_network_admin() ) { $fs_addon->_sync_license( $background ); return; } } // Validate add-on exists. $addon = $this->get_addon( $addon_id ); if ( ! is_object( $addon ) ) { return; } // Add add-on into account add-ons. $account_addons = $this->get_account_addons(); if ( ! is_array( $account_addons ) ) { $account_addons = array(); } $account_addons[] = $addon->id; $account_addons = array_unique( $account_addons ); $this->_store_account_addons( $account_addons ); // Load add-on licenses. $licenses = $this->_fetch_licenses( $addon->id ); // Sync add-on licenses. if ( $this->is_array_instanceof( $licenses, 'FS_Plugin_License' ) ) { $this->_update_licenses( $licenses, $addon->id ); if ( ! $this->is_addon_installed( $addon->id ) && FS_License_Manager::has_premium_license( $licenses ) ) { $plans_result = $this->get_api_site_or_plugin_scope()->get( $this->add_show_pending( "/addons/{$addon_id}/plans.json" ) ); if ( ! isset( $plans_result->error ) ) { $plans = array(); foreach ( $plans_result->plans as $plan ) { $plans[] = new FS_Plugin_Plan( $plan ); } $this->_admin_notices->add_sticky( sprintf( ( FS_Plan_Manager::instance()->has_free_plan( $plans ) ? $this->get_text_inline( 'Your %s Add-on plan was successfully upgraded.', 'addon-successfully-upgraded-message' ) : /* translators: %s:product name, e.g. Facebook add-on was successfully... */ $this->get_text_inline( '%s Add-on was successfully purchased.', 'addon-successfully-purchased-message' ) ), $addon->title ) . ' ' . $this->get_latest_download_link( $this->get_text_inline( 'Download the latest version', 'download-latest-version' ), $addon_id ), 'addon_plan_upgraded_' . $addon->slug, $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!' ); } } } } /** * Sync site's plugin plan. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * @uses FS_Api * * @param bool $background Hints the method if it's a background sync. If false, it means that was initiated by the admin. * @param bool $send_installs_update Since 2.0.0 * @param bool $is_context_single_site Since 2.0.0. This is used when sending an update for a single install and * syncing its license from the network-level "Account" page (e.g.: after * activating a license only for the single install). * @param int|null $current_blog_id Since 2.2.3. This is passed from the `execute_cron` method so that it * can be used here to switch to the previous blog in case `execute_cron` * has switched to a different blog. */ private function _sync_plugin_license( $background = false, $send_installs_update = true, $is_context_single_site = false, $current_blog_id = null ) { $this->_logger->entrance(); $plan_change = 'none'; $is_site_level_sync = ( $is_context_single_site || fs_is_blog_admin() || ! $this->_is_network_active ); if ( ! $send_installs_update ) { $site = $this->_site; } else { /** * Sync site info. * * @todo This line will execute install sync on a daily basis, even if running the free version (for opted-in users). The reason we want to keep it that way is for cases when the user was a paying customer, then there was a failure in subscription payment, and then after some time the payment was successful. This could be heavily optimized. For example, we can skip the $flush if the current install was never associated with a paid version. */ if ( $is_site_level_sync ) { /** * Switch to the previous blog since `execute_cron` may have switched to a different blog. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ if ( is_numeric( $current_blog_id ) ) { $this->switch_to_blog( $current_blog_id ); } $result = $this->send_install_update( array(), true, true ); $is_valid = $this->is_api_result_entity( $result ); } else { $result = $this->send_installs_update( array(), true, true ); $is_valid = $this->is_api_result_object( $result, 'installs' ); } if ( ! $is_valid ) { if ( $is_context_single_site ) { // Switch back to the main blog so that the following logic will have the right entities. $this->switch_to_blog( $this->_storage->network_install_blog_id ); } // 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::is_blocked( $result ) ) { /** * @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. */ 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 ); } } // 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 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 ), '', 'error' ); } } // No reason to continue with license sync while there are API issues. 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 { // Map site addresses to their blog IDs. $address_to_blog_map = $this->get_address_to_blog_map(); // Find the current context install. $site = null; foreach ( $result->installs as $install ) { if ( $install->id == $this->_site->id ) { $site = new FS_Site( $install ); } else { $address = trailingslashit( fs_strip_url_protocol( $install->url ) ); $blog_id = $address_to_blog_map[ $address ]; $this->_store_site( true, $blog_id, new FS_Site( $install ) ); } } } // Sync plans. $this->_sync_plans(); } // Remove sticky API connectivity message. self::$_global_admin_notices->remove_sticky( 'api_blocked' ); if ( ! $this->has_paid_plan() ) { $this->_site = $site; $this->_store_site( true, $is_site_level_sync ? null : $this->get_network_install_blog_id() ); } else { $context_blog_id = 0; if ( $is_context_single_site ) { $context_blog_id = get_current_blog_id(); // Switch back to the main blog in order to properly sync the license. $this->switch_to_blog( $this->_storage->network_install_blog_id ); } /** * Sync licenses. Pass the site's license ID so that the foreign licenses will be fetched if the license * associated with that ID is not included in the user's licenses collection. */ $this->_sync_licenses( $site->license_id, ( $is_context_single_site ? $context_blog_id : null ) ); if ( $is_context_single_site ) { $this->switch_to_blog( $context_blog_id ); } // Check if plan / license changed. if ( $site->plan_id != $this->_site->plan_id || // Check if trial started. $site->trial_plan_id != $this->_site->trial_plan_id || $site->trial_ends != $this->_site->trial_ends || // Check if license changed. $site->license_id != $this->_site->license_id ) { if ( $site->is_trial() && ( ! $this->_site->is_trial() || $site->trial_ends != $this->_site->trial_ends ) ) { // New trial started. $this->_site = $site; $plan_change = 'trial_started'; // For trial with subscription use-case. $new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id ); if ( is_object( $new_license ) && $new_license->is_valid() ) { $this->_site = $site; $this->_update_site_license( $new_license ); $this->_store_licenses(); $this->_sync_site_subscription( $this->_license ); } } else if ( $this->_site->is_trial() && ! $site->is_trial() && ! is_numeric( $site->license_id ) ) { // Was in trial, but now trial expired and no license ID. // New trial started. $this->_site = $site; $plan_change = 'trial_expired'; } else { $is_free = $this->is_free_plan(); // Make sure license exist and not expired. $new_license = is_null( $site->license_id ) ? null : $this->_get_license_by_id( $site->license_id ); if ( $is_free && is_null( $new_license ) && $this->has_any_license() && $this->_license->is_cancelled ) { // License cancelled. $this->_site = $site; $this->_update_site_license( $new_license ); $this->_store_licenses(); $plan_change = 'cancelled'; } else if ( $is_free && ( ( ! is_object( $new_license ) || $new_license->is_expired() ) ) ) { // The license is expired, so ignore upgrade method. $this->_site = $site; } else { // License changed. $this->_site = $site; /** * IMPORTANT: * The line below should be executed before trying to activate the license on the rest of the network, otherwise, the license' activation counters may be out of sync + there's no need to activate the license on the context site since it's already activated on it. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ $this->_update_site_license( $new_license ); if ( ! $is_context_single_site && fs_is_network_admin() && $this->_is_network_active && $new_license->quota > 1 && get_blog_count() > 1 ) { // See if license can activated on all sites. if ( ! $this->try_activate_license_on_network( $this->_user, $new_license ) ) { if ( ! fs_request_get_bool( 'auto_install' ) ) { // Open the license activation dialog box on the account page. add_action( 'admin_footer', array( &$this, '_open_license_activation_dialog_box' ) ); } } } $this->_store_licenses(); $plan_change = $is_free ? ( $this->is_only_premium() ? 'activated' : 'upgraded' ) : ( is_object( $new_license ) ? 'changed' : 'downgraded' ); } } // Store updated site info. $this->_store_site( true, $is_site_level_sync ? null : $this->get_network_install_blog_id() ); } else { if ( ! is_object( $this->_license ) ) { $this->maybe_update_whitelabel_flag( FS_Plugin_License::is_valid_id( $site->license_id ) ? $this->get_license_by_id( $site->license_id ) : null ); } else { $this->maybe_update_whitelabel_flag( $this->_license ); if ( $this->_license->is_expired() ) { if ( ! $this->has_features_enabled_license() ) { $this->_deactivate_license(); $plan_change = 'downgraded'; } else { $last_time_expired_license_notice_was_shown = $this->_storage->get( 'expired_license_notice_shown', 0 ); if ( time() - ( 14 * WP_FS__TIME_24_HOURS_IN_SEC ) >= $last_time_expired_license_notice_was_shown ) { /** * Show the expired license notice every 14 days. * * @author Leo Fajardo (@leorw) * @since 2.3.1 */ $plan_change = 'expired'; } } } } if ( is_numeric( $site->license_id ) && is_object( $this->_license ) ) { $this->_sync_site_subscription( $this->_license ); } } if ( ! $this->is_addon() && $this->_site->is_beta() !== $site->is_beta() ) { // Beta flag updated. $this->_site = $site; $this->_store_site( true, $is_site_level_sync ? null : $this->get_network_install_blog_id() ); } if ( $this->is_addon() || $this->has_addons() ) { /** * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, * an updated valid user licenses collection will be fetched from the server which is used to also * update the account add-ons (add-ons the user has licenses for). * * @author Leo Fajardo (@leorw) * @since 2.2.4 */ $this->purge_valid_user_licenses_cache(); } } $hmm_text = $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...'; if ( $this->apply_filters( 'has_paid_plan_account', $this->has_paid_plan() ) ) { switch ( $plan_change ) { case 'none': if ( ! $background && is_admin() ) { $plan = $this->is_trial() ? $this->get_trial_plan() : $this->get_plan(); if ( $plan->is_free() ) { $this->_admin_notices->add( sprintf( $this->get_text_inline( 'It looks like you are still on the %s plan. If you did upgrade or change your plan, it\'s probably an issue on our side - sorry.', 'plan-did-not-change-message' ), '' . $plan->title . ( $this->is_trial() ? ' ' . $this->get_text_x_inline( 'Trial', 'trial period', 'trial' ) : '' ) . '' ) . ' ' . sprintf( '%s', $this->contact_url( 'bug', sprintf( $this->get_text_inline( 'I have upgraded my account but when I try to Sync the License, the plan remains %s.', 'plan-did-not-change-email-message' ), strtoupper( $plan->name ) ) ), $this->get_text_inline( 'Please contact us here', 'contact-us-here' ) ), $hmm_text ); } } break; case 'upgraded': case 'activated': $this->add_after_plan_activation_or_upgrade_instructions_notice( 'upgraded' === $plan_change ); $this->_admin_notices->remove_sticky( array( 'trial_started', 'trial_promotion', 'trial_expired', 'activation_complete', 'license_expired', ) ); break; case 'changed': $this->_admin_notices->add_sticky( sprintf( $this->get_text_inline( 'Your plan was successfully changed to %s.', 'plan-changed-to-x-message' ), $this->get_plan_title() ), 'plan_changed' ); $this->_admin_notices->remove_sticky( array( 'trial_started', 'trial_promotion', 'trial_expired', 'activation_complete', ) ); break; case 'downgraded': $this->_admin_notices->add_sticky( ($this->has_free_plan() ? sprintf( $this->get_text_inline( 'Your license has expired. You can still continue using the free %s forever.', 'license-expired-blocking-message' ), $this->_module_type ) : /* translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. */ sprintf( $this->get_text_inline( 'Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.', 'license-expired-blocking-message_premium-only' ), sprintf('', $this->pricing_url()), '', $this->get_module_label(true) ) ), 'license_expired', $hmm_text ); $this->_admin_notices->remove_sticky( 'plan_upgraded' ); break; case 'cancelled': $this->_admin_notices->add( $this->get_text_inline( 'Your license has been cancelled. If you think it\'s a mistake, please contact support.', 'license-cancelled' ) . ' ' . sprintf( '%s', $this->contact_url( 'bug' ), $this->get_text_inline( 'Please contact us here', 'contact-us-here' ) ), $hmm_text, 'error' ); $this->_admin_notices->remove_sticky( 'plan_upgraded' ); break; case 'expired': $this->_admin_notices->add_sticky( sprintf( $this->get_text_inline( 'Your license has expired. You can still continue using all the %s features, but you\'ll need to renew your license to continue getting updates and support.', 'license-expired-non-blocking-message' ), $this->get_plan()->title ), 'license_expired', $hmm_text ); $this->_storage->expired_license_notice_shown = WP_FS__SCRIPT_START_TIME; $this->_admin_notices->remove_sticky( 'plan_upgraded' ); break; case 'trial_started': $this->add_complete_upgrade_instructions_notice( sprintf( $this->get_text_inline( 'Your trial has been successfully started.', 'trial-started-message' ), '' . $this->get_plugin_name() . '' ), 'trial_started', $this->get_trial_plan()->title ); $this->_admin_notices->remove_sticky( array( 'trial_promotion', ) ); break; case 'trial_expired': $this->_admin_notices->add_sticky( ($this->has_free_plan() ? $this->get_text_inline( 'Your free trial has expired. You can still continue using all our free features.', 'trial-expired-message' ) : /* translators: %1$s: product title; %2$s, %3$s: wrapping HTML anchor element; %4$s: 'plugin', 'theme', or 'add-on'. */ sprintf( $this->get_text_inline( 'Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.', 'trial-expired-message_premium-only' ), sprintf('', $this->pricing_url()), '', $this->get_module_label(true))), 'trial_expired', $hmm_text ); $this->_admin_notices->remove_sticky( array( 'trial_started', 'trial_promotion', 'plan_upgraded', ) ); break; } } if ( 'none' !== $plan_change ) { if ( ! is_object( $this->_license ) || ! $this->_license->is_whitelabeled ) { $this->_admin_notices->remove_sticky( 'license_whitelabeled' ); } $this->do_action( 'after_license_change', $plan_change, $this->get_plan() ); } } /** * @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. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ public function _open_license_activation_dialog_box() { $vars = array( 'license_id' => $this->_site->license_id ); fs_require_once_template( 'js/open-license-activation.php', $vars ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param bool $background * @param FS_Plugin_License|null $premium_license */ protected function _activate_license( $background = false, $premium_license = null ) { $this->_logger->entrance(); if ( is_null( $premium_license ) ) { $license_id = fs_request_get( 'license_id' ); if ( is_object( $this->_site ) && FS_Plugin_License::is_valid_id( $license_id ) && $license_id == $this->_site->license_id ) { // License is already activated. return; } $premium_license = FS_Plugin_License::is_valid_id( $license_id ) ? $this->_get_license_by_id( $license_id ) : $this->_get_available_premium_license(); } if ( ! is_object( $premium_license ) ) { return; } if ( ! is_object( $this->_site ) ) { // Not yet opted-in. $user = $this->get_current_or_network_user(); if ( ! is_object( $user ) ) { $user = self::_get_user_by_id( $premium_license->user_id ); } if ( is_object( $user ) ) { $this->install_with_user( $user, $premium_license->secret_key, false, false, false ); } else { $this->opt_in( false, false, false, $premium_license->secret_key ); return; } } /** * If the premium license is already associated with the install, just * update the license reference (activation is not required). * * @since 1.1.9 */ if ( $premium_license->id == $this->_site->license_id ) { // License is already activated. $this->_update_site_license( $premium_license ); $this->_store_account(); return; } if ( $this->_site->user_id != $premium_license->user_id ) { $api_request_params = array( 'license_key' => $premium_license->secret_key ); } else { $api_request_params = array(); } $api = $this->get_api_site_scope(); $license = $api->call( "/licenses/{$premium_license->id}.json?is_enriched=true", 'put', $api_request_params ); if ( ! $this->is_api_result_entity( $license ) ) { if ( ! $background ) { $this->_admin_notices->add( sprintf( '%s %s', $this->get_text_inline( 'It looks like the license could not be activated.', 'license-activation-failed-message' ), ( is_object( $license ) && isset( $license->error ) ? $license->error->message : sprintf( '%s
    %s', $this->get_text_inline( 'Error received from the server:', 'server-error-message' ), var_export( $license, true ) ) ) ), $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...', 'error' ); } return; } $premium_license = new FS_Plugin_License( $license ); // Updated site plan. $site = $this->get_api_site_scope()->get( '/', true ); if ( $this->is_api_result_entity( $site ) ) { $this->_site = new FS_Site( $site ); } $this->_update_site_license( $premium_license ); $this->_store_account(); if ( $this->is_addon() || $this->has_addons() ) { /** * Purge the valid user licenses cache so that when the "Account" or the "Add-Ons" page is loaded, * an updated valid user licenses collection will be fetched from the server which is used to also * update the account add-ons (add-ons the user has licenses for). * * @author Leo Fajardo (@leorw) * @since 2.2.4 */ $this->purge_valid_user_licenses_cache(); } if ( ! $background ) { $this->add_complete_upgrade_instructions_notice( $this->get_text_inline( 'Your license was successfully activated.', 'license-activated-message' ), 'license_activated' ); } $this->_admin_notices->remove_sticky( array( 'trial_promotion', 'license_expired', ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param bool $show_notice */ protected function _deactivate_license( $show_notice = true ) { $this->_logger->entrance(); $hmm_text = $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...'; if ( ! FS_Plugin_License::is_valid_id( $this->_site->license_id ) ) { $this->_admin_notices->add( sprintf( $this->get_text_inline( 'It looks like your site currently doesn\'t have an active license.', 'no-active-license-message' ), $this->get_plan_title() ), $hmm_text ); return; } $api = $this->get_api_site_scope(); $license = $api->call( "/licenses/{$this->_site->license_id}.json", 'delete' ); $this->handle_license_deactivation_result( $license, $hmm_text, $show_notice ); } /** * @author Leo Fajardo (@leorw) * @since 2.2.1 * * @param FS_Plugin_License $license * @param bool|string $hmm_text * @param bool $show_notice */ private function handle_license_deactivation_result( $license, $hmm_text = false, $show_notice = true ) { if ( isset( $license->error ) ) { $this->_admin_notices->add( $this->get_text_inline( 'It looks like the license deactivation failed.', 'license-deactivation-failed-message' ) . '
    ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . ' ' . var_export( $license->error, true ), $hmm_text, 'error' ); return; } // Update license cache. if ( is_array( $this->_licenses ) ) { for ( $i = 0, $len = count( $this->_licenses ); $i < $len; $i ++ ) { if ( $license->id == $this->_licenses[ $i ]->id ) { $this->_licenses[ $i ] = new FS_Plugin_License( $license ); } } } // Update site plan to default. $this->_sync_plans(); $this->_site->plan_id = $this->_plans[0]->id; // Unlink license from site. $this->_update_site_license( null ); $this->_store_account(); if ( $show_notice ) { $this->_admin_notices->add( sprintf( $this->is_only_premium() ? $this->get_text_inline( 'Your %s license was successfully deactivated.', 'license-deactivation-message_premium-only' ) : $this->get_text_inline( 'Your license was successfully deactivated, you are back to the %s plan.', 'license-deactivation-message' ), $this->get_plan_title() ), $this->get_text_inline( 'O.K', 'ok' ) ); } $this->_admin_notices->remove_sticky( array( 'plan_upgraded', 'license_activated', ) ); } /** * Site plan downgrade. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @return object * * @uses FS_Api */ private function _downgrade_site() { $this->_logger->entrance(); $deactivate_license = fs_request_get_bool( 'deactivate_license' ); $api = $this->get_api_site_scope(); $site = $api->call( 'downgrade.json', 'put', array( 'deactivate_license' => $deactivate_license ) ); $plan_downgraded = false; $plan = false; if ( $this->is_api_result_entity( $site ) ) { $prev_plan_id = $this->_site->plan_id; // Update new site plan id. $this->_site->plan_id = $site->plan_id; $plan = $this->get_plan(); $subscription = $this->_sync_site_subscription( $this->_license ); // Plan downgraded if plan was changed or subscription was cancelled. $plan_downgraded = ( $plan instanceof FS_Plugin_Plan && $prev_plan_id != $plan->id ) || ( is_object( $subscription ) && ! isset( $subscription->error ) && ! $subscription->is_active() ); } else { // handle different error cases. $this->handle_license_deactivation_result( $site, $this->get_text_x_inline( 'Hmm', 'something somebody says when they are thinking about what you have just said.', 'hmm' ) . '...' ); } if ( ! $plan_downgraded ) { return (object) array( 'error' => (object) array( 'message' => $this->get_text_inline( 'Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.', 'subscription-cancellation-failure-message' ) ) ); } // Remove previous sticky message about upgrade (if exist). $this->_admin_notices->remove_sticky( 'plan_upgraded' ); $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Your subscription was successfully cancelled. Your %s plan license will expire in %s.', 'plan-x-downgraded-message' ), $plan->title, human_time_diff( time(), strtotime( $this->_license->expiration ) ) ) ); // Store site updates. $this->_store_site(); if ( $deactivate_license && ! FS_Plugin_License::is_valid_id( $site->license_id ) ) { if ( $this->_site->is_localhost() ) { $this->_license->activated_local = max( 0, $this->_license->activated_local - 1 ); } else { $this->_license->activated = max( 0, $this->_license->activated - 1 ); } // Handle successful license deactivation result. $this->handle_license_deactivation_result( $this->_license ); } return $site; } /** * @author Vova Feldman (@svovaf) * @since 1.1.8.1 * * @param bool|string $plan_name * * @return bool If trial was successfully started. */ function start_trial( $plan_name = false ) { $this->_logger->entrance(); // Alias. $oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...'; if ( $this->is_trial() ) { // Already in trial mode. $this->_admin_notices->add( sprintf( $this->get_text_inline( 'You are already running the %s in a trial mode.', 'in-trial-mode' ), $this->_module_type ), $oops_text, 'error' ); return false; } if ( $this->_site->is_trial_utilized() ) { // Trial was already utilized. $this->_admin_notices->add( $this->get_text_inline( 'You already utilized a trial before.', 'trial-utilized' ), $oops_text, 'error' ); return false; } if ( false !== $plan_name ) { $plan = $this->get_plan_by_name( $plan_name ); if ( false === $plan ) { // Plan doesn't exist. $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Plan %s do not exist, therefore, can\'t start a trial.', 'trial-plan-x-not-exist' ), $plan_name ), $oops_text, 'error' ); return false; } if ( ! $plan->has_trial() ) { // Plan doesn't exist. $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Plan %s does not support a trial period.', 'plan-x-no-trial' ), $plan_name ), $oops_text, 'error' ); return false; } } else { if ( ! $this->has_trial_plan() ) { // None of the plans have a trial. $this->_admin_notices->add( sprintf( $this->get_text_inline( 'None of the %s\'s plans supports a trial period.', 'no-trials' ), $this->_module_type ), $oops_text, 'error' ); return false; } $plans_with_trial = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans ); $plan = $plans_with_trial[0]; } $api = $this->get_api_site_scope(); $plan = $api->call( "plans/{$plan->id}/trials.json", 'post' ); if ( ! $this->is_api_result_entity( $plan ) ) { // Some API error while trying to start the trial. $this->_admin_notices->add( $this->get_api_error_message( $plan ), $oops_text, 'error' ); return false; } // Sync license. $this->_sync_license(); return $this->is_trial(); } /** * Cancel site trial. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return object * * @uses FS_Api */ private function _cancel_trial() { $this->_logger->entrance(); if ( ! $this->is_trial() ) { return (object) array( 'error' => (object) array( 'message' => $this->get_text_inline( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)', 'trial-cancel-no-trial-message' ) ) ); } $trial_plan = $this->get_trial_plan(); $api = $this->get_api_site_scope(); $site = $api->call( 'trials.json', 'delete' ); $trial_cancelled = false; if ( $this->is_api_result_entity( $site ) ) { $prev_trial_ends = $this->_site->trial_ends; if ( $this->is_paid_trial() ) { $this->_license->expiration = $site->trial_ends; $this->_license->is_cancelled = true; $this->_update_site_license( $this->_license ); $this->_store_licenses(); // Clear subscription reference. $this->_sync_site_subscription( null ); } // Update site info. $this->_site = new FS_Site( $site ); $trial_cancelled = ( $prev_trial_ends != $site->trial_ends ); } else { // @todo handle different error cases. } if ( ! $trial_cancelled ) { return (object) array( 'error' => (object) array( 'message' => $this->get_text_inline( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.', 'trial-cancel-failure-message' ) ) ); } // Remove previous sticky messages about upgrade or trial (if exist). $this->_admin_notices->remove_sticky( array( 'trial_started', 'trial_promotion', 'plan_upgraded', ) ); // Store site updates. $this->_store_site(); if ( ! $this->is_addon() || ! $this->deactivate_premium_only_addon_without_license( true ) ) { $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Your %s free trial was successfully cancelled.', 'trial-cancel-message' ), $trial_plan->title ) ); } return $site; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param bool|number $plugin_id * * @return bool */ private function _is_addon_id( $plugin_id ) { return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id ); } /** * Check if user eligible to download premium version updates. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool */ private function _can_download_premium() { return $this->has_any_active_valid_license() || ( $this->is_trial() && ! $this->get_trial_plan()->is_free() ); } /** * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param bool|number $addon_id * @param string $type "json" or "zip" * * @return string */ private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) { $is_addon = $this->_is_addon_id( $addon_id ); $is_premium = null; if ( ! $is_addon ) { $is_premium = ( $this->is_premium() || $this->_can_download_premium() ); } else if ( $this->is_addon_activated( $addon_id ) ) { $fs_addon = self::get_instance_by_id( $addon_id ); $is_premium = ( $fs_addon->is_premium() || $fs_addon->_can_download_premium() ); } // If add-on, then append add-on ID. $endpoint = ( $is_addon ? "/addons/$addon_id" : '' ) . '/updates/latest.' . $type; // If add-on and not yet activated, try to fetch based on server licensing. if ( is_bool( $is_premium ) ) { $endpoint = add_query_arg( 'is_premium', json_encode( $is_premium ), $endpoint ); } if ( $this->has_secret_key() ) { $endpoint = add_query_arg( 'type', 'all', $endpoint ); } else if ( is_object( $this->_site ) && $this->_site->is_beta() ) { $endpoint = add_query_arg( 'type', 'beta', $endpoint ); } return $endpoint; } /** * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param bool|number $addon_id * @param bool $flush Since 1.1.7.3 * @param int $expiration Since 1.2.2.7 * @param bool|string $newer_than Since 2.2.1 * @param bool|string $fetch_readme Since 2.2.1 * * @return object|false Plugin latest tag info. */ function _fetch_latest_version( $addon_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false, $fetch_readme = true ) { $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() || ! FS_Permission_Manager::instance( $this )->is_essentials_tracking_allowed() ) && ! $this->_is_addon_id( $addon_id ) ) { if ( ! is_multisite() ) { return false; } $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 */ if ( $install->is_trial() ) { $switch_to_blog_id = $blog_id; break; } if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { $license = $this->get_license_by_id( $install->license_id ); if ( is_object( $license ) && $license->is_features_enabled() ) { $switch_to_blog_id = $blog_id; break; } } } if ( is_null( $switch_to_blog_id ) ) { return false; } } $current_blog_id = is_numeric( $switch_to_blog_id ) ? get_current_blog_id() : 0; if ( is_numeric( $switch_to_blog_id ) ) { $this->switch_to_blog( $switch_to_blog_id ); } $latest_version_endpoint = $this->_get_latest_version_endpoint( $addon_id, 'json' ); if ( ! empty( $newer_than ) ) { $latest_version_endpoint = add_query_arg( 'newer_than', $newer_than, $latest_version_endpoint ); } if ( true === $fetch_readme ) { $latest_version_endpoint = add_query_arg( 'readme', 'true', $latest_version_endpoint ); } $tag = $this->get_api_site_or_plugin_scope()->get( $latest_version_endpoint, $flush, $expiration ); if ( is_numeric( $switch_to_blog_id ) ) { $this->switch_to_blog( $current_blog_id ); } $latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get'; $this->_logger->departure( 'Latest version ' . $latest_version ); return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false; } #---------------------------------------------------------------------------------- #region Download Plugin #---------------------------------------------------------------------------------- /** * Download latest plugin version, based on plan. * * Not like _download_latest(), this will redirect the page * to secure download url to prevent dual download (from FS to WP server, * and then from WP server to the client / browser). * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param bool|number $plugin_id * * @uses FS_Api * @uses wp_redirect() */ private function download_latest_directly( $plugin_id = false ) { $this->_logger->entrance(); wp_redirect( $this->get_latest_download_api_url( $plugin_id ) ); } /** * Get latest plugin FS API download URL. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param bool|number $plugin_id * * @return string */ private function get_latest_download_api_url( $plugin_id = false ) { $this->_logger->entrance(); $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 ); } /** * Get payment invoice URL. * * @author Vova Feldman (@svovaf) * @since 1.2.0 * * @param bool|number $payment_id * * @return string */ function _get_invoice_api_url( $payment_id = false ) { $this->_logger->entrance(); $url = $this->get_api_user_scope()->get_signed_url( "/payments/{$payment_id}/invoice.pdf" ); if ( ! fs_starts_with( $url, 'https://' ) ) { // Always use HTTPS for invoices. $url = 'https' . substr( $url, 4 ); } return $url; } /** * Get latest plugin download link. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string $label * @param bool|number $plugin_id * * @return string */ private function get_latest_download_link( $label, $plugin_id = false ) { return sprintf( '%s', $this->_get_latest_download_local_url( $plugin_id ), $label ); } /** * Get latest plugin download local URL. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param bool|number $plugin_id * * @return string */ function _get_latest_download_local_url( $plugin_id = false ) { // Add timestamp to protect from caching. $params = array( 'ts' => WP_FS__SCRIPT_START_TIME ); if ( ! empty( $plugin_id ) ) { $params['plugin_id'] = $plugin_id; } else if ( $this->is_addon() ) { $params['plugin_id'] = $this->get_id(); } $fs = $this->is_addon() ? $this->get_parent_instance() : $this; return $this->apply_filters( 'download_latest_url', $fs->get_account_url( 'download_latest', $params ) ); } #endregion Download Plugin ------------------------------------------------------------------ /** * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @uses FS_Api * * @param bool $background Hints the method if it's a background updates check. If false, it means that * was initiated by the admin. * @param bool|number $plugin_id * @param bool $flush Since 1.1.7.3 * @param int $expiration Since 1.2.2.7 * @param bool|string $newer_than Since 2.2.1 */ private function check_updates( $background = false, $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) { $this->_logger->entrance(); // Check if there's a newer version for download. $new_version = $this->_fetch_newer_version( $plugin_id, $flush, $expiration, $newer_than ); $update = null; if ( is_object( $new_version ) ) { $update = new FS_Plugin_Tag( $new_version ); if ( ! $background ) { $this->_admin_notices->add( sprintf( /* translators: %s: Numeric version number (e.g. '2.1.9' */ $this->get_text_inline( 'Version %s was released.', 'version-x-released' ) . ' ' . $this->get_text_inline( 'Please download %s.', 'please-download-x' ), $update->version, sprintf( '%s', $this->get_account_url( 'download_latest' ), sprintf( /* translators: %s: plan name (e.g. latest "Professional" version) */ $this->get_text_inline( 'the latest %s version here', 'latest-x-version' ), $this->get_plan_title() ) ) ), $this->get_text_inline( 'New', 'new' ) . '!' ); } } else if ( false === $new_version && ! $background ) { $this->_admin_notices->add( $this->get_text_inline( 'Seems like you got the latest release.', 'you-have-latest' ), $this->get_text_inline( 'You are all good!', 'you-are-good' ) ); } $this->_store_update( $update, true, $plugin_id ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param bool $flush Since 1.1.7.3 add 24 hour cache by default. * * @return FS_Plugin[] * * @uses FS_Api */ private function sync_addons( $flush = false ) { $this->_logger->entrance(); $api = $this->get_api_site_or_plugin_scope(); $path = $this->add_show_pending( '/addons.json?enriched=true&count=50' ); /** * @since 1.2.1 * * If there's a cached version of the add-ons and not asking * for a flush, just use the currently stored add-ons. */ if ( ! $flush && $api->is_cached( $path ) ) { $addons = self::get_all_addons(); return isset( $addons[ $this->_plugin->id ] ) ? $addons[ $this->_plugin->id ] : array(); } $result = $api->get( $path, $flush ); $addons = array(); if ( $this->is_api_result_object( $result, 'plugins' ) && is_array( $result->plugins ) ) { for ( $i = 0, $len = count( $result->plugins ); $i < $len; $i ++ ) { $addons[ $i ] = new FS_Plugin( $result->plugins[ $i ] ); } $this->_store_addons( $addons, true ); } return $addons; } /** * Handle user email update. * * @author Vova Feldman (@svovaf) * @since 1.0.3 * @uses FS_Api * * @param string $new_email * * @return object */ 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, 'after_email_confirm_url' => $this->_get_admin_page_url( 'account', array( 'fs_action' => 'sync_user' ) ), ) ); if ( ! isset( $user->error ) ) { $this->_user->email = $user->email; $this->_user->is_verified = $user->is_verified; $this->_store_user(); } else { // handle different error cases. } return $user; } #---------------------------------------------------------------------------------- #region API Error Handling #---------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 1.1.1 * * @param mixed $result * * @return bool Is API result contains an error. */ private function is_api_error( $result ) { return FS_Api::is_api_error( $result ); } /** * Checks if given API result is a non-empty and not an error object. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $result * @param string|null $required_property Optional property we want to verify that is set. * * @return bool */ function is_api_result_object( $result, $required_property = null ) { return FS_Api::is_api_result_object( $result, $required_property ); } /** * Checks if given API result is a non-empty entity object with non-empty ID. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $result * * @return bool */ private function is_api_result_entity( $result ) { return FS_Api::is_api_result_entity( $result ); } #endregion /** * Make sure a given argument is an array of a specific type. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $array * @param string $class * * @return bool */ private function is_array_instanceof( $array, $class ) { return ( is_array( $array ) && ( empty( $array ) || $array[0] instanceof $class ) ); } /** * Start install ownership change. * * @author Vova Feldman (@svovaf) * @since 1.1.1 * @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, $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' ) ), ) ); return ! $this->is_api_error( $result ); } /** * Handle install ownership change. * * @author Vova Feldman (@svovaf) * @since 1.1.1 * @uses FS_Api * * @return bool Was ownership change successfully complete. */ private function complete_change_owner() { $this->_logger->entrance(); $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 ] ); } } } 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; } $this->set_account_option( 'sites', $current_blog_sites, true ); // Fetch new user information. $user_result = $this->get_api_user_scope( true )->get(); $user = new FS_User( $user_result ); $this->_user = $user; $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; } /** * Completes ownership change by license. * * @author Leo Fajardo (@leorw) * @since 2.3.2 * * @param number $user_id * @param array[string]number $install_ids_by_slug_map * */ private function complete_ownership_change_by_license( $user_id, $install_ids_by_slug_map ) { $this->_logger->entrance(); $this->sync_user_by_current_install( $user_id ); $result = $this->get_api_user_scope( true )->get( "/installs.json?install_ids=" . implode( ',', $install_ids_by_slug_map ) ); if ( $this->is_api_result_object( $result, 'installs' ) ) { $sites = self::get_all_sites( $this->get_module_type() ); $install_ids_by_slug_map = array_flip( $install_ids_by_slug_map ); foreach ( $result->installs as $install ) { $site = new FS_Site( $install ); $sites[ $install_ids_by_slug_map[ $site->id ] ] = clone $site; } $this->set_account_option( 'sites', $sites, true ); } } /** * Handle user name update. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * @uses FS_Api * * @return object */ private function update_user_name() { $this->_logger->entrance(); $name = fs_request_get( 'fs_user_name_' . $this->get_unique_affix(), '' ); $api = $this->get_api_user_scope(); $user = $api->call( "?plugin_id={$this->_plugin->id}&fields=id,first,last", 'put', array( 'name' => $name, ) ); if ( ! isset( $user->error ) ) { $this->_user->first = $user->first; $this->_user->last = $user->last; $this->_store_user(); } else { // handle different error cases. } return $user; } /** * Verify user email. * * @author Vova Feldman (@svovaf) * @since 1.0.3 * @uses FS_Api */ private function verify_email() { $this->_handle_account_user_sync(); if ( $this->_user->is_verified() ) { return; } $api = $this->get_api_site_scope(); $result = $api->call( "/users/{$this->_user->id}/verify.json", 'put', array( 'after_email_confirm_url' => $this->_get_admin_page_url( 'account', array( 'fs_action' => 'sync_user' ) ) ) ); if ( ! isset( $result->error ) ) { $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.', 'verification-email-sent-message' ), sprintf( '%2$s', esc_url( $this->_user->email ), $this->_user->email ) ) ); } else { // handle different error cases. } } /** * @author Vova Feldman (@svovaf) * @since 1.1.2 * * @param array $params * @param bool|null $network * * @return string */ function get_activation_url( $params = array(), $network = null ) { if ( $this->is_addon() && $this->has_free_plan() ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.1.7 Add-on's activation is the parent's module activation. */ return $this->get_parent_instance()->get_activation_url( $params ); } return $this->apply_filters( 'connect_url', $this->_get_admin_page_url( '', $params, $network ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param array $params * * @return string */ function get_reconnect_url( $params = array() ) { $params['fs_action'] = 'reset_anonymous_mode'; $params['fs_unique_affix'] = $this->get_unique_affix(); return $this->get_activation_url( $params ); } /** * Get the URL of the page that should be loaded after the user connect * or skip in the opt-in screen. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string $filter Filter name. * @param array $params Since 1.2.2.7 * @param bool|null $network * * @return string */ function get_after_activation_url( $filter, $params = array(), $network = null ) { if ( $this->show_opt_in_on_themes_page() && ( fs_request_has( 'pending_activation' ) || // For cases when the first time path is set, even though it's a WP.org theme. fs_request_get_bool( $this->get_unique_affix() . '_show_optin' ) ) ) { $first_time_path = ''; } else { $first_time_path = $this->_menu->get_first_time_path( fs_is_network_admin() && $this->_is_network_active ); } if ( $this->_is_network_active && fs_is_network_admin() && ! $this->_menu->has_network_menu() && $this->is_network_registered() ) { $target_url = $this->get_account_url(); } else { // Default plugin's page. $target_url = $this->_get_admin_page_url( '', array(), $network ); } return add_query_arg( $params, $this->apply_filters( $filter, empty( $first_time_path ) ? $target_url : $first_time_path ) ); } /** * Handle account page updates / edits / actions. * * @author Vova Feldman (@svovaf) * @since 1.0.2 * */ private function _handle_account_edits() { if ( ! $this->is_user_admin() ) { return; } $action = fs_get_action(); if ( empty( $action ) ) { return; } $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); $install_id = fs_request_get( 'install_id', '' ); // Alias. $oops_text = $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...'; $is_network_action = $this->is_network_level_action(); $blog_id = $this->is_network_level_site_specific_action(); $is_parent_plugin_action = ( $plugin_id == $this->get_id() ); if ( is_numeric( $blog_id ) ) { $this->switch_to_blog( $blog_id ); } else { $blog_id = ''; } switch ( $action ) { case 'opt_in': check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); if ( $is_parent_plugin_action ) { if ( $is_network_action && ! empty( $blog_id ) ) { if ( ! $this->is_registered() ) { $this->install_with_user( $this->get_network_user(), false, false, false, false ); $this->_admin_notices->add( $this->get_text_inline( 'Site successfully opted in.', 'successful-opt-in' ), $this->get_text_inline( 'Awesome', 'awesome' ) ); } } } break; case 'toggle_tracking': check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); if ( $is_parent_plugin_action ) { if ( $is_network_action && ! empty( $blog_id ) ) { 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( '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->toggle_site_tracking( false, $blog_id ) ) { $install = $this->get_install_by_blog_id( $blog_id ); $this->_admin_notices->add( sprintf( $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()}" ) ); } } } } } break; case 'delete_account': check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); $is_network_deletion = $is_network_action && empty( $blog_id ); if ( $is_parent_plugin_action ) { // Delete add-on installs if have any. $installed_addons = $this->get_installed_addons(); foreach ( $installed_addons as $fs_addon ) { if ( $is_network_deletion ) { $fs_addon->delete_network_account_event(); } else { $fs_addon->delete_account_event(); } } if ( $is_network_deletion ) { $this->delete_network_account_event(); } else { $this->delete_account_event(); } // Clear user and site. $this->_site = null; $this->_user = null; $this->maybe_set_slug_and_network_menu_exists_flag(); fs_redirect( $this->get_activation_url() ); } else { if ( $this->is_addon_activated( $plugin_id ) ) { $fs_addon = self::get_instance_by_id( $plugin_id ); if ( $is_network_deletion ) { $fs_addon->delete_network_account_event(); } else { $fs_addon->delete_account_event(); } fs_redirect( $this->_get_admin_page_url( 'account' ) ); } } return; case 'downgrade_account': if ( is_numeric( $blog_id ) ) { check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); } else { check_admin_referer( $action ); } $switch_to_network_install_blog_after_cancellation = ( is_numeric( $blog_id ) && $plugin_id == $this->get_id() && ! $this->is_trial() ); $result = $this->cancel_subscription_or_trial( $plugin_id ); if ( $this->is_api_error( $result ) ) { $this->_admin_notices->add( $result->error->message, $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', 'error' ); } if ( $switch_to_network_install_blog_after_cancellation ) { $this->switch_to_blog( $this->_storage->network_install_blog_id ); } return; case 'activate_license': check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); $fs = $this; if ( $plugin_id != $this->get_id() ) { $fs = $this->is_addon_activated( $plugin_id ) ? self::get_instance_by_id( $plugin_id ) : null; } if ( is_object( $fs ) ) { $fs->_activate_license(); /** * Remove the product ID from `$_REQUEST` so that the syncing of the license for the other products will work properly. * * @author Leo Fajardo (@leorw) * @since 2.4.0 */ unset( $_REQUEST['plugin_id'] ); if ( $this->is_bundle_license_auto_activation_enabled() ) { $fs->maybe_activate_bundle_license( null, array(), is_numeric( $blog_id ) ? $blog_id : 0 ); } } return; case 'deactivate_license': check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) ); if ( $plugin_id == $this->get_id() ) { $this->_deactivate_license(); if ( $this->is_only_premium() ) { // Clear user and site. $this->_site = null; $this->_user = null; if ( ! $is_network_action ) { fs_redirect( $this->get_activation_url() ); } else if ( is_numeric( $blog_id ) ) { $this->switch_to_blog( $this->_storage->network_install_blog_id ); } } } else { if ( $this->is_addon_activated( $plugin_id ) ) { $fs_addon = self::get_instance_by_id( $plugin_id ); $fs_addon->_deactivate_license(); } } return; case 'check_updates': check_admin_referer( $action ); $this->check_updates(); return; case 'change_owner': $state = fs_request_get( 'state', 'init' ); switch ( $state ) { case 'init': // The nonce is injected by the error handler in `_email_address_update_ajax_handler` function. check_admin_referer( 'change_owner' ); $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 . '' ), 'ownership_changed', $this->get_text_x_inline( 'Congrats', 'as congratulations', 'congrats' ) . '!' ); } else { // @todo Handle failed ownership change message. } break; } return; case 'update_user_name': check_admin_referer( 'update_user_name' ); $result = $this->update_user_name(); if ( isset( $result->error ) ) { $this->_admin_notices->add( $this->get_text_inline( 'Please provide your full name.', 'name-update-failed-message' ), $oops_text, 'error' ); } else { $this->_admin_notices->add( $this->get_text_inline( 'Your name was successfully updated.', 'name-updated-message' ) ); } return; #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 ) ) { $this->_admin_notices->add( $result->error->message, $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...', 'error' ); } return; case 'verify_email': $this->verify_email(); return; case 'sync_user': $this->_handle_account_user_sync(); return; case $this->get_unique_affix() . '_sync_license': $this->_sync_license(); return; case 'download_latest': $this->download_latest_directly( $plugin_id ); return; #endregion } if ( WP_FS__IS_POST_REQUEST ) { $properties = array( 'site_secret_key', 'site_id', 'site_public_key' ); foreach ( $properties as $p ) { if ( 'update_' . $p === $action ) { check_admin_referer( $action ); $this->_logger->log( $action ); $site_property = substr( $p, strlen( 'site_' ) ); $site_property_value = fs_request_get( 'fs_' . $p . '_' . $this->get_unique_affix(), '' ); $this->get_site()->{$site_property} = $site_property_value; // Store account after modification. $this->_store_site(); $this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value ); $this->_admin_notices->add( sprintf( /* translators: %s: User's account property (e.g. email address, name) */ $this->get_text_inline( 'You have successfully updated your %s.', 'x-updated' ), '' . str_replace( '_', ' ', $p ) . '' ) ); return; } } } } /** * Account page resources load. * * @author Vova Feldman (@svovaf) * @since 1.0.6 */ function _account_page_load() { $this->_logger->entrance(); $this->_logger->info( var_export( $_REQUEST, true ) ); fs_enqueue_local_style( 'fs_account', '/admin/account.css' ); 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' ); } if ( $this->has_paid_plan() && ! $this->has_any_license() && ! $this->is_sync_executed() && $this->is_tracking_allowed() ) { /** * If no licenses found and no sync job was executed during the last 24 hours, * just execute the sync job right away (blocking execution). * * @since 1.1.7.3 */ $this->run_manual_sync(); } $this->_handle_account_edits(); if ( is_object( $this->_license ) && $this->_license->user_id == $this->_user->id && ! $this->is_whitelabeled( true ) ) { $this->_admin_notices->add( sprintf( $this->get_text_inline( "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.", 'license_not_whitelabeled' ), sprintf( '%s', $this->get_text_inline( 'Click here', 'click-here' ) ) ), '', 'success', false, 'license_not_whitelabeled' ); } $this->do_action( 'account_page_load_before_departure' ); } /** * Renders the "Affiliation" page. * * @author Leo Fajardo (@leorw) * @since 1.2.3 */ function _affiliation_page_render() { $this->_logger->entrance(); $this->fetch_affiliate_and_terms(); fs_enqueue_local_style( 'fs_affiliation', '/admin/affiliation.css' ); $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 ) ); } /** * Render account page. * * @author Vova Feldman (@svovaf) * @since 1.0.0 */ function _account_page_render() { $this->_logger->entrance(); $template = 'account.php'; $vars = array( 'id' => $this->_module_id ); /** * Added filter to the template to allow developers wrapping the template * in custom HTML (e.g. within a wizard/tabs). * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 */ echo $this->apply_filters( "templates/{$template}", fs_get_template( $template, $vars ) ); } /** * Render account connect page. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ function _connect_page_render() { $this->_logger->entrance(); $vars = array( 'id' => $this->_module_id ); /** * Added filter to the template to allow developers wrapping the template * in custom HTML (e.g. within a wizard/tabs). * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 */ echo $this->apply_filters( 'templates/connect.php', fs_get_template( 'connect.php', $vars ) ); } /** * Load required resources before add-ons page render. * * @author Vova Feldman (@svovaf) * @since 1.0.6 */ function _addons_page_load() { $this->_logger->entrance(); fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' ); 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' ); if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) { $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Just letting you know that the add-ons information of %s is being pulled from an external server.', 'addons-info-external-message' ), '' . $this->get_plugin_name() . '' ), $this->get_text_x_inline( 'Heads up', 'advance notice of something that will need attention.', 'heads-up' ), 'update-nag' ); } } /** * Render add-ons page. * * @author Vova Feldman (@svovaf) * @since 1.0.6 */ function _addons_page_render() { $this->_logger->entrance(); $vars = array( 'id' => $this->_module_id ); /** * Added filter to the template to allow developers wrapping the template * in custom HTML (e.g. within a wizard/tabs). * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 */ echo $this->apply_filters( 'templates/add-ons.php', fs_get_template( 'add-ons.php', $vars ) ); } /* Pricing & Upgrade ------------------------------------------------------------------------------------------------------------------*/ /** * Render pricing page. * * @author Vova Feldman (@svovaf) * @since 1.0.0 */ function _pricing_page_render() { $this->_logger->entrance(); $vars = array( 'id' => $this->_module_id ); if ( 'true' === fs_request_get( 'checkout', false ) ) { echo $this->apply_filters( 'templates/checkout.php', fs_get_template( 'checkout.php', $vars ) ); } else { echo $this->apply_filters( 'templates/pricing.php', fs_get_template( 'pricing.php', $vars ) ); } } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 */ function _maybe_add_pricing_ajax_handler() { if ( ! $this->should_use_external_pricing() ) { $this->add_ajax_action( 'pricing_ajax_action', array( &$this, '_fs_pricing_ajax_action_handler' ) ); } } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 */ function _fs_pricing_ajax_action_handler() { $this->check_ajax_referer( 'pricing_ajax_action' ); $result = null; $pricing_action = fs_request_get( 'pricing_action' ); switch ( $pricing_action ) { case 'fetch_pricing_data': $params = array( 'is_enriched' => true, 'trial' => fs_request_get_bool( 'trial' ), '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(); $bundle_public_key = $this->get_bundle_public_key(); $has_bundle_context = ( FS_Plugin::is_valid_id( $bundle_id ) && ! empty( $bundle_public_key ) ); if ( ! $has_bundle_context ) { $api = $this->get_api_plugin_scope(); } else { $api = FS_Api::instance( $bundle_id, 'plugin', $bundle_id, $bundle_public_key, ! $this->is_live(), false, $this->get_sdk_version() ); $params['plugin_id'] = $this->get_id(); $params['plugin_public_key'] = $this->get_public_key(); } $result = $api->get( 'pricing.json?' . http_build_query( $params ) ); break; case 'start_trial': $result = $this->opt_in( false, false, false, false, false, fs_request_get( 'plan_id' ) ); } if ( is_object( $result ) && $this->is_api_error( $result ) ) { $this->_logger->api_error( $result ); self::shoot_ajax_failure( isset( $result->error ) ? ( is_string( $result->error ) ? $result->error : $result->error->message ) : var_export( $result, true ) ); } $this->shoot_ajax_success( $result ); } #---------------------------------------------------------------------------------- #region Contact Us #---------------------------------------------------------------------------------- /** * Render contact-us page. * * @author Vova Feldman (@svovaf) * @since 1.0.3 */ function _contact_page_render() { $this->_logger->entrance(); $vars = array( 'id' => $this->_module_id ); /** * Added filter to the template to allow developers wrapping the template * in custom HTML (e.g. within a wizard/tabs). * * @author Vova Feldman (@svovaf) * @since 2.1.3 */ echo $this->apply_filters( 'templates/contact.php', fs_get_template( 'contact.php', $vars ) ); } #endregion ------------------------------------------------------------------------ /** * Hide all admin notices to prevent distractions. * * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @uses remove_all_actions() */ private static function _hide_admin_notices() { remove_all_actions( 'admin_notices' ); remove_all_actions( 'network_admin_notices' ); remove_all_actions( 'all_admin_notices' ); remove_all_actions( 'user_admin_notices' ); } static function _clean_admin_content_section_hook() { $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 ''; } /** * Attach to admin_head hook to hide all admin notices. * * @author Vova Feldman (@svovaf) * @since 1.0.3 */ static function _clean_admin_content_section() { add_action( 'admin_head', 'Freemius::_clean_admin_content_section_hook' ); } /* CSS & JavaScript ------------------------------------------------------------------------------------------------------------------*/ /* function _enqueue_script($handle, $src) { $url = plugins_url( substr( WP_FS__DIR_JS, strlen( $this->_plugin_dir_path ) ) . '/assets/js/' . $src ); $this->_logger->entrance( 'script = ' . $url ); wp_enqueue_script( $handle, $url ); }*/ /* SDK ------------------------------------------------------------------------------------------------------------------*/ private $_user_api; /** * * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @param bool $flush * * @return FS_Api */ 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 ); } return $this->_user_api; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_User $user * * @return \FS_Api */ private function get_api_user_scope_by_user( FS_User $user ) { return FS_Api::instance( $this->_module_id, 'user', $user->id, $user->public_key, ! $this->is_live(), $user->secret_key, $this->get_sdk_version() ); } /** * * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param bool $flush * * @return FS_Api */ private function get_current_or_network_user_api_scope( $flush = false ) { if ( ! $this->_is_network_active || ( isset( $this->_user ) && $this->_user instanceof FS_User ) ) { return $this->get_api_user_scope( $flush ); } $user = $this->get_current_or_network_user(); $this->_user_api = FS_Api::instance( $this->_module_id, 'user', $user->id, $user->public_key, ! $this->is_live(), $user->secret_key, $this->get_sdk_version() ); return $this->_user_api; } private $_site_api; /** * * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @param bool $flush * * @return FS_Api */ private function get_api_site_scope( $flush = false ) { if ( ! isset( $this->_site_api ) || $flush ) { $this->_site_api = FS_Api::instance( $this->_module_id, 'install', $this->_site->id, $this->_site->public_key, ! $this->is_live(), $this->_site->secret_key, $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; /** * Get plugin public API scope. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return FS_Api */ function get_api_plugin_scope() { if ( ! isset( $this->_plugin_api ) ) { $this->_plugin_api = FS_Api::instance( $this->_module_id, 'plugin', $this->_plugin->id, $this->_plugin->public_key, ! $this->is_live(), false, $this->get_sdk_version() ); } return $this->_plugin_api; } /** * Get bundle public API scope. * * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @return FS_Api */ function get_api_bundle_scope() { return FS_Api::instance( $this->get_bundle_id(), 'plugin', $this->get_bundle_id(), $this->get_bundle_public_key(), ! $this->is_live(), false, $this->get_sdk_version() ); } /** * Get site API scope object (fallback to public plugin scope when not registered). * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return FS_Api */ function get_api_site_or_plugin_scope() { return $this->is_registered() ? $this->get_api_site_scope() : $this->get_api_plugin_scope(); } /** * @author Leo Fajardo (@leorw) * @since 2.2.3.1 * * @param object $result */ private function maybe_modify_api_curl_error_message( $result ) { if ( 'cUrlMissing' !== $result->error->type && ( 'CurlException' !== $result->error->type || CURLE_COULDNT_CONNECT != $result->error->code ) && ( 'HttpRequestFailed' !== $result->error->type || false === strpos( $result->error->message, 'cURL error ' . CURLE_COULDNT_CONNECT ) ) ) { return; } $result->error->message = $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' ) . ' ' . $this->esc_html_inline( sprintf( 'Please contact your hosting provider and ask them to whitelist %s for external connection.', implode( ', ', $this->apply_filters( 'api_domains', array( 'api.freemius.com', 'wp.freemius.com' ) ) ) ), 'connectivity-whitelist' ) . ' ' . sprintf( $this->esc_html_inline( 'Once you are done, deactivate the %s and activate it again.', 'connectivity-reactivate-module' ), $this->get_module_type() ); } /** * Show trial promotional notice (if any trial exist). * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param FS_Plugin_Plan[] $plans */ function _check_for_trial_plans( $plans ) { /** * For some reason core's do_action() flattens arrays when it has a single object item. Therefore, we need to restructure the array as expected. * * @author Vova Feldman (@svovaf) * @since 2.1.2 */ if ( ! is_array( $plans ) && is_object( $plans ) ) { $plans = array( $plans ); } if ( ! $this->is_array_instanceof( $plans, 'FS_Plugin_Plan' ) ) { $plans = array(); } $this->_storage->has_trial_plan = FS_Plan_Manager::instance()->has_trial_plan( $plans ); } /** * During trial promotion the "upgrade" submenu item turns to * "start trial" to encourage the trial. Since we want to keep * the same menu item handler and there's no robust way to * add new arguments to the menu item link's querystring, * use JavaScript to find the menu item and update the href of * the link. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 */ function _fix_start_trial_menu_item_url() { $template_args = array( 'id' => $this->_module_id ); fs_require_template( 'add-trial-to-pricing.php', $template_args ); } /** * Check if module is currently in a trial promotion mode. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return bool */ function is_in_trial_promotion() { return $this->_admin_notices->has_sticky( 'trial_promotion' ); } /** * Show trial promotional notice (if any trial exist). * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool If trial notice added. */ function _add_trial_notice() { if ( ! $this->is_user_admin() ) { return false; } if ( ! $this->is_user_in_admin() ) { return false; } if ( $this->_is_network_active ) { if ( fs_is_network_admin() ) { // Network level trial is disabled at the moment. return false; } if ( ! $this->is_delegated_connection() ) { // Only delegated sites should support trials. return false; } } // Check if trial message is already shown. if ( $this->is_in_trial_promotion() ) { add_action( 'admin_footer', array( &$this, '_fix_start_trial_menu_item_url' ) ); $this->_menu->add_counter_to_menu_item( 1, 'fs-trial' ); return false; } if ( $this->is_premium() && ! WP_FS__DEV_MODE ) { // Don't show trial if running the premium code, unless running in DEV mode. return false; } if ( ! $this->has_trial_plan() ) { // No plans with trial. return false; } if ( ! $this->apply_filters( 'show_trial', true ) ) { // Developer explicitly asked not to show the trial promo. return false; } if ( $this->is_registered() ) { // Check if trial already utilized. if ( $this->_site->is_trial_utilized() ) { return false; } if ( $this->is_paying_or_trial() ) { // Don't show trial if paying or already in trial. return false; } } if ( $this->is_activation_mode() || $this->is_pending_activation() ) { // If not yet opted-in/skipped, or pending activation, don't show trial. return false; } $last_time_trial_promotion_shown = $this->_storage->get( 'trial_promotion_shown', false ); $was_promotion_shown_before = ( false !== $last_time_trial_promotion_shown ); // Show promotion if never shown before and 24 hours after initial activation with FS. if ( ! $was_promotion_shown_before && $this->_storage->install_timestamp > ( time() - $this->apply_filters( 'show_first_trial_after_n_sec', WP_FS__TIME_24_HOURS_IN_SEC ) ) ) { return false; } // OR if promotion was shown before, try showing it every 30 days. if ( $was_promotion_shown_before && $this->apply_filters( 'reshow_trial_after_every_n_sec', 30 * WP_FS__TIME_24_HOURS_IN_SEC ) > time() - $last_time_trial_promotion_shown ) { return false; } $trial_period = $this->_trial_days; $require_payment = $this->_is_trial_require_payment; $trial_url = $this->get_trial_url(); $plans_string = strtolower( $this->get_text_inline( 'Awesome', 'awesome' ) ); if ( $this->is_registered() ) { // If opted-in, override trial with up to date data from API. $trial_plans = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans ); $trial_plans_count = count( $trial_plans ); if ( 0 === $trial_plans_count ) { // If there's no plans with a trial just exit. return false; } /** * @var FS_Plugin_Plan $paid_plan */ $paid_plan = $trial_plans[0]; $require_payment = $paid_plan->is_require_subscription; $trial_period = $paid_plan->trial_period; $total_paid_plans = count( $this->_plans ) - ( FS_Plan_Manager::instance()->has_free_plan( $this->_plans ) ? 1 : 0 ); if ( $total_paid_plans !== $trial_plans_count ) { // Not all paid plans have a trial - generate a string of those that have it. for ( $i = 0; $i < $trial_plans_count; $i ++ ) { $plans_string .= sprintf( ' %s', $trial_url, $trial_plans[ $i ]->title ); if ( $i < $trial_plans_count - 2 ) { $plans_string .= ', '; } else if ( $i == $trial_plans_count - 2 ) { $plans_string .= ' and '; } } } } $message = sprintf( $this->get_text_x_inline( 'Hey', 'exclamation', 'hey' ) . '! ' . $this->get_text_inline( 'How do you like %s so far? Test all our %s premium features with a %d-day free trial.', 'trial-x-promotion-message' ), sprintf( '%s', $this->get_plugin_name() ), $plans_string, $trial_period ); // "No Credit-Card Required" or "No Commitment for N Days". $cc_string = $require_payment ? sprintf( $this->get_text_inline( 'No commitment for %s days - cancel anytime!', 'no-commitment-for-x-days' ), $trial_period ) : $this->get_text_inline( 'No credit card required', 'no-cc-required' ) . '!'; // Start trial button. $button = ' ' . sprintf( '', $trial_url, $this->get_text_x_inline( 'Start free trial', 'call to action', 'start-free-trial' ) ); $this->_admin_notices->add_sticky( $this->apply_filters( 'trial_promotion_message', "{$message} {$cc_string} {$button}" ), 'trial_promotion', '', 'promotion' ); $this->_storage->trial_promotion_shown = WP_FS__SCRIPT_START_TIME; return true; } /** * Lets users/customers know that the product has an affiliate program. * * @author Leo Fajardo (@leorw) * @since 1.2.2.11 * * @return bool Returns true if the notice has been added. */ function _add_affiliate_program_notice() { if ( ! $this->is_user_admin() ) { return false; } if ( ! $this->is_user_in_admin() ) { return false; } // Check if the notice is already shown. if ( $this->_admin_notices->has_sticky( 'affiliate_program' ) ) { return false; } if ( // Product has no affiliate program. ! $this->has_affiliate_program() || // User has applied for an affiliate account. ! empty( $this->_storage->affiliate_application_data ) ) { return false; } if ( ! $this->apply_filters( 'show_affiliate_program_notice', true ) ) { // Developer explicitly asked not to show the notice about the affiliate program. return false; } if ( $this->is_activation_mode() || $this->is_pending_activation() ) { // If not yet opted in/skipped, or pending activation, don't show the notice. return false; } $last_time_notice_was_shown = $this->_storage->get( 'affiliate_program_notice_shown', false ); $was_notice_shown_before = ( false !== $last_time_notice_was_shown ); /** * Do not show the notice if it was already shown before or less than 30 days have passed since the initial * activation with FS. */ if ( $was_notice_shown_before || $this->_storage->install_timestamp > ( time() - ( WP_FS__TIME_24_HOURS_IN_SEC * 30 ) ) ) { return false; } if ( ! $this->is_paying() && FS_Plugin::AFFILIATE_MODERATION_CUSTOMERS == $this->_plugin->affiliate_moderation ) { // If the user is not a customer and the affiliate program is only for customers, don't show the notice. return false; } $message = sprintf( $this->get_text_inline( 'Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!', 'become-an-ambassador-admin-notice' ), sprintf( '%s', $this->get_plugin_name() ), $this->get_module_label( true ) ); // HTML code for the "Learn more..." button. $button = ' ' . sprintf( '', $this->_get_admin_page_url( 'affiliation' ), $this->get_text_inline( 'Learn more', 'learn-more' ) . '...' ); $this->_admin_notices->add_sticky( $this->apply_filters( 'affiliate_program_notice', "{$message} {$button}" ), 'affiliate_program', '', 'promotion' ); $this->_storage->affiliate_program_notice_shown = WP_FS__SCRIPT_START_TIME; return true; } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.5 */ function _enqueue_common_css() { if ( $this->has_paid_plan() && ! $this->is_paying() ) { // Add basic CSS for admin-notices and menu-item colors. fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); } } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 */ function _show_theme_activation_optin_dialog() { fs_enqueue_local_style( 'fs_connect', '/admin/connect.css' ); add_action( 'admin_footer', array( &$this, '_add_fs_theme_activation_dialog' ) ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 */ function _add_fs_theme_activation_dialog() { global $pagenow; if ( 'themes.php' !== $pagenow ) { return; } $vars = array( 'id' => $this->_module_id ); fs_require_once_template( 'connect.php', $vars ); } /* Action Links ------------------------------------------------------------------------------------------------------------------*/ private $_action_links_hooked = false; private $_action_links = array(); /** * Hook to plugin action links filter. * * @author Vova Feldman (@svovaf) * @since 1.0.0 */ private function hook_plugin_action_links() { $this->_logger->entrance(); $this->_action_links_hooked = true; $this->_logger->log( 'Adding action links hooks.' ); // Add action link to settings page. add_filter( 'plugin_action_links_' . $this->_plugin_basename, array( &$this, '_modify_plugin_action_links_hook' ), WP_FS__DEFAULT_PRIORITY, 2 ); add_filter( 'network_admin_plugin_action_links_' . $this->_plugin_basename, array( &$this, '_modify_plugin_action_links_hook' ), WP_FS__DEFAULT_PRIORITY, 2 ); } /** * Add plugin action link. * * @author Vova Feldman (@svovaf) * @since 1.0.0 * * @param $label * @param $url * @param bool $external * @param int $priority * @param bool $key */ function add_plugin_action_link( $label, $url, $external = false, $priority = WP_FS__DEFAULT_PRIORITY, $key = false ) { $this->_logger->entrance(); if ( ! isset( $this->_action_links[ $priority ] ) ) { $this->_action_links[ $priority ] = array(); } if ( false === $key ) { $key = preg_replace( "/[^A-Za-z0-9 ]/", '', strtolower( $label ) ); } $this->_action_links[ $priority ][] = array( 'label' => $label, 'href' => $url, 'key' => $key, 'external' => $external ); } /** * Adds Upgrade and Add-Ons links to the main Plugins page link actions collection. * * @author Vova Feldman (@svovaf) * @since 1.0.0 */ function _add_upgrade_action_link() { $this->_logger->entrance(); $is_activation_mode = $this->is_activation_mode(); $add_action_links = $this->should_add_submenu_or_action_links( $is_activation_mode ); /** * The following logic is based on the logic in `add_submenu_items()` method that decides when the "Upgrade" * and "Add-Ons" menus should be added. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $add_upgrade_link = ( $add_action_links || ( $is_activation_mode && $this->is_only_premium() ) ) && ! WP_FS__DEMO_MODE && ( ! $this->is_whitelabeled() ); $add_addons_link = ( $add_action_links && $this->has_addons() ); if ( ! $add_upgrade_link && ! $add_addons_link ) { return; } if ( $add_upgrade_link && $this->is_pricing_page_visible() && $this->is_submenu_item_visible( 'pricing' ) ) { $this->add_plugin_action_link( $this->get_text_inline( 'Upgrade', 'upgrade' ), $this->get_upgrade_url(), false, 7, 'upgrade' ); } if ( $add_addons_link && $this->has_addons() && $this->is_submenu_item_visible( 'addons' ) ) { $this->add_plugin_action_link( $this->get_text_inline( 'Add-Ons', 'add-ons' ), $this->_get_admin_page_url( 'addons' ), false, 9, 'addons' ); } } /** * Adds "Activate License" or "Change License" link to the main Plugins page link actions collection. * * @author Leo Fajardo (@leorw) * @since 1.1.9 */ function _add_license_action_link() { $this->_logger->entrance(); if ( ! self::is_ajax() ) { // Inject license activation dialog UI and client side code. add_action( 'admin_footer', array( &$this, '_add_license_activation_dialog_box' ) ); } $link_text = $this->is_free_plan() ? $this->get_text_inline( 'Activate License', 'activate-license' ) : $this->get_text_inline( 'Change License', 'change-license' ); $this->add_plugin_action_link( $link_text, '#', false, 11, ( 'activate-license ' . $this->get_unique_affix() ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.0.2 */ function _add_premium_version_upgrade_selection_action() { $this->_logger->entrance(); if ( ! self::is_ajax() ) { add_action( 'admin_footer', array( &$this, '_add_premium_version_upgrade_selection_dialog_box' ) ); } } /** * Adds "Opt In" or "Opt Out" link to the main "Plugins" page link actions collection. * * @author Leo Fajardo (@leorw) * @since 1.2.1.5 */ function _add_tracking_links() { if ( ! current_user_can( 'manage_options' ) ) { return; } $this->_logger->entrance(); 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; } if ( $this->is_addon() && ! $this->is_only_premium() ) { $parent = $this->get_parent_instance(); if ( is_object( $parent ) && $parent->is_anonymous() ) { return; } } if ( fs_is_network_admin() ) { if ( ! $this->_is_network_active ) { // Don't add tracking links when browsing the network WP Admin and the plugin is not network active. return; } else if ( $this->is_network_delegated_connection() ) { // Don't add tracking links when browsing the network WP Admin and the activation has been delegated to site admins. return; } } else { if ( $this->_is_network_active && ! $this->is_delegated_connection() ) { // Don't add tracking links when browsing the sub-site WP Admin, the plugin is network active, and the connection was not delegated. return; } } if ( fs_request_is_action_secure( $this->get_unique_affix() . '_reconnect' ) ) { if ( ! $this->is_registered() && $this->is_anonymous() ) { $this->connect_again(); return; } } if ( ( $this->is_plugin() && ! self::is_plugins_page() ) || ( $this->is_theme() && ! self::is_themes_page() ) ) { // Only show tracking links on the plugins and themes pages. return; } if ( $this->is_activation_mode() && $this->is_premium() && ! $this->is_registered() ) { // If not yet registered and running the premium code base, a license activation link will already be shown. return; } if ( $this->is_registered() && $this->is_tracking_allowed() ) { if ( ! $this->is_premium() && ! $this->is_enable_anonymous() ) { // If opted in and tracking is allowed, don't allow to opt out if not premium and anonymous mode is disabled. return; } } if ( $this->add_ajax_action( 'toggle_permission_tracking', array( &$this, '_toggle_permission_tracking_callback' ) ) ) { return; } $link_text_id = ''; $url = '#'; 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' ); } } else if ( $this->is_anonymous() || $this->is_activation_mode() ) { /** * Show opt-in link only if skipped or in activation mode. */ $link_text_id = $this->get_text_inline( 'Opt In', 'opt-in' ); $params = ! $this->is_anonymous() ? array() : array( 'nonce' => wp_create_nonce( $this->get_unique_affix() . '_reconnect' ), 'fs_action' => ( $this->get_unique_affix() . '_reconnect' ), ); $url = $this->get_activation_url( $params ); } add_action( 'admin_footer', array( &$this, '_add_optout_dialog' ) ); if ( ! empty( $link_text_id ) && $this->is_plugin() && self::is_plugins_page() ) { $this->add_plugin_action_link( $link_text_id, $url, false, 13, "opt-in-or-opt-out {$this->_slug}" ); } } /** * Get the URL of the page that should be loaded right after the plugin activation. * * @author Vova Feldman (@svovaf) * @since 1.1.7.4 * * @return string */ function get_after_plugin_activation_redirect_url() { $url = false; if ( ! $this->is_addon() || ! $this->has_free_plan() ) { $first_time_path = $this->_menu->get_first_time_path( fs_is_network_admin() && $this->_is_network_active ); if ( $this->is_activation_mode() ) { $url = $this->get_activation_url(); } else if ( ! empty( $first_time_path ) ) { $url = $first_time_path; } else { $page = ''; if ( ! empty( $this->_dynamically_added_top_level_page_hook_name ) ) { if ( $this->is_network_registered() ) { $page = 'account'; } else if ( $this->is_pending_activation() || $this->is_network_anonymous() ) { $this->maybe_set_slug_and_network_menu_exists_flag(); } } $url = $this->_get_admin_page_url( $page ); } } else { $plugin_fs = false; if ( $this->is_parent_plugin_installed() ) { $plugin_fs = self::get_parent_instance(); } if ( is_object( $plugin_fs ) ) { if ( ! $plugin_fs->is_registered() ) { // Forward to parent plugin connect when parent not registered. $url = $plugin_fs->get_activation_url(); } else { // Forward to account page. $url = $plugin_fs->_get_admin_page_url( 'account' ); } } } return $url; } /** * Forward page to activation page. * * @author Vova Feldman (@svovaf) * @since 1.0.3 */ function _redirect_on_activation_hook() { if ( $this->apply_filters( 'redirect_on_activation', true ) ) { $url = $this->get_after_plugin_activation_redirect_url(); if ( is_string( $url ) ) { fs_redirect( $url ); } } } /** * Modify plugin's page action links collection. * * @author Vova Feldman (@svovaf) * @since 1.0.0 * * @param array $links * @param $file * * @return array */ function _modify_plugin_action_links_hook( $links, $file ) { $this->_logger->entrance(); $passed_deactivate = false; $deactivate_link = ''; $before_deactivate = array(); $after_deactivate = array(); foreach ( $links as $key => $link ) { if ( 'deactivate' === $key ) { $deactivate_link = $link; $passed_deactivate = true; continue; } if ( ! $passed_deactivate ) { $before_deactivate[ $key ] = $link; } else { $after_deactivate[ $key ] = $link; } } ksort( $this->_action_links ); foreach ( $this->_action_links as $new_links ) { foreach ( $new_links as $link ) { $before_deactivate[ $link['key'] ] = '' . $link['label'] . ''; } } if ( ! empty( $deactivate_link ) ) { /** * This HTML element is used to identify the correct plugin when attaching an event to its Deactivate link. * * @since 1.2.1.6 Always show the deactivation feedback form since we added automatic free version deactivation upon premium code activation. */ $deactivate_link .= ''; // Append deactivation link. $before_deactivate['deactivate'] = $deactivate_link; } return array_merge( $before_deactivate, $after_deactivate ); } /** * Adds admin message. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $message * @param string $title * @param string $type */ function add_admin_message( $message, $title = '', $type = 'success' ) { $this->_admin_notices->add( $message, $title, $type ); } /** * Adds sticky admin message. * * @author Vova Feldman (@svovaf) * @since 1.1.0 * * @param string $message * @param string $id * @param string $title * @param string $type */ function add_sticky_admin_message( $message, $id, $title = '', $type = 'success' ) { $this->_admin_notices->add_sticky( $message, $id, $title, $type ); } /** * Check if the paid version of the module is installed. * * @author Vova Feldman (@svovaf) * @since 2.2.0 * * @return bool */ private function is_premium_version_installed() { $premium_plugin_basename = $this->premium_plugin_basename(); 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 ) ); } /** * Helper function that returns the final steps for the upgrade completion. * * If the module is already running the premium code, returns an empty string. * * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @param string $plan_title * * @return string */ private function get_complete_upgrade_instructions( $plan_title = '' ) { $this->_logger->entrance(); $activate_license_string = $this->get_license_network_activation_notice(); if ( ! $this->has_premium_version() || $this->is_premium() ) { return '' . $activate_license_string; } if ( empty( $plan_title ) ) { $plan_title = $this->get_plan_title(); } if ( $this->is_premium_version_installed() ) { /** * If the premium version is already installed, instead of showing the installation instructions, * tell the current user to activate it. * * @author Leo Fajardo (@leorw) * @since 2.2.1 */ $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 */ $this->get_text_inline( ' The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s', 'activate-premium-version' ), sprintf( '%s', esc_html( $this->get_plugin_title() ) ), $plan_title, sprintf( '', ( $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' ), $plan_title ) ) ) ); } else { // @since 1.2.1.5 The free version is auto deactivated. $deactivation_step = version_compare( $this->version, '1.2.1.5', '<' ) ? ( '
  • ' . $this->esc_html_inline( 'Deactivate the free version', 'deactivate-free-version' ) . '.
  • ' ) : ''; return sprintf( ' %s:
    1. %s.
    2. %s
    3. %s (%s).
    ', $this->get_text_inline( 'Please follow these steps to complete the upgrade', 'follow-steps-to-complete-upgrade' ), ( empty( $activate_license_string ) ? '' : $activate_license_string . '
  • ' ) . $this->get_latest_download_link( sprintf( /* translators: %s: Plan title */ $this->get_text_inline( 'Download the latest %s version', 'download-latest-x-version' ), $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/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 * * @param string $url * @param array $request */ private static function enrich_request_for_debug( &$url, &$request ) { if ( WP_FS__DEBUG_SDK || isset( $_COOKIE['XDEBUG_SESSION'] ) ) { $url = add_query_arg( 'XDEBUG_SESSION_START', rand( 0, 9999999 ), $url ); $url = add_query_arg( 'XDEBUG_SESSION', 'PHPSTORM', $url ); $request['cookies'] = array( new WP_Http_Cookie( array( 'name' => 'XDEBUG_SESSION', 'value' => 'PHPSTORM', ) ) ); } } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 * * @param string $url * @param array $request * @param int $success_cache_expiration * @param int $failure_cache_expiration * @param bool $maybe_enrich_request_for_debug * * @return WP_Error|array */ static function safe_remote_post( &$url, $request, $success_cache_expiration = 0, $failure_cache_expiration = 0, $maybe_enrich_request_for_debug = true ) { $should_cache = ($success_cache_expiration + $failure_cache_expiration > 0); $cache_key = $should_cache ? md5( fs_strip_url_protocol($url) . json_encode( $request ) ) : false; $response = (!WP_FS__DEBUG_SDK && ( false !== $cache_key )) ? get_transient( $cache_key ) : false; if ( false === $response ) { if ( $maybe_enrich_request_for_debug ) { self::enrich_request_for_debug( $url, $request ); } if ( ! isset( $request['method'] ) ) { $request['method'] = 'POST'; } $response = FS_Api::remote_request( $url, $request ); 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 = FS_Api::remote_request( $url, $request ); } if ( false !== $cache_key ) { set_transient( $cache_key, $response, ( ( $response instanceof WP_Error ) ? $failure_cache_expiration : $success_cache_expiration ) ); } } return $response; } /** * This method is used to enrich the after upgrade notice instructions when the upgraded * license cannot be activated network wide (license quota isn't large enough). * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ private function get_license_network_activation_notice() { if ( ! $this->_is_network_active ) { // Module isn't network level activated. return ''; } if ( ! fs_is_network_admin() ) { // Not network level admin. return ''; } if ( get_blog_count() == 1 ) { // There's only a single site in the network so if there's a context license it was already activated. return ''; } if ( ! is_object( $this->_license ) ) { // No context license. return ''; } if ( $this->_license->is_single_site() && 0 < $this->_license->activated ) { // License was already utilized (this is not 100% the case if all the network is localhost sites and the license can be utilized on unlimited localhost sites). return ''; } if ( $this->can_activate_license_on_network( $this->_license ) ) { // License can be activated on all the network, so probably, the license is already activate on all the network (that's how the after upgrade sync works). return ''; } return sprintf( $this->get_text_inline( '%sClick here%s to choose the sites where you\'d like to activate the license on.', 'network-choose-sites-for-license' ), '', '' ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $key * * @return string */ function get_text( $key ) { return fs_text( $key, $this->_slug ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * * @return string */ function get_text_inline( $text, $key = '' ) { return _fs_text_inline( $text, $key, $this->_slug ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * * @return string */ function get_text_x_inline( $text, $context, $key ) { return _fs_text_x_inline( $text, $context, $key, $this->_slug ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * * @return string */ function esc_html_inline( $text, $key ) { return esc_html( _fs_text_inline( $text, $key, $this->_slug ) ); } #---------------------------------------------------------------------------------- #region Versioning #---------------------------------------------------------------------------------- /** * Check if Freemius in SDK upgrade mode. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_sdk_upgrade_mode() { return isset( $this->_storage->sdk_upgrade_mode ) ? $this->_storage->sdk_upgrade_mode : false; } /** * Turn SDK upgrade mode off. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ function set_sdk_upgrade_complete() { $this->_storage->sdk_upgrade_mode = false; } /** * Check if plugin upgrade mode. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_plugin_upgrade_mode() { return isset( $this->_storage->plugin_upgrade_mode ) ? $this->_storage->plugin_upgrade_mode : false; } /** * Turn plugin upgrade mode off. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ function set_plugin_upgrade_complete() { $this->_storage->plugin_upgrade_mode = false; $license_migration = ! empty( $this->_storage->license_migration ) ? $this->_storage->license_migration : array(); $license_migration['is_migrating'] = false; $this->_storage->license_migration = $license_migration; } #endregion #---------------------------------------------------------------------------------- #region Permissions #---------------------------------------------------------------------------------- /** * Check if specific permission requested. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $permission * * @return bool */ function is_permission_requested( $permission ) { return isset( $this->_permissions[ $permission ] ) && ( true === $this->_permissions[ $permission ] ); } #endregion #---------------------------------------------------------------------------------- #region Auto Activation #---------------------------------------------------------------------------------- /** * Hints the SDK if running an auto-installation. * * @var bool */ private $_isAutoInstall = false; /** * After upgrade callback to install and auto activate a plugin. * This code will only be executed on explicit request from the user, * following the practice Jetpack are using with their theme installations. * * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/ * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 */ function _install_premium_version_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'install_premium_version' ); if ( ! $this->is_registered() ) { // Not registered. self::shoot_ajax_failure( array( 'message' => $this->get_text_inline( 'Auto installation only works for opted-in users.', 'auto-install-error-not-opted-in' ), 'code' => 'premium_installed', ) ); } $plugin_id = fs_request_get( 'target_module_id', $this->get_id() ); if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { // Invalid ID. self::shoot_ajax_failure( array( 'message' => $this->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), 'code' => 'invalid_module_id', ) ); } if ( $plugin_id == $this->get_id() ) { if ( $this->is_premium() ) { // Already using the premium code version. self::shoot_ajax_failure( array( 'message' => $this->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ), 'code' => 'premium_installed', ) ); } if ( ! $this->can_use_premium_code() ) { // Don't have access to the premium code. self::shoot_ajax_failure( array( 'message' => $this->get_text_inline( 'You do not have a valid license to access the premium version.', 'auto-install-error-invalid-license' ), 'code' => 'invalid_license', ) ); } if ( ! $this->has_release_on_freemius() ) { // Plugin is a serviceware, no premium code version. self::shoot_ajax_failure( array( 'message' => $this->get_text_inline( 'Plugin is a "Serviceware" which means it does not have a premium code version.', 'auto-install-error-serviceware' ), 'code' => 'premium_version_missing', ) ); } } else { $addon = $this->get_addon( $plugin_id ); if ( ! is_object( $addon ) ) { // Invalid add-on ID. self::shoot_ajax_failure( array( 'message' => $this->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), 'code' => 'invalid_module_id', ) ); } if ( $this->is_addon_activated( $plugin_id, true ) ) { // Premium add-on version is already activated. self::shoot_ajax_failure( array( 'message' => $this->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ), 'code' => 'premium_installed', ) ); } } $this->_isAutoInstall = true; // Try to install and activate. $updater = FS_Plugin_Updater::instance( $this ); $result = $updater->install_and_activate_plugin( $plugin_id ); if ( is_array( $result ) && ! empty( $result['message'] ) ) { self::shoot_ajax_failure( array( 'message' => $result['message'], 'code' => $result['code'], ) ); } self::shoot_ajax_success( $result ); } /** * Displays module activation dialog box after a successful upgrade * where the user explicitly requested to auto download and install * the premium version. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 */ function _add_auto_installation_dialog_box() { $this->_logger->entrance(); if ( ! $this->is_registered() ) { // Not registered. return; } $plugin_id = fs_request_get( 'plugin_id', $this->get_id() ); if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { // Invalid module ID. return; } if ( $plugin_id == $this->get_id() ) { if ( $this->is_premium() ) { // Already using the premium code version. return; } if ( ! $this->can_use_premium_code() ) { // Don't have access to the premium code. return; } if ( ! $this->has_release_on_freemius() ) { // Plugin is a serviceware, no premium code version. return; } } else { $addon = $this->get_addon( $plugin_id ); if ( ! is_object( $addon ) ) { // Invalid add-on ID. return; } if ( $this->is_addon_activated( $plugin_id, true ) ) { // Premium add-on version is already activated. return; } } $vars = array( 'id' => $this->_module_id, 'target_module_id' => $plugin_id, 'slug' => $this->_slug, ); fs_require_template( 'auto-installation.php', $vars ); } #endregion #-------------------------------------------------------------------------------- #region Tabs Integration #-------------------------------------------------------------------------------- #region Module's Original Tabs /** * Inject a JavaScript logic to capture the theme tabs HTML. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ function _tabs_capture() { $this->_logger->entrance(); if ( ! $this->is_product_settings_page() || ! $this->should_page_include_tabs() || ! $this->is_matching_url( $this->main_menu_url() ) ) { return; } $params = array( 'id' => $this->_module_id, ); fs_require_once_template( 'tabs-capture-js.php', $params ); } /** * Cache theme's tabs HTML for a week. The cache will also be set as expired * after version and type (free/premium) changes, in addition to the week period. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ function _store_tabs_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'store_tabs' ); // Init filesystem if not yet initiated. WP_Filesystem(); // Get POST body HTML data. global $wp_filesystem; $tabs_html = $wp_filesystem->get_contents( "php://input" ); if ( is_string( $tabs_html ) ) { $tabs_html = trim( $tabs_html ); } if ( ! is_string( $tabs_html ) || empty( $tabs_html ) ) { self::shoot_ajax_failure(); } $this->_cache->set( 'tabs', $tabs_html, 7 * WP_FS__TIME_24_HOURS_IN_SEC ); self::shoot_ajax_success(); } /** * Cache theme's settings page custom styles. The cache will also be set as expired * after version and type (free/premium) changes, in addition to the week period. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ function _store_tabs_styles() { $this->_logger->entrance(); if ( ! $this->is_product_settings_page() || ! $this->should_page_include_tabs() || ! $this->is_matching_url( $this->main_menu_url() ) ) { return; } $wp_styles = wp_styles(); $theme_styles_url = get_template_directory_uri(); $stylesheets = array(); foreach ( $wp_styles->queue as $handler ) { if ( fs_starts_with( $handler, 'fs_' ) ) { // Assume that stylesheets that their handler starts with "fs_" belong to the SDK. continue; } /** * @var _WP_Dependency $stylesheet */ $stylesheet = $wp_styles->registered[ $handler ]; if ( fs_starts_with( $stylesheet->src, $theme_styles_url ) ) { $stylesheets[] = $stylesheet->src; } } if ( ! empty( $stylesheets ) ) { $this->_cache->set( 'tabs_stylesheets', $stylesheets, 7 * WP_FS__TIME_24_HOURS_IN_SEC ); } } /** * Check if module's original settings page has any tabs. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return bool */ private function has_tabs() { return $this->_cache->has( 'tabs' ); } /** * Get module's settings page HTML content, starting * from the beginning of the
    element, * until the tabs HTML (including). * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return string */ private function get_tabs_html() { $this->_logger->entrance(); return $this->_cache->get( 'tabs' ); } /** * Check if page should include tabs. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return bool */ private function should_page_include_tabs() { if ( ! $this->has_settings_menu() ) { // Don't add tabs if no settings at all. return false; } if ( self::NAVIGATION_TABS !== $this->_navigation ) { // Only add tabs to themes for now. return false; } if ( $this->is_theme() && ! $this->has_paid_plan() && ! $this->has_addons() ) { // Only add tabs to monetizing themes. return false; } if ( ! $this->is_product_settings_page() ) { // Only add tabs if browsing one of the product's setting pages. return false; } if ( $this->is_activation_mode() && $this->is_activation_page() ) { // Don't include tabs in the activation page. return false; } if ( $this->is_admin_page( 'pricing' ) && fs_request_get_bool( 'checkout' ) ) { // Don't add tabs on checkout page, we want to reduce distractions // as much as possible. return false; } return true; } /** * Add the tabs HTML before the setting's page content and * enqueue any required stylesheets. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return bool If tabs were included. */ function _add_tabs_before_content() { $this->_logger->entrance(); if ( ! $this->should_page_include_tabs() ) { return false; } /** * Enqueue the original stylesheets that are included in the * theme settings page. That way, if the theme settings has * some custom _styled_ content above the tabs UI, this * will make sure that the styling is preserved. */ $stylesheets = $this->_cache->get( 'tabs_stylesheets', array() ); if ( is_array( $stylesheets ) ) { for ( $i = 0, $len = count( $stylesheets ); $i < $len; $i ++ ) { wp_enqueue_style( "fs_{$this->_module_id}_tabs_{$i}", $stylesheets[ $i ] ); } } // Cut closing
    tag. echo substr( trim( $this->get_tabs_html() ), 0, - 6 ); return true; } /** * Add the tabs closing HTML after the setting's page content. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return bool If tabs closing HTML was included. */ function _add_tabs_after_content() { $this->_logger->entrance(); if ( ! $this->should_page_include_tabs() ) { return false; } echo ''; return true; } #endregion /** * Add in-page JavaScript to inject the Freemius tabs into * the module's setting tabs section. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ function _add_freemius_tabs() { $this->_logger->entrance(); if ( ! $this->should_page_include_tabs() ) { return; } $params = array( 'id' => $this->_module_id ); fs_require_once_template( 'tabs.php', $params ); } #endregion #-------------------------------------------------------------------------------- #region Customizer Integration for Themes #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @param WP_Customize_Manager $customizer */ function _customizer_register( $customizer ) { $this->_logger->entrance(); if ( $this->is_pricing_page_visible() ) { require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-upsell-control.php'; $customizer->add_section( 'freemius_upsell', array( 'title' => '★ ' . $this->get_text_inline( 'View paid features', 'view-paid-features' ), 'priority' => 1, ) ); $customizer->add_setting( 'freemius_upsell', array( 'sanitize_callback' => 'esc_html', ) ); $customizer->add_control( new FS_Customizer_Upsell_Control( $customizer, 'freemius_upsell', array( 'fs' => $this, 'section' => 'freemius_upsell', 'priority' => 100, ) ) ); } if ( $this->is_page_visible( 'contact' ) || $this->is_page_visible( 'support' ) ) { require_once WP_FS__DIR_INCLUDES . '/customizer/class-fs-customizer-support-section.php'; // Main Documentation Link In Customizer Root. $customizer->add_section( new FS_Customizer_Support_Section( $customizer, 'freemius_support', array( 'fs' => $this, 'priority' => 1000, ) ) ); } } #endregion /** * If the theme has a paid version, add some custom * styling to the theme's premium version (if exists) * to highlight that it's the premium version of the * same theme, making it easier for identification * after the user upgrades and upload it to the site. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ function _style_premium_theme() { $this->_logger->entrance(); if ( ! self::is_themes_page() ) { // Only include in the themes page. return; } if ( ! $this->has_paid_plan() ) { // Only include if has any paid plans. return; } $params = null; fs_require_once_template( '/js/jquery.content-change.php', $params ); $params = array( 'slug' => $this->_slug, 'id' => $this->_module_id, ); fs_require_template( '/js/style-premium-theme.php', $params ); } /** * This method will return the absolute URL of the module's local icon. * * When you are running your plugin or theme on a **localhost** environment, if the icon * is not found in the local assets folder, try to fetch the icon URL from Freemius. If not set and * it's a plugin hosted on WordPress.org, try fetching the icon URL from wordpress.org. * If an icon is found, this method will automatically attempt to download the icon and store it * in /freemius/assets/img/{slug}.{png|jpg|gif|svg}. * * It's important to mention that this method is NOT phoning home since the developer will deploy * the product with the local icon in the assets folder. The download process just simplifies * the process for the developer. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function get_local_icon_url() { global $fs_active_plugins; /** * @since 1.1.7.5 */ $local_path = $this->apply_filters( 'plugin_icon', false ); if ( is_string( $local_path ) ) { $icons = array( $local_path ); } else { $img_dir = WP_FS__DIR_IMG; // Locate the main assets folder. if ( 1 < count( $fs_active_plugins->plugins ) ) { $plugin_or_theme_img_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) ); foreach ( $fs_active_plugins->plugins as $sdk_path => &$data ) { if ( $data->plugin_path == $this->get_plugin_basename() ) { $img_dir = $plugin_or_theme_img_dir . '/' /** * The basename will be `themes` or the basename of a custom themes directory. * * @author Leo Fajardo (@leorw) * @since 2.2.3 */ . str_replace( '../' . basename( $plugin_or_theme_img_dir ) . '/', '', $sdk_path ) . '/assets/img'; break; } } } // Try to locate the icon in the assets folder. $icons = glob( fs_normalize_path( $img_dir . "/{$this->_slug}.*" ) ); if ( ! is_array( $icons ) || 0 === count( $icons ) ) { if ( ! WP_FS__IS_LOCALHOST && $this->is_theme() ) { $icons = array( fs_normalize_path( $img_dir . '/theme-icon.png' ) ); } else { $icon_found = false; $local_path = fs_normalize_path( "{$img_dir}/{$this->_slug}.png" ); if ( ! function_exists( 'get_filesystem_method' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } $have_write_permissions = ( 'direct' === get_filesystem_method( array(), fs_normalize_path( $img_dir ) ) ); /** * IMPORTANT: THIS CODE WILL NEVER RUN AFTER THE PLUGIN IS IN THE REPO. * * This code will only be executed once during the testing * of the plugin in a local environment. The plugin icon file WILL * already exist in the assets folder when the plugin is deployed to * the repository. */ if ( WP_FS__IS_LOCALHOST && $have_write_permissions ) { // Fetch icon from Freemius. $icon = $this->fetch_remote_icon_url(); // Fetch icon from WordPress.org. if ( empty( $icon ) && $this->is_plugin() && $this->is_org_repo_compliant() ) { if ( ! function_exists( 'plugins_api' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; } $plugin_information = plugins_api( 'plugin_information', array( 'slug' => $this->_slug, 'fields' => array( 'sections' => false, 'tags' => false, 'icons' => true ) ) ); if ( ! is_wp_error( $plugin_information ) && isset( $plugin_information->icons ) && ! empty( $plugin_information->icons ) ) { /** * Get the smallest icon. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ $icon = end( $plugin_information->icons ); } } if ( ! empty( $icon ) ) { if ( 0 !== strpos( $icon, 'http' ) ) { $icon = 'http:' . $icon; } /** * Get a clean file extension, e.g.: "jpg" and not "jpg?rev=1305765". * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ $ext = pathinfo( strtok( $icon, '?' ), PATHINFO_EXTENSION ); $local_path = fs_normalize_path( "{$img_dir}/{$this->_slug}.{$ext}" ); // Try to download the icon. $icon_found = fs_download_image( $icon, $local_path ); } } if ( ! $icon_found ) { // No icons found, fallback to default icon. if ( $have_write_permissions ) { // If have write permissions, copy default icon. copy( fs_normalize_path( $img_dir . "/{$this->_module_type}-icon.png" ), $local_path ); } else { // If doesn't have write permissions, use default icon path. $local_path = fs_normalize_path( $img_dir . "/{$this->_module_type}-icon.png" ); } } $icons = array( $local_path ); } } } $icon_dir = dirname( $icons[0] ); return fs_img_url( substr( $icons[0], strlen( $icon_dir ) ), $icon_dir ); } /** * Fetch module's extended info. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return object|mixed */ private function fetch_module_info() { return $this->get_api_plugin_scope()->get( 'info.json', false, WP_FS__TIME_WEEK_IN_SEC ); } /** * Fetch module's remote icon URL. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function fetch_remote_icon_url() { $info = $this->fetch_module_info(); return ( $this->is_api_result_object( $info, 'icon' ) && is_string( $info->icon ) ) ? $info->icon : ''; } #-------------------------------------------------------------------------------- #region GDPR #-------------------------------------------------------------------------------- /** * @author Leo Fajardo (@leorw) * @since 2.1.0 * * @param array $user_plugins * * @return string */ private function get_gdpr_admin_notice_string( $user_plugins ) { $this->_logger->entrance(); $addons = self::get_all_addons(); foreach ( $user_plugins as $user_plugin ) { $has_addons = isset( $addons[ $user_plugin->id ] ); if ( WP_FS__MODULE_TYPE_PLUGIN === $user_plugin->type && ! $has_addons ) { if ( $this->_module_id == $user_plugin->id ) { $addons = $this->get_addons(); $has_addons = ( ! empty( $addons ) ); } else { $plugin_api = FS_Api::instance( $user_plugin->id, 'plugin', $user_plugin->id, $user_plugin->public_key, ! $user_plugin->is_live, false, $this->get_sdk_version() ); $addons_result = $plugin_api->get( '/addons.json?enriched=true', true ); if ( $this->is_api_result_object( $addons_result, 'plugins' ) && is_array( $addons_result->plugins ) && ! empty( $addons_result->plugins ) ) { $has_addons = true; } } } $user_plugin->has_addons = $has_addons; } $is_single_parent_product = ( 1 === count( $user_plugins ) ); $multiple_products_text = ''; if ( $is_single_parent_product ) { $single_parent_product = reset( $user_plugins ); $thank_you = sprintf( "%s", $single_parent_product->id, sprintf( $single_parent_product->has_addons ? $this->get_text_inline( 'Thank you so much for using %s and its add-ons!', 'thank-you-for-using-product-and-its-addons' ) : $this->get_text_inline( 'Thank you so much for using %s!', 'thank-you-for-using-product' ), sprintf('%s', $single_parent_product->title) ) ); $already_opted_in = sprintf( $this->get_text_inline( "You've already opted-in to our usage-tracking, which helps us keep improving the %s.", 'already-opted-in-to-product-usage-tracking' ), ( WP_FS__MODULE_TYPE_THEME === $single_parent_product->type ) ? WP_FS__MODULE_TYPE_THEME : WP_FS__MODULE_TYPE_PLUGIN ); } else { $thank_you = $this->get_text_inline( 'Thank you so much for using our products!', 'thank-you-for-using-products' ); $already_opted_in = $this->get_text_inline( "You've already opted-in to our usage-tracking, which helps us keep improving them.", 'already-opted-in-to-products-usage-tracking' ); $products_and_add_ons = ''; foreach ( $user_plugins as $user_plugin ) { if ( ! empty( $products_and_add_ons ) ) { $products_and_add_ons .= ', '; } if ( ! $user_plugin->has_addons ) { $products_and_add_ons .= sprintf( "%s", $user_plugin->id, $user_plugin->title ); } else { $products_and_add_ons .= sprintf( "%s", $user_plugin->id, sprintf( $this->get_text_inline( '%s and its add-ons', 'product-and-its-addons' ), $user_plugin->title ) ); } } $multiple_products_text = sprintf( "%s: %s", $this->get_text_inline( 'Products', 'products' ), $products_and_add_ons ); } $actions = sprintf( '
    • %s - %s
    • %s - %s
    ', sprintf('', $this->get_text_inline( 'Yes', 'yes' ) ), $this->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ), sprintf('', $this->get_text_inline( 'No', 'no' ) ), sprintf( $this->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), '', '' ) ); return sprintf( '%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' ), '', '' ) . '

    ' . '' . $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 . ( $is_single_parent_product ? '' : $multiple_products_text ) ); } /** * This method is called for opted-in users to fetch the is_marketing_allowed flag of the user for all the * plugins and themes they've opted in to. * * @author Leo Fajardo (@leorw) * @since 2.1.0 * * @param string $user_email * @param string $license_key * @param array $plugin_ids * @param string|null $license_key * * @return array|false */ private function fetch_user_marketing_flag_status_by_plugins( $user_email, $license_key, $plugin_ids ) { $request = array( 'method' => 'POST', 'body' => array(), 'timeout' => WP_FS__DEBUG_SDK ? 60 : 30, ); if ( is_string( $user_email ) ) { $request['body']['email'] = $user_email; } else { $request['body']['license_key'] = $license_key; } $result = array(); $url = WP_FS__ADDRESS . '/action/service/user_plugin/'; $total_plugin_ids = count( $plugin_ids ); $plugin_ids_count_per_request = 10; for ( $i = 1; $i <= $total_plugin_ids; $i += $plugin_ids_count_per_request ) { $plugin_ids_set = array_slice( $plugin_ids, $i - 1, $plugin_ids_count_per_request ); $request['body']['plugin_ids'] = $plugin_ids_set; $response = self::safe_remote_post( $url, $request, WP_FS__TIME_24_HOURS_IN_SEC, WP_FS__TIME_12_HOURS_IN_SEC ); if ( ! is_wp_error( $response ) ) { $decoded = is_string( $response['body'] ) ? json_decode( $response['body'] ) : null; if ( !is_object($decoded) || !isset($decoded->success) || true !== $decoded->success || !isset( $decoded->data ) || !is_array( $decoded->data ) ) { return false; } $result = array_merge( $result, $decoded->data ); } } return $result; } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function _maybe_show_gdpr_admin_notice() { if ( ! $this->is_user_in_admin() ) { return; } if ( ! $this->should_handle_gdpr_admin_notice() ) { return; } if ( ! $this->is_user_admin() ) { return; } require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; $lock = FS_User_Lock::instance(); /** * Try to acquire a 60-sec lock based on the WP user and thread/process ID. */ if ( ! $lock->try_lock( 60 ) ) { return; } /** * @var $current_wp_user WP_User */ $current_wp_user = self::_get_current_wp_user(); /** * @var FS_User $current_fs_user */ $current_fs_user = Freemius::_get_user_by_email( $current_wp_user->user_email ); $ten_years_in_sec = 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC; if ( ! is_object( $current_fs_user ) ) { // 10-year lock. $lock->lock( $ten_years_in_sec ); return; } $gdpr = FS_GDPR_Manager::instance(); if ( $gdpr->is_opt_in_notice_shown() ) { // 30-day lock. $lock->lock( 30 * WP_FS__TIME_24_HOURS_IN_SEC ); return; } if ( ! $gdpr->should_show_opt_in_notice() ) { // 10-year lock. $lock->lock( $ten_years_in_sec ); return; } $last_time_notice_shown = $gdpr->last_time_notice_was_shown(); $was_notice_shown_before = ( false !== $last_time_notice_shown ); if ( $was_notice_shown_before && 30 * WP_FS__TIME_24_HOURS_IN_SEC > time() - $last_time_notice_shown ) { // If the notice was shown before, show it again after 30 days from the last time it was shown. return; } /** * Find all plugin IDs that were installed by the current admin. */ $plugin_ids_map = self::get_user_opted_in_module_ids_map( $current_fs_user->id ); if ( empty( $plugin_ids_map )) { $lock->lock( $ten_years_in_sec ); return; } $user_plugins = $this->fetch_user_marketing_flag_status_by_plugins( $current_fs_user->email, null, array_keys( $plugin_ids_map ) ); if ( empty( $user_plugins ) ) { $lock->lock( is_array($user_plugins) ? $ten_years_in_sec : // Lock for 24-hours on errors. WP_FS__TIME_24_HOURS_IN_SEC ); return; } $has_unset_marketing_optin = false; foreach ( $user_plugins as $user_plugin ) { if ( true == $user_plugin->is_marketing_allowed ) { unset( $plugin_ids_map[ $user_plugin->plugin_id ] ); } if ( ! $has_unset_marketing_optin && is_null( $user_plugin->is_marketing_allowed ) ) { $has_unset_marketing_optin = true; } } if ( empty( $plugin_ids_map ) || ( $was_notice_shown_before && ! $has_unset_marketing_optin ) ) { $lock->lock( $ten_years_in_sec ); return; } $modules = array_merge( array_values( self::maybe_get_entities_account_option( 'plugins', array() ) ), array_values( self::maybe_get_entities_account_option( 'themes', array() ) ) ); foreach ( $modules as $module ) { if ( ! FS_Plugin::is_valid_id( $module->parent_plugin_id ) && isset( $plugin_ids_map[ $module->id ] ) ) { $plugin_ids_map[ $module->id ] = $module; } } $plugin_title = null; if ( 1 === count( $plugin_ids_map ) ) { $module = reset( $plugin_ids_map ); $plugin_title = $module->title; } $gdpr->add_opt_in_sticky_notice( $this->get_gdpr_admin_notice_string( $plugin_ids_map ), $plugin_title ); $this->add_gdpr_optin_ajax_handler_and_style(); $gdpr->notice_was_just_shown(); // 30-day lock. $lock->lock( 30 * WP_FS__TIME_24_HOURS_IN_SEC ); } /** * Prevents the GDPR opt-in admin notice from being added if the user has already chosen to allow or not allow * marketing. * * @author Leo Fajardo (@leorw) * @since 2.1.0 */ private function disable_opt_in_notice_and_lock_user() { FS_GDPR_Manager::instance()->disable_opt_in_notice(); require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; // 10-year lock. 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 */ function _add_gdpr_optin_js() { $vars = array( 'id' => $this->_module_id ); fs_require_once_template( 'gdpr-optin-js.php', $vars ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function enqueue_gdpr_optin_notice_style() { fs_enqueue_local_style( 'fs_gdpr_optin_notice', '/admin/gdpr-optin-notice.css' ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function _maybe_add_gdpr_optin_ajax_handler() { $this->add_ajax_action( 'fetch_is_marketing_required_flag_value', array( &$this, '_fetch_is_marketing_required_flag_value_ajax_action' ) ); if ( FS_GDPR_Manager::instance()->is_opt_in_notice_shown() ) { $this->add_gdpr_optin_ajax_handler_and_style(); } } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function _fetch_is_marketing_required_flag_value_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'fetch_is_marketing_required_flag_value' ); $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' ) ); } $user_plugins = $this->fetch_user_marketing_flag_status_by_plugins( null, $license_key, array( $this->_module_id ) ); if ( ! is_array( $user_plugins ) || empty($user_plugins) || !isset($user_plugins[0]->plugin_id) || $user_plugins[0]->plugin_id != $this->_module_id ) { /** * If faced an error or if the module ID do not match to the current module, ask for GDPR opt-in. * * @author Vova Feldman (@svovaf) */ self::shoot_ajax_success( array( 'is_marketing_allowed' => null, 'license_owner_id' => null ) ); } self::shoot_ajax_success( array( 'is_marketing_allowed' => $user_plugins[0]->is_marketing_allowed, 'license_owner_id' => ( isset( $user_plugins[0]->license_owner_id ) ? $user_plugins[0]->license_owner_id : null ) ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.2 * * @param number[] $install_ids * * @return array { * An array of objects containing the installs' licenses owners data. * * @property number $id User ID. * @property string $email User email (can be masked email). * } */ private function fetch_installs_licenses_owners_data( $install_ids ) { $this->_logger->entrance(); $response = $this->get_api_user_scope()->get( '/licenses_owners.json?install_ids=' . implode( ',', $install_ids ) ); $license_owners = array(); if ( $this->is_api_result_object( $response, 'owners' ) ) { $license_owners = $response->owners; } return $license_owners; } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ private function add_gdpr_optin_ajax_handler_and_style() { // Add GDPR action AJAX callback. $this->add_ajax_action( 'gdpr_optin_action', array( &$this, '_gdpr_optin_ajax_action' ) ); add_action( 'admin_footer', array( &$this, '_add_gdpr_optin_js' ) ); add_action( 'admin_enqueue_scripts', array( &$this, 'enqueue_gdpr_optin_notice_style' ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 */ function _gdpr_optin_ajax_action() { $this->_logger->entrance(); $this->check_ajax_referer( 'gdpr_optin_action' ); if ( ! fs_request_has( 'is_marketing_allowed' ) || ! fs_request_has( 'plugin_ids' ) ) { self::shoot_ajax_failure(); } $current_wp_user = self::_get_current_wp_user(); $plugin_ids = fs_request_get( 'plugin_ids', array() ); if ( ! is_array( $plugin_ids ) || empty( $plugin_ids ) ) { self::shoot_ajax_failure(); } $modules = array_merge( array_values( self::maybe_get_entities_account_option( 'plugins', array() ) ), array_values( self::maybe_get_entities_account_option( 'themes', array() ) ) ); foreach ( $modules as $key => $module ) { if ( ! in_array( $module->id, $plugin_ids ) ) { unset( $modules[ $key ] ); } } if ( empty( $modules ) ) { self::shoot_ajax_failure(); } $user_api = $this->get_api_user_scope_by_user( Freemius::_get_user_by_email( $current_wp_user->user_email ) ); foreach ( $modules as $module ) { $user_api->call( "?plugin_id={$module->id}", 'put', array( 'is_marketing_allowed' => ( true == fs_request_get_bool( 'is_marketing_allowed' ) ) ) ); } FS_GDPR_Manager::instance()->remove_opt_in_notice(); require_once WP_FS__DIR_INCLUDES . '/class-fs-user-lock.php'; // 10-year lock. FS_User_Lock::instance()->lock( 10 * 365 * WP_FS__TIME_24_HOURS_IN_SEC ); self::shoot_ajax_success(); } /** * Checks if the GDPR admin notice should be handled. By default, this logic is off, unless the integrator adds the special 'handle_gdpr_admin_notice' filter. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @return bool */ private function should_handle_gdpr_admin_notice() { return $this->apply_filters( 'handle_gdpr_admin_notice', // Default to false. false ); } #endregion #---------------------------------------------------------------------------------- #region Marketing #---------------------------------------------------------------------------------- /** * Check if current user purchased any other plugins before. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function has_purchased_before() { // TODO: Implement has_purchased_before() method. throw new Exception( 'not implemented' ); } /** * Check if current user classified as an agency. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_agency() { // TODO: Implement is_agency() method. throw new Exception( 'not implemented' ); } /** * Check if current user classified as a developer. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_developer() { // TODO: Implement is_developer() method. throw new Exception( 'not implemented' ); } /** * Check if current user classified as a business. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_business() { // TODO: Implement is_business() method. throw new Exception( 'not implemented' ); } #endregion #---------------------------------------------------------------------------------- #region Helper #---------------------------------------------------------------------------------- /** * If running with a secret key, assume it's the developer and show pending plans as well. * * @author Vova Feldman (@svovaf) * @since 2.1.2 * * @param string $path * * @return string */ function add_show_pending( $path ) { if ( ! $this->has_secret_key() ) { return $path; } return $path . ( false !== strpos( $path, '?' ) ? '&' : '?' ) . 'show_pending=true'; } #endregion } PK! 6X  (freemius/includes/class-fs-user-lock.phpnu[_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. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @param int $expiration * * @return bool TRUE if successfully acquired lock. */ function try_lock( $expiration = 0 ) { return $this->_lock->try_lock( $expiration ); } /** * Acquire lock regardless if it's already acquired by another locker or not. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @param int $expiration */ function lock( $expiration = 0 ) { $this->_lock->lock( $expiration ); } /** * Unlock the lock. * * @author Vova Feldman (@svovaf) * @since 2.1.0 */ function unlock() { $this->_lock->unlock(); } }PK!qCC%freemius/includes/class-fs-logger.phpnu[_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} ( `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, `process_id` INT UNSIGNED NOT NULL, `user_name` VARCHAR(64) NOT NULL, `logger` VARCHAR(128) NOT NULL, `log_order` INT UNSIGNED NOT NULL, `type` ENUM('log','info','warn','error') NOT NULL DEFAULT 'log', `message` TEXT NOT NULL, `file` VARCHAR(256) NOT NULL, `line` INT UNSIGNED NOT NULL, `function` VARCHAR(256) NOT NULL, `request_type` ENUM('call','ajax','cron') NOT NULL DEFAULT 'call', `request_url` VARCHAR(1024) NOT NULL, `created` DECIMAL(16, 6) NOT NULL, PRIMARY KEY (`id`), 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'; } 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 } PK!p2WW!freemius/includes/debug/index.phpnu[title( 'Freemius' ); } static function requests_count() { if ( class_exists( 'Freemius_Api_WordPress' ) ) { $logger = Freemius_Api_WordPress::GetLogger(); } else { $logger = array(); } return number_format( count( $logger ) ); } static function total_time() { 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( 100 * $total_time, 2 ) . ' ' . fs_text_x_inline( 'ms', 'milliseconds' ); } function render() { ?>



    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {description} Copyright (C) {year} {fullname} This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. PK!p2WWfreemius/includes/sdk/index.phpnu[_id = $pID; $this->_public = $pPublic; $this->_secret = $pSecret; $this->_scope = $pScope; $this->_isSandbox = $pIsSandbox; } public function IsSandbox() { return $this->_isSandbox; } function CanonizePath( $pPath ) { $pPath = trim( $pPath, '/' ); $query_pos = strpos( $pPath, '?' ); $query = ''; if ( false !== $query_pos ) { $query = substr( $pPath, $query_pos ); $pPath = substr( $pPath, 0, $query_pos ); } // Trim '.json' suffix. $format_length = strlen( '.' . self::FORMAT ); $start = $format_length * ( - 1 ); //negative if ( substr( strtolower( $pPath ), $start ) === ( '.' . self::FORMAT ) ) { $pPath = substr( $pPath, 0, strlen( $pPath ) - $format_length ); } switch ( $this->_scope ) { case 'app': $base = '/apps/' . $this->_id; break; case 'developer': $base = '/developers/' . $this->_id; break; case 'user': $base = '/users/' . $this->_id; break; case 'plugin': $base = '/plugins/' . $this->_id; break; case 'install': $base = '/installs/' . $this->_id; break; default: throw new Freemius_Exception( 'Scope not implemented.' ); } return '/v' . FS_API__VERSION . $base . ( ! empty( $pPath ) ? '/' : '' ) . $pPath . ( ( false === strpos( $pPath, '.' ) ) ? '.' . self::FORMAT : '' ) . $query; } abstract function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array() ); /** * @param string $pPath * @param string $pMethod * @param array $pParams * * @return object[]|object|null */ private function _Api( $pPath, $pMethod = 'GET', $pParams = array() ) { $pMethod = strtoupper( $pMethod ); try { $result = $this->MakeRequest( $pPath, $pMethod, $pParams ); } catch ( Freemius_Exception $e ) { // Map to error object. $result = (object) $e->getResult(); } catch ( Exception $e ) { // Map to error object. $result = (object) array( 'error' => (object) array( 'type' => 'Unknown', 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', 'code' => 'unknown', 'http' => 402 ) ); } return $result; } public function Api( $pPath, $pMethod = 'GET', $pParams = array() ) { return $this->_Api( $this->CanonizePath( $pPath ), $pMethod, $pParams ); } /** * Base64 decoding that does not need to be urldecode()-ed. * * Exactly the same as PHP base64 encode except it uses * `-` instead of `+` * `_` instead of `/` * No padded = * * @param string $input Base64UrlEncoded() string * * @return string */ protected static function Base64UrlDecode( $input ) { /** * IMPORTANT NOTE: * This is a hack suggested by @otto42 and @greenshady from * the theme's review team. The usage of base64 for API * signature encoding was approved in a Slack meeting * held on Tue (10/25 2016). * * @todo Remove this hack once the base64 error is removed from the Theme Check. * * @since 1.2.2 * @author Vova Feldman (@svovaf) */ $fn = 'base64' . '_decode'; return $fn( strtr( $input, '-_', '+/' ) ); } /** * Base64 encoding that does not need to be urlencode()ed. * * Exactly the same as base64 encode except it uses * `-` instead of `+ * `_` instead of `/` * * @param string $input string * * @return string Base64 encoded string */ protected static function Base64UrlEncode( $input ) { /** * IMPORTANT NOTE: * This is a hack suggested by @otto42 and @greenshady from * the theme's review team. The usage of base64 for API * signature encoding was approved in a Slack meeting * held on Tue (10/25 2016). * * @todo Remove this hack once the base64 error is removed from the Theme Check. * * @since 1.2.2 * @author Vova Feldman (@svovaf) */ $fn = 'base64' . '_encode'; $str = strtr( $fn( $input ), '+/', '-_' ); $str = str_replace( '=', '', $str ); return $str; } } }PK!c&T&T+freemius/includes/sdk/FreemiusWordPress.phpnu[ '7.37' ); if ( ! defined( 'FS_API__PROTOCOL' ) ) { define( 'FS_API__PROTOCOL', version_compare( $curl_version['version'], '7.37', '>=' ) ? 'https' : 'http' ); } if ( ! defined( 'FS_API__LOGGER_ON' ) ) { define( 'FS_API__LOGGER_ON', false ); } if ( ! defined( 'FS_API__ADDRESS' ) ) { define( 'FS_API__ADDRESS', '://api.freemius.com' ); } if ( ! defined( 'FS_API__SANDBOX_ADDRESS' ) ) { define( 'FS_API__SANDBOX_ADDRESS', '://sandbox-api.freemius.com' ); } if ( ! class_exists( 'Freemius_Api_WordPress' ) ) { class Freemius_Api_WordPress extends Freemius_Api_Base { private static $_logger = array(); /** * @param string $pScope 'app', 'developer', 'user' or 'install'. * @param number $pID Element's id. * @param string $pPublic Public key. * @param string|bool $pSecret Element's secret key. * @param bool $pSandbox Whether or not to run API in sandbox mode. */ public function __construct( $pScope, $pID, $pPublic, $pSecret = false, $pSandbox = false ) { // If secret key not provided, use public key encryption. if ( is_bool( $pSecret ) ) { $pSecret = $pPublic; } parent::Init( $pScope, $pID, $pPublic, $pSecret, $pSandbox ); } public static function GetUrl( $pCanonizedPath = '', $pIsSandbox = false ) { $address = ( $pIsSandbox ? FS_API__SANDBOX_ADDRESS : FS_API__ADDRESS ); if ( ':' === $address[0] ) { $address = self::$_protocol . $address; } return $address . $pCanonizedPath; } #---------------------------------------------------------------------------------- #region Servers Clock Diff #---------------------------------------------------------------------------------- /** * @var int Clock diff in seconds between current server to API server. */ private static $_clock_diff = 0; /** * Set clock diff for all API calls. * * @since 1.0.3 * * @param $pSeconds */ public static function SetClockDiff( $pSeconds ) { self::$_clock_diff = $pSeconds; } /** * Find clock diff between current server to API server. * * @since 1.0.2 * @return int Clock diff in seconds. */ public static function FindClockDiff() { $time = time(); $pong = self::Ping(); return ( $time - strtotime( $pong->timestamp ) ); } #endregion /** * @var string http or https */ private static $_protocol = FS_API__PROTOCOL; /** * Set API connection protocol. * * @since 1.0.4 */ public static function SetHttp() { self::$_protocol = 'http'; } /** * Sets API connection protocol to HTTPS. * * @since 2.5.4 */ public static function SetHttps() { self::$_protocol = 'https'; } /** * @since 1.0.4 * * @return bool */ public static function IsHttps() { return ( 'https' === self::$_protocol ); } /** * Sign request with the following HTTP headers: * Content-MD5: MD5(HTTP Request body) * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, * {scope_entity_secret_key})) * * @param string $pResourceUrl * @param array $pWPRemoteArgs * * @return array */ function SignRequest( $pResourceUrl, $pWPRemoteArgs ) { $auth = $this->GenerateAuthorizationParams( $pResourceUrl, $pWPRemoteArgs['method'], ! empty( $pWPRemoteArgs['body'] ) ? $pWPRemoteArgs['body'] : '' ); $pWPRemoteArgs['headers']['Date'] = $auth['date']; $pWPRemoteArgs['headers']['Authorization'] = $auth['authorization']; if ( ! empty( $auth['content_md5'] ) ) { $pWPRemoteArgs['headers']['Content-MD5'] = $auth['content_md5']; } return $pWPRemoteArgs; } /** * Generate Authorization request headers: * * Content-MD5: MD5(HTTP Request body) * Date: Current date (i.e Sat, 14 Feb 2016 20:24:46 +0000) * Authorization: FS {scope_entity_id}:{scope_entity_public_key}:base64encode(sha256(string_to_sign, * {scope_entity_secret_key})) * * @author Vova Feldman * * @param string $pResourceUrl * @param string $pMethod * @param string $pPostParams * * @return array * @throws Freemius_Exception */ function GenerateAuthorizationParams( $pResourceUrl, $pMethod = 'GET', $pPostParams = '' ) { $pMethod = strtoupper( $pMethod ); $eol = "\n"; $content_md5 = ''; $content_type = ''; $now = ( time() - self::$_clock_diff ); $date = date( 'r', $now ); if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { $content_type = 'application/json'; if ( ! empty( $pPostParams ) ) { $content_md5 = md5( $pPostParams ); } } $string_to_sign = implode( $eol, array( $pMethod, $content_md5, $content_type, $date, $pResourceUrl ) ); // If secret and public keys are identical, it means that // the signature uses public key hash encoding. $auth_type = ( $this->_secret !== $this->_public ) ? 'FS' : 'FSP'; $auth = array( 'date' => $date, 'authorization' => $auth_type . ' ' . $this->_id . ':' . $this->_public . ':' . self::Base64UrlEncode( hash_hmac( 'sha256', $string_to_sign, $this->_secret ) ) ); if ( ! empty( $content_md5 ) ) { $auth['content_md5'] = $content_md5; } return $auth; } /** * Get API request URL signed via query string. * * @since 1.2.3 Stopped using http_build_query(). Instead, use urlencode(). In some environments the encoding of http_build_query() can generate a URL that once used with a redirect, the `&` querystring separator is escaped to `&` which breaks the URL (Added by @svovaf). * * @param string $pPath * * @throws Freemius_Exception * * @return string */ function GetSignedUrl( $pPath ) { $resource = explode( '?', $this->CanonizePath( $pPath ) ); $pResourceUrl = $resource[0]; $auth = $this->GenerateAuthorizationParams( $pResourceUrl ); return Freemius_Api_WordPress::GetUrl( $pResourceUrl . '?' . ( 1 < count( $resource ) && ! empty( $resource[1] ) ? $resource[1] . '&' : '' ) . 'authorization=' . urlencode( $auth['authorization'] ) . '&auth_date=' . urlencode( $auth['date'] ) , $this->_isSandbox ); } /** * @author Vova Feldman * * @param string $pUrl * @param array $pWPRemoteArgs * * @return mixed */ private static function ExecuteRequest( $pUrl, &$pWPRemoteArgs ) { $bt = debug_backtrace(); $start = microtime( true ); $response = self::RemoteRequest( $pUrl, $pWPRemoteArgs ); if ( FS_API__LOGGER_ON ) { $end = microtime( true ); $has_body = ( isset( $pWPRemoteArgs['body'] ) && ! empty( $pWPRemoteArgs['body'] ) ); $is_http_error = is_wp_error( $response ); self::$_logger[] = array( 'id' => count( self::$_logger ), 'start' => $start, 'end' => $end, 'total' => ( $end - $start ), 'method' => $pWPRemoteArgs['method'], 'path' => $pUrl, 'body' => $has_body ? $pWPRemoteArgs['body'] : null, 'result' => ! $is_http_error ? $response['body'] : json_encode( $response->get_error_messages() ), 'code' => ! $is_http_error ? $response['response']['code'] : null, 'backtrace' => $bt, ); } return $response; } /** * @author Leo Fajardo (@leorw) * * @param string $pUrl * @param array $pWPRemoteArgs * * @return array|WP_Error The response array or a WP_Error on failure. */ static function RemoteRequest( $pUrl, $pWPRemoteArgs ) { $response = wp_remote_request( $pUrl, $pWPRemoteArgs ); 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; } /** * @return array */ static function GetLogger() { return self::$_logger; } /** * @param string $pCanonizedPath * @param string $pMethod * @param array $pParams * @param null|array $pWPRemoteArgs * @param bool $pIsSandbox * @param null|callable $pBeforeExecutionFunction * * @return object[]|object|null * * @throws \Freemius_Exception */ private static function MakeStaticRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array(), $pWPRemoteArgs = null, $pIsSandbox = false, $pBeforeExecutionFunction = null ) { // Connectivity errors simulation. if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_CLOUDFLARE ) { self::ThrowCloudFlareDDoSException(); } else if ( FS_SDK__SIMULATE_NO_API_CONNECTIVITY_SQUID_ACL ) { self::ThrowSquidAclException(); } if ( empty( $pWPRemoteArgs ) ) { $user_agent = 'Freemius/WordPress-SDK/' . Freemius_Api_Base::VERSION . '; ' . home_url(); $pWPRemoteArgs = array( 'method' => strtoupper( $pMethod ), 'connect_timeout' => 10, 'timeout' => 60, 'follow_redirects' => true, 'redirection' => 5, 'user-agent' => $user_agent, 'blocking' => true, ); } if ( ! isset( $pWPRemoteArgs['headers'] ) || ! is_array( $pWPRemoteArgs['headers'] ) ) { $pWPRemoteArgs['headers'] = array(); } if ( in_array( $pMethod, array( 'POST', 'PUT' ) ) ) { $pWPRemoteArgs['headers']['Content-type'] = 'application/json'; if ( is_array( $pParams ) && 0 < count( $pParams ) ) { $pWPRemoteArgs['body'] = json_encode( $pParams ); } } $request_url = self::GetUrl( $pCanonizedPath, $pIsSandbox ); $resource = explode( '?', $pCanonizedPath ); if ( FS_SDK__HAS_CURL ) { // Disable the 'Expect: 100-continue' behaviour. This causes cURL to wait // for 2 seconds if the server does not support this header. $pWPRemoteArgs['headers']['Expect'] = ''; } if ( 'https' === substr( strtolower( $request_url ), 0, 5 ) ) { $pWPRemoteArgs['sslverify'] = FS_SDK__SSLVERIFY; } if ( false !== $pBeforeExecutionFunction && is_callable( $pBeforeExecutionFunction ) ) { $pWPRemoteArgs = call_user_func( $pBeforeExecutionFunction, $resource[0], $pWPRemoteArgs ); } $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); if ( is_wp_error( $result ) ) { /** * @var WP_Error $result */ if ( self::IsCurlError( $result ) ) { /** * With dual stacked DNS responses, it's possible for a server to * have IPv6 enabled but not have IPv6 connectivity. If this is * the case, cURL will try IPv4 first and if that fails, then it will * fall back to IPv6 and the error EHOSTUNREACH is returned by the * operating system. */ $matches = array(); $regex = '/Failed to connect to ([^:].*): Network is unreachable/'; if ( preg_match( $regex, $result->get_error_message( 'http_request_failed' ), $matches ) ) { /** * Validate IP before calling `inet_pton()` to avoid PHP un-catchable warning. * @author Vova Feldman (@svovaf) */ if ( filter_var( $matches[1], FILTER_VALIDATE_IP ) ) { if ( strlen( inet_pton( $matches[1] ) ) === 16 ) { // error_log('Invalid IPv6 configuration on server, Please disable or get native IPv6 on your server.'); // Hook to an action triggered just before cURL is executed to resolve the IP version to v4. add_action( 'http_api_curl', 'Freemius_Api_WordPress::CurlResolveToIPv4', 10, 1 ); // Re-run request. $result = self::ExecuteRequest( $request_url, $pWPRemoteArgs ); } } } } if ( is_wp_error( $result ) ) { self::ThrowWPRemoteException( $result ); } } $response_body = $result['body']; if ( empty( $response_body ) ) { return null; } $decoded = json_decode( $response_body ); if ( is_null( $decoded ) ) { if ( preg_match( '/Please turn JavaScript on/i', $response_body ) && preg_match( '/text\/javascript/', $response_body ) ) { self::ThrowCloudFlareDDoSException( $response_body ); } else if ( preg_match( '/Access control configuration prevents your request from being allowed at this time. Please contact your service provider if you feel this is incorrect./', $response_body ) && preg_match( '/squid/', $response_body ) ) { self::ThrowSquidAclException( $response_body ); } else { $decoded = (object) array( 'error' => (object) array( 'type' => 'Unknown', 'message' => $response_body, 'code' => 'unknown', 'http' => 402 ) ); } } return $decoded; } /** * Makes an HTTP request. This method can be overridden by subclasses if * developers want to do fancier things or use something other than wp_remote_request() * to make the request. * * @param string $pCanonizedPath The URL to make the request to * @param string $pMethod HTTP method * @param array $pParams The parameters to use for the POST body * @param null|array $pWPRemoteArgs wp_remote_request options. * * @return object[]|object|null * * @throws Freemius_Exception */ public function MakeRequest( $pCanonizedPath, $pMethod = 'GET', $pParams = array(), $pWPRemoteArgs = null ) { $resource = explode( '?', $pCanonizedPath ); // Only sign request if not ping.json connectivity test. $sign_request = ( '/v1/ping.json' !== strtolower( substr( $resource[0], - strlen( '/v1/ping.json' ) ) ) ); return self::MakeStaticRequest( $pCanonizedPath, $pMethod, $pParams, $pWPRemoteArgs, $this->_isSandbox, $sign_request ? array( &$this, 'SignRequest' ) : null ); } /** * Sets CURLOPT_IPRESOLVE to CURL_IPRESOLVE_V4 for cURL-Handle provided as parameter * * @param resource $handle A cURL handle returned by curl_init() * * @return resource $handle A cURL handle returned by curl_init() with CURLOPT_IPRESOLVE set to * CURL_IPRESOLVE_V4 * * @link https://gist.github.com/golderweb/3a2aaec2d56125cc004e */ static function CurlResolveToIPv4( $handle ) { curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); return $handle; } #---------------------------------------------------------------------------------- #region Connectivity Test #---------------------------------------------------------------------------------- /** * This method exists only for backward compatibility to prevent a fatal error from happening when called from an outdated piece of code. * * @param mixed $pPong * * @return bool */ public static function Test( $pPong = null ) { return ( is_object( $pPong ) && isset( $pPong->api ) && 'pong' === $pPong->api ); } /** * Ping API to test connectivity. * * @return object */ public static function Ping() { try { $result = self::MakeStaticRequest( '/v' . FS_API__VERSION . '/ping.json' ); } catch ( Freemius_Exception $e ) { // Map to error object. $result = (object) $e->getResult(); } catch ( Exception $e ) { // Map to error object. $result = (object) array( 'error' => (object) array( 'type' => 'Unknown', 'message' => $e->getMessage() . ' (' . $e->getFile() . ': ' . $e->getLine() . ')', 'code' => 'unknown', 'http' => 402 ) ); } return $result; } #endregion #---------------------------------------------------------------------------------- #region Connectivity Exceptions #---------------------------------------------------------------------------------- /** * @param \WP_Error $pError * * @return bool */ private static function IsCurlError( WP_Error $pError ) { $message = $pError->get_error_message( 'http_request_failed' ); return ( 0 === strpos( $message, 'cURL' ) ); } /** * @param WP_Error $pError * * @throws Freemius_Exception */ private static function ThrowWPRemoteException( WP_Error $pError ) { if ( self::IsCurlError( $pError ) ) { $message = $pError->get_error_message( 'http_request_failed' ); #region Check if there are any missing cURL methods. $curl_required_methods = array( 'curl_version', 'curl_exec', 'curl_init', 'curl_close', 'curl_setopt', 'curl_setopt_array', 'curl_error', ); // Find all missing methods. $missing_methods = array(); foreach ( $curl_required_methods as $m ) { if ( ! function_exists( $m ) ) { $missing_methods[] = $m; } } if ( ! empty( $missing_methods ) ) { throw new Freemius_Exception( array( 'error' => (object) array( 'type' => 'cUrlMissing', 'message' => $message, 'code' => 'curl_missing', 'http' => 402 ), 'missing_methods' => $missing_methods, ) ); } #endregion // cURL error - "cURL error {{errno}}: {{error}}". $parts = explode( ':', substr( $message, strlen( 'cURL error ' ) ), 2 ); $code = ( 0 < count( $parts ) ) ? $parts[0] : 'http_request_failed'; $message = ( 1 < count( $parts ) ) ? $parts[1] : $message; $e = new Freemius_Exception( array( 'error' => (object) array( 'code' => $code, 'message' => $message, 'type' => 'CurlException', ), ) ); } else { $e = new Freemius_Exception( array( 'error' => (object) array( 'code' => $pError->get_error_code(), 'message' => $pError->get_error_message(), 'type' => 'WPRemoteException', ), ) ); } throw $e; } /** * @param string $pResult * * @throws Freemius_Exception */ private static function ThrowCloudFlareDDoSException( $pResult = '' ) { throw new Freemius_Exception( array( 'error' => (object) array( 'type' => 'CloudFlareDDoSProtection', 'message' => $pResult, 'code' => 'cloudflare_ddos_protection', 'http' => 402 ) ) ); } /** * @param string $pResult * * @throws Freemius_Exception */ private static function ThrowSquidAclException( $pResult = '' ) { throw new Freemius_Exception( array( 'error' => (object) array( 'type' => 'SquidCacheBlock', 'message' => $pResult, 'code' => 'squid_cache_block', 'http' => 402 ) ) ); } #endregion } } PK!u9Z.freemius/includes/sdk/Exceptions/Exception.phpnu[_result = $result; $code = 0; $message = 'Unknown error, please check GetResult().'; $type = ''; if ( isset( $result['error'] ) && is_array( $result['error'] ) ) { if ( isset( $result['error']['code'] ) ) { $code = $result['error']['code']; } if ( isset( $result['error']['message'] ) ) { $message = $result['error']['message']; } if ( isset( $result['error']['type'] ) ) { $type = $result['error']['type']; } } $this->_type = $type; $this->_code = $code; parent::__construct( $message, is_numeric( $code ) ? $code : 0 ); } /** * Return the associated result object returned by the API server. * * @return array The result from the API server */ public function getResult() { return $this->_result; } public function getStringCode() { return $this->_code; } public function getType() { return $this->_type; } /** * To make debugging easier. * * @return string The string representation of the error */ public function __toString() { $str = $this->getType() . ': '; if ( $this->code != 0 ) { $str .= $this->getStringCode() . ': '; } return $str . $this->getMessage(); } } } PK!p2WW*freemius/includes/sdk/Exceptions/index.phpnu[freemius/includes/sdk/Exceptions/ArgumentNotExistException.phpnu[ $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 ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return isset( $_REQUEST[ $key ] ); } } 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. * * @author Vova Feldman (@svovaf) * * @param string $key * @param bool $def * * @return bool|mixed */ function fs_request_get_bool( $key, $def = false ) { $val = fs_request_get( $key, null ); if ( is_null( $val ) ) { return $def; } if ( is_bool( $val ) ) { return $val; } else if ( is_numeric( $val ) ) { if ( 1 == $val ) { return true; } else if ( 0 == $val ) { return false; } } else if ( is_string( $val ) ) { $val = strtolower( $val ); if ( 'true' === $val ) { return true; } else if ( 'false' === $val ) { return false; } } return $def; } } 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_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 ) { $action_key = 'fs_action'; 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_secure' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.0.0 * * @since 1.2.1.5 Allow nonce verification. * * @param string $action * @param string $action_key * @param string $nonce_key * * @return bool */ function fs_request_is_action_secure( $action, $action_key = 'action', $nonce_key = 'nonce' ) { if ( strtolower( $action ) !== fs_get_action( $action_key ) ) { return false; } $nonce = ! empty( $_REQUEST[ $nonce_key ] ) ? $_REQUEST[ $nonce_key ] : ''; if ( empty( $nonce ) || ( false === wp_verify_nonce( $nonce, $action ) ) ) { return false; } return true; } } #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_get_raw_referer' ) ) { /** * Retrieves unvalidated referer from '_wp_http_referer' or HTTP referer. * * Do not use for redirects, use {@see wp_get_referer()} instead. * * @since 1.2.3 * * @return string|false Referer URL on success, false on failure. */ 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'] ); } else if ( ! empty( $_SERVER['HTTP_REFERER'] ) ) { return wp_unslash( $_SERVER['HTTP_REFERER'] ); } return false; } } /* Core UI. --------------------------------------------------------------------------------------------*/ if ( ! function_exists( 'fs_ui_action_button' ) ) { /** * @param number $module_id * @param string $page * @param string $action * @param string $title * @param string $button_class * @param array $params * @param bool $is_primary * @param bool $is_small * @param string|bool $icon_class Optional class for an icon (since 1.1.7). * @param string|bool $confirmation Optional confirmation message before submit (since 1.1.7). * @param string $method Since 1.1.7 * * @uses fs_ui_get_action_button() */ function fs_ui_action_button( $module_id, $page, $action, $title, $button_class = '', $params = array(), $is_primary = true, $is_small = false, $icon_class = false, $confirmation = false, $method = 'GET' ) { echo fs_ui_get_action_button( $module_id, $page, $action, $title, $button_class, $params, $is_primary, $is_small, $icon_class, $confirmation, $method ); } } if ( ! function_exists( 'fs_ui_get_action_button' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @param number $module_id * @param string $page * @param string $action * @param string $title * @param string $button_class * @param array $params * @param bool $is_primary * @param bool $is_small * @param string|bool $icon_class Optional class for an icon. * @param string|bool $confirmation Optional confirmation message before submit. * @param string $method * * @return string */ function fs_ui_get_action_button( $module_id, $page, $action, $title, $button_class = '', $params = array(), $is_primary = true, $is_small = false, $icon_class = false, $confirmation = false, $method = 'GET' ) { // Prepend icon (if set). $title = ( is_string( $icon_class ) ? ' ' : '' ) . $title; 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' : '' ), $confirmation, $title ); } 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' : '' ), $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' : '' ), $title ); } } function fs_ui_action_link( $module_id, $page, $action, $title, $params = array() ) { ?> $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 ); } } return $entities; } } if ( ! function_exists( 'fs_nonce_url' ) ) { /** * Retrieve URL with nonce added to URL query. * * Originally was using `wp_nonce_url()` but the new version * changed the return value to escaped URL, that's not the expected * behaviour. * * @author Vova Feldman (@svovaf) * @since ~1.1.3 * * @param string $actionurl URL to add nonce action. * @param int|string $action Optional. Nonce action name. Default -1. * @param string $name Optional. Nonce name. Default '_wpnonce'. * * @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 ); } } if ( ! function_exists( 'fs_starts_with' ) ) { /** * Check if string starts with. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string $haystack * @param string $needle * * @return bool */ function fs_starts_with( $haystack, $needle ) { $length = strlen( $needle ); return ( substr( $haystack, 0, $length ) === $needle ); } } if ( ! function_exists( 'fs_ends_with' ) ) { /** * Check if string ends with. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $haystack * @param string $needle * * @return bool */ function fs_ends_with( $haystack, $needle ) { $length = strlen( $needle ); $start = $length * - 1; // negative return ( substr( $haystack, $start ) === $needle ); } } 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, '://' ); if ( $protocol_pos > 5 ) { return $url; } return substr( $url, $protocol_pos + 3 ); } } #region Url Canonization ------------------------------------------------------------------ if ( ! function_exists( 'fs_canonize_url' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string $url * @param bool $omit_host * @param array $ignore_params * * @return string */ function fs_canonize_url( $url, $omit_host = false, $ignore_params = array() ) { $parsed_url = parse_url( strtolower( $url ) ); // if ( ! isset( $parsed_url['host'] ) ) { // return $url; // } $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 ); } return $canonical; } } if ( ! function_exists( 'fs_canonize_query_string' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param array $params * @param array $ignore_params * @param bool $params_prefix * * @return string */ 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 ); // Parameters are sorted by name, using lexicographical byte value ordering. // Ref: Spec: 9.1.1 (1) uksort( $params, 'strcmp' ); $pairs = array(); 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 ) ) ) { continue; } 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 ) { $pairs[] = $lower_param . '=' . $duplicate_value; } } else { $pairs[] = $lower_param . '=' . $value; } } if ( 0 === count( $pairs ) ) { return ''; } return implode( "&", $pairs ); } } if ( ! function_exists( 'fs_urlencode_rfc3986' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string|string[] $input * * @return array|mixed|string */ 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 ''; } } #endregion Url Canonization ------------------------------------------------------------------ if ( ! function_exists( 'fs_download_image' ) ) { /** * @author Vova Feldman (@svovaf) * * @since 1.2.2 Changed to usage of WP_Filesystem_Direct. * * @param string $from URL * @param string $to File path. * * @return bool Is successfully downloaded. */ function fs_download_image( $from, $to ) { $dir = dirname( $to ); if ( 'direct' !== get_filesystem_method( array(), $dir ) ) { return false; } 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 ); if ( $tmpfile instanceof WP_Error ) { // Issue downloading the file. return false; } $fs->copy( $tmpfile, $to ); $fs->delete( $tmpfile ); return true; } } /* General Utilities --------------------------------------------------------------------------------------------*/ if ( ! function_exists( 'fs_sort_by_priority' ) ) { /** * Sorts an array by the value of the priority key. * * @author Daniel Iser (@danieliser) * @since 1.1.7 * * @param $a * @param $b * * @return int */ 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'] ) ) { return 1; } // If b has a priority and a does not, b wins. elseif ( isset( $a['priority'] ) && ! isset( $b['priority'] ) ) { return - 1; } // 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; } } #-------------------------------------------------------------------------------- #region Localization #-------------------------------------------------------------------------------- global $fs_text_overrides; if ( ! isset( $fs_text_overrides ) ) { $fs_text_overrides = array(); } if ( ! function_exists( 'fs_text' ) ) { /** * Retrieve a translated text by key. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $key * @param string $slug * * @return string * * @global $fs_text_overrides */ 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 ] ) ) { return $fs_text_overrides[ $slug ][ $lower_key ]; } } return $key; } #region Private /** * Retrieve an inline translated text by key with a context. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string * * @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 ); // Avoid misleading Theme Check warning. $fn = 'translate_with_gettext_context'; return $fn( $text, $context, $text_domain ); } #endregion /** * Retrieve an inline translated text by key with a context. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string * * @global $fs_text_overrides */ function fs_text_x_inline( $text, $context, $key = '', $slug = 'freemius' ) { return _fs_text_x_inline( $text, $context, $key, $slug ); } /** * Output a translated text by key. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $key * @param string $slug */ function fs_echo( $key, $slug = 'freemius' ) { echo fs_text( $key, $slug ); } /** * Output an inline translated text. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @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 ); } /** * Output an inline translated text with a context. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @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 ); } } if ( ! function_exists( 'fs_text_override' ) ) { /** * Get a translatable text override if exists, or `false`. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string|false */ function fs_text_override( $text, $key, $slug ) { global $fs_text_overrides; /** * Check if string is overridden. */ if ( ! isset( $fs_text_overrides[ $slug ] ) ) { return false; } if ( empty( $key ) ) { $key = strtolower( str_replace( ' ', '-', $text ) ); } 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 ]; } return false; } } if ( ! function_exists( 'fs_text_and_domain' ) ) { /** * Get a translatable text and its text domain. * * When the text is overridden by the module, returns the overridden text and the text domain of the module. Otherwise, returns the original text and 'freemius' as the text domain. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string[] */ function fs_text_and_domain( $text, $key, $slug ) { $override = fs_text_override( $text, $key, $slug ); if ( false === $override ) { // No override, use FS text domain. $text_domain = 'freemius'; } else { // Found an override. $text = $override; // Use the module's text domain. $text_domain = $slug; } return array( $text, $text_domain ); } } if ( ! function_exists( '_fs_text_inline' ) ) { /** * Retrieve an inline translated text by key. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string * * @global $fs_text_overrides */ 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 ); } } if ( ! function_exists( 'fs_text_inline' ) ) { /** * Retrieve an inline translated text by key. * * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string * * @global $fs_text_overrides */ function fs_text_inline( $text, $key = '', $slug = 'freemius' ) { return _fs_text_inline( $text, $key, $slug ); } } if ( ! function_exists( 'fs_esc_attr' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug * * @return string */ function fs_esc_attr( $key, $slug ) { return esc_attr( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_attr_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ 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' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ 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' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug */ function fs_esc_attr_echo( $key, $slug ) { echo esc_attr( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_attr_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @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 ) ); } } if ( ! function_exists( 'fs_esc_js' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug * * @return string */ function fs_esc_js( $key, $slug ) { return esc_js( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_js_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ 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' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ 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' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ 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' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug */ function fs_esc_js_echo( $key, $slug ) { echo esc_js( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_js_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @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 ) ); } } if ( ! function_exists( 'fs_json_encode_echo' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug */ function fs_json_encode_echo( $key, $slug ) { echo json_encode( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_json_encode_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @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 ) ); } } if ( ! function_exists( 'fs_esc_html' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug * * @return string */ function fs_esc_html( $key, $slug ) { return esc_html( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_html_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ 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' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @param string $key String key for overrides. * @param string $slug Module slug for overrides. * * @return string */ 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' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @param string $context Context information for the translators. * @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 ) ); } } if ( ! function_exists( 'fs_esc_html_echo' ) ) { /** * @author Vova Feldman * @since 1.2.1.6 * * @param string $key * @param string $slug */ function fs_esc_html_echo( $key, $slug ) { echo esc_html( fs_text( $key, $slug ) ); } } if ( ! function_exists( 'fs_esc_html_echo_inline' ) ) { /** * @author Vova Feldman (@svovaf) * @since 1.2.3 * * @param string $text Translatable string. * @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 ) ); } } if ( ! function_exists( 'fs_override_i18n' ) ) { /** * Override default i18n text phrases. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param array[string]string $key_value * @param string $slug * * @global $fs_text_overrides */ function fs_override_i18n( array $key_value, $slug = 'freemius' ) { global $fs_text_overrides; if ( ! isset( $fs_text_overrides[ $slug ] ) ) { $fs_text_overrides[ $slug ] = array(); } foreach ( $key_value as $key => $value ) { $fs_text_overrides[ $slug ][ $key ] = $value; } } } #endregion #-------------------------------------------------------------------------------- #region Multisite Network #-------------------------------------------------------------------------------- if ( ! function_exists( 'fs_is_plugin_uninstall' ) ) { /** * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function fs_is_plugin_uninstall() { return ( defined( 'WP_UNINSTALL_PLUGIN' ) || ( 0 < did_action( 'pre_uninstall_plugin' ) ) ); } } 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 * uninstall. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function fs_is_network_admin() { return ( WP_FS__IS_NETWORK_ADMIN || ( is_multisite() && fs_is_plugin_uninstall() ) ); } } 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 * uninstall. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function fs_is_blog_admin() { return ( WP_FS__IS_BLOG_ADMIN || ( ! is_multisite() && fs_is_plugin_uninstall() ) ); } } #endregion if ( ! function_exists( 'fs_apply_filter' ) ) { /** * Apply filter for specific plugin. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string $module_unique_affix Module's unique affix. * @param string $tag The name of the filter hook. * @param mixed $value The value on which the filters hooked to `$tag` are applied on. * * @return mixed The filtered value after all hooked functions are applied to it. * * @uses apply_filters() */ function fs_apply_filter( $module_unique_affix, $tag, $value ) { $args = func_get_args(); return call_user_func_array( 'apply_filters', array_merge( array( "fs_{$tag}_{$module_unique_affix}" ), array_slice( $args, 2 ) ) ); } }PK! ) ) #freemius/includes/class-fs-lock.phpnu[_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. */ 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 */ 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 */ function is_locked() { return ( false !== get_site_transient( $this->_lock_id ) ); } /** * Unlock the lock. * * @author Vova Feldman (@svovaf) * @since 2.1.0 */ 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 ) ); } }PK!D0rXrX&freemius/includes/class-fs-storage.phpnu[_module_type = $module_type; $this->_module_slug = $slug; $this->_is_multisite = is_multisite(); if ( $this->_is_multisite ) { $this->_blog_id = get_current_blog_id(); $this->_network_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, true ); } $this->_storage = FS_Key_Value_Storage::instance( $module_type . '_data', $slug, $this->_blog_id ); } /** * Tells this storage wrapper class that the context plugin is network active. This flag will affect how values * are retrieved/stored from/into the storage. * * @author Leo Fajardo (@leorw) * * @param bool $is_network_active * @param bool $is_delegated_connection */ function set_network_active( $is_network_active = true, $is_delegated_connection = false ) { $this->_is_network_active = $is_network_active; $this->_is_delegated_connection = $is_delegated_connection; } /** * Switch the context of the site level storage manager. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id */ function set_site_blog_context( $blog_id ) { $this->_storage = $this->get_site_storage( $blog_id ); $this->_blog_id = $blog_id; } /** * @author Leo Fajardo (@leorw) * * @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 */ 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 ); $storage->store( $key, $value, $flush ); } } /** * @author Leo Fajardo (@leorw) * * @param bool $store * @param string[] $exceptions Set of keys to keep and not clear. * @param int|null|bool $network_level_or_blog_id */ function clear_all( $store = true, $exceptions = array(), $network_level_or_blog_id = null ) { if ( ! $this->_is_multisite || false === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) || is_numeric( $network_level_or_blog_id ) ) { $storage = $this->get_site_storage( $network_level_or_blog_id ); $storage->clear_all( $store, $exceptions ); } if ( $this->_is_multisite && ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) ) { $this->_network_storage->clear_all( $store, $exceptions ); } } /** * @author Leo Fajardo (@leorw) * * @param string $key * @param bool $store * @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). */ function remove( $key, $store = true, $network_level_or_blog_id = null ) { if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { $this->_network_storage->remove( $key, $store ); } else { $storage = $this->get_site_storage( $network_level_or_blog_id ); $storage->remove( $key, $store ); } } /** * @author Leo Fajardo (@leorw) * * @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 */ 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 ); return $storage->get( $key, $default ); } } /** * Multisite activated: * true: Save network storage. * int: Save site specific storage. * false|0: Save current site storage. * null: Save network and current site storage. * Site level activated: * Save site storage. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param bool|int|null $network_level_or_blog_id */ function save( $network_level_or_blog_id = null ) { if ( $this->_is_network_active && ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) ) { $this->_network_storage->save(); } if ( ! $this->_is_network_active || true !== $network_level_or_blog_id ) { $storage = $this->get_site_storage( $network_level_or_blog_id ); $storage->save(); } } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function get_module_slug() { return $this->_module_slug; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function get_module_type() { return $this->_module_type; } /** * Migration script to the new storage data structure that is network compatible. * * IMPORTANT: * This method should be executed only after it is determined if this is a network * level compatible product activation. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function migrate_to_network() { if ( ! $this->_is_multisite ) { return; } $updated = false; if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { self::load_network_options_map(); } foreach ( self::$_NETWORK_OPTIONS_MAP as $option => $storage_level ) { if ( ! $this->is_multisite_option( $option ) ) { continue; } if ( isset( $this->_storage->{$option} ) && ! isset( $this->_network_storage->{$option} ) ) { // Migrate option to the network storage. $this->_network_storage->store( $option, $this->_storage->{$option}, false ); $updated = true; } } if ( ! $updated ) { return; } // Update network level storage. $this->_network_storage->save(); // $this->_storage->save(); } #-------------------------------------------------------------------------------- #region Helper Methods #-------------------------------------------------------------------------------- /** * We don't want to load the map right away since it's not even needed in a non-MS environment. * * Example: * array( * 'option1' => 0, // Means that the option should always be stored on the network level. * 'option2' => 1, // Means that the option should be stored on the network level only when the module was network level activated. * 'option2' => 2, // Means that the option should be stored on the network level only when the module was network level activated AND the connection was NOT delegated. * 'option3' => 3, // Means that the option should always be stored on the site level. * ) * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ private static function load_network_options_map() { self::$_NETWORK_OPTIONS_MAP = array( // Network level options. '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 the sites in the network. */ 'is_network_activation' => self::OPTION_LEVEL_NETWORK, 'license_migration' => self::OPTION_LEVEL_NETWORK, // When network activated, then network level. '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' => 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' => self::OPTION_LEVEL_SITE, 'clone_id' => self::OPTION_LEVEL_SITE, ); } /** * This method will and should only be executed when is_multisite() is true. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $key * @param int $option_level Since 2.5.1 * * @return bool */ private function is_multisite_option( $key, $option_level = self::OPTION_LEVEL_UNDEFINED ) { if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { self::load_network_options_map(); } 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 ( self::OPTION_LEVEL_NETWORK === $option_level ) { // Option found and set to always use the network level storage on a multisite. return true; } if ( self::OPTION_LEVEL_SITE === $option_level ) { // Option found and set to always use the site level storage on a multisite. return false; } if ( ! $this->_is_network_active ) { return false; } if ( self::OPTION_LEVEL_NETWORK_ACTIVATED === $option_level ) { // Network activated. return true; } if ( self::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED === $option_level && ! $this->_is_delegated_connection ) { // Network activated and not delegated. return true; } return false; } /** * @author Leo Fajardo * * @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, $option_level = self::OPTION_LEVEL_UNDEFINED ) { if ( ! $this->_is_multisite ) { // Not a multisite environment. return false; } if ( is_numeric( $network_level_or_blog_id ) ) { // Explicitly asked to use a specified blog storage. return false; } if ( is_bool( $network_level_or_blog_id ) ) { // 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, $option_level ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id * * @return \FS_Key_Value_Storage */ private function get_site_storage( $blog_id = 0 ) { if ( ! is_numeric( $blog_id ) || $blog_id == $this->_blog_id || 0 == $blog_id ) { return $this->_storage; } return FS_Key_Value_Storage::instance( $this->_module_type . '_data', $this->_storage->get_secondary_id(), $blog_id ); } #endregion #-------------------------------------------------------------------------------- #region Magic methods #-------------------------------------------------------------------------------- function __set( $k, $v ) { if ( $this->should_use_network_storage( $k ) ) { $this->_network_storage->{$k} = $v; } else { $this->_storage->{$k} = $v; } } function __isset( $k ) { return $this->should_use_network_storage( $k ) ? isset( $this->_network_storage->{$k} ) : isset( $this->_storage->{$k} ); } function __unset( $k ) { if ( $this->should_use_network_storage( $k ) ) { unset( $this->_network_storage->{$k} ); } else { unset( $this->_storage->{$k} ); } } function __get( $k ) { return $this->should_use_network_storage( $k ) ? $this->_network_storage->{$k} : $this->_storage->{$k}; } #endregion } PK!KK>freemius/includes/supplements/fs-essential-functions-2.2.1.phpnu[ $install ) { if ( true === $install->is_disconnected ) { $permission_manager->update_site_tracking( false, ( 0 == $blog_id ) ? null : $blog_id, // Update only if permissions are not yet set. true ); } } } }PK!p2WW'freemius/includes/supplements/index.phpnu[ $data ) { if ( 0 === strpos( $file_real_path, fs_normalize_path( dirname( realpath( WP_PLUGIN_DIR . '/' . $relative_path ) ) . '/' ) ) ) { if ( '.' !== dirname( trailingslashit( $relative_path ) ) ) { return $relative_path; } } } return null; } PK!p2WWfreemius/includes/index.phpnu[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 ) { #>
      <# for (i in data.plans) { #> <# if ( 'free' != data.plans[i].name && (null != data.plans[i].features && 0 < data.plans[i].features.length) ) { #>
    • <# if ( data.plans[i].description ) { #>

      {{ data.plans[i].description }}

      <# } #> <# if ( data.plans[i].features ) { #>
        <# for ( j in data.plans[i].features ) { #>
      • <# if ( data.plans[i].features[j].value ) { #>{{ data.plans[i].features[j].value }} <# } #>{{ data.plans[i].features[j].title }} <# if ( data.plans[i].features[j].description ) { #> {{ data.plans[i].features[j].description }} <# } #>
      • <# } #>
      <# } #> <# if ( 'free' != data.plans[i].name ) { #> {{{ data.button_text }}} <# } #>
    • <# } #> <# } #>
    <# } #>
    register_section_type( 'FS_Customizer_Support_Section' ); parent::__construct( $manager, $id, $args ); } /** * The type of customize section being rendered. * * @since 1.0.0 * @access public * @var string */ public $type = 'freemius-support-section'; /** * @var Freemius */ public $fs = null; /** * Add custom parameters to pass to the JS via JSON. * * @since 1.0.0 */ public function json() { $json = parent::json(); $is_contact_visible = $this->fs->is_page_visible( 'contact' ); $is_support_visible = $this->fs->is_page_visible( 'support' ); $json['theme_title'] = $this->fs->get_plugin_name(); if ( $is_contact_visible && $is_support_visible ) { $json['theme_title'] .= ' ' . $this->fs->get_text_inline( 'Support', 'support' ); } if ( $is_contact_visible ) { $json['contact'] = array( 'label' => $this->fs->get_text_inline( 'Contact Us', 'contact-us' ), 'url' => $this->fs->contact_url(), ); } if ( $is_support_visible ) { $json['support'] = array( 'label' => $this->fs->get_text_inline( 'Support Forum', 'support-forum' ), 'url' => $this->fs->get_support_forum_url() ); } return $json; } /** * Outputs the Underscore.js template. * * @since 1.0.0 */ protected function render_template() { ?>
  • {{ data.theme_title }} <# if ( data.contact && data.support ) { #>
    <# } #> <# if ( data.contact ) { #> {{ data.contact.label }} <# } #> <# if ( data.support ) { #> {{ data.support.label }} <# } #> <# if ( data.contact && data.support ) { #>
    <# } #>

  • name = strtolower( $plan->name ); } } static function get_type() { return 'plan'; } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_free() { return ( 'free' === $this->name ); } /** * Checks if this plan supports "Technical Support". * * @author Leo Fajardo (leorw) * @since 1.2.0 * * @return bool */ function has_technical_support() { return ( ! empty( $this->support_email ) || ! empty( $this->support_skype ) || ! empty( $this->support_phone ) || ! empty( $this->is_success_manager ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function has_trial() { return ! $this->is_free() && is_numeric( $this->trial_period ) && ( $this->trial_period > 0 ); } }PK!96 6 .freemius/includes/entities/class-fs-entity.phpnu[ $def_value ) { $this->{$key} = isset( $entity->{$key} ) ? $entity->{$key} : $def_value; } } static function get_type() { return 'type'; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param FS_Entity $entity1 * @param FS_Entity $entity2 * * @return bool */ static function equals( $entity1, $entity2 ) { if ( is_null( $entity1 ) && is_null( $entity2 ) ) { return true; } else if ( is_object( $entity1 ) && is_object( $entity2 ) ) { return ( $entity1->id == $entity2->id ); } else if ( is_object( $entity1 ) ) { return is_null( $entity1->id ); } else { return is_null( $entity2->id ); } } private $_is_updated = false; /** * Update object property. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string|array[string]mixed $key * @param string|bool $val * * @return bool */ function update( $key, $val = false ) { if ( ! is_array( $key ) ) { $key = array( $key => $val ); } $is_updated = false; foreach ( $key as $k => $v ) { if ( $this->{$k} === $v ) { continue; } if ( ( is_string( $this->{$k} ) && is_numeric( $v ) || ( is_numeric( $this->{$k} ) && is_string( $v ) ) ) && $this->{$k} == $v ) { continue; } // Update value. $this->{$k} = $v; $is_updated = true; } $this->_is_updated = $is_updated; return $is_updated; } /** * Checks if entity was updated. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_updated() { return $this->_is_updated; } /** * @param $id * * @author Vova Feldman (@svovaf) * @since 1.1.2 * * @return bool */ static function is_valid_id($id){ return is_numeric($id); } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @return string */ public static function get_class_name() { return get_called_class(); } }PK!Ly/freemius/includes/entities/class-fs-billing.phpnu[first ) ? $this->first : '' ) ) . ' ' . ucfirst( trim( is_string( $this->last ) ? $this->last : '' ) ) ); } function is_verified() { return ( isset( $this->is_verified ) && true === $this->is_verified ); } /** * @author Leo Fajardo (@leorw) * @since 2.4.2 * * @return bool */ function is_beta() { // Return `false` since this is just for backward compatibility. return false; } static function get_type() { return 'user'; } }PK!7&,freemius/includes/entities/class-fs-site.phpnu[plan_id = $site->plan_id; } if ( ! is_bool( $this->is_disconnected ) ) { $this->is_disconnected = false; } } static function get_type() { return 'install'; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $url * * @return bool */ static function is_localhost_by_address( $url ) { if ( false !== strpos( $url, '127.0.0.1' ) || false !== strpos( $url, 'localhost' ) ) { return true; } if ( ! fs_starts_with( $url, 'http' ) ) { $url = 'http://' . $url; } $url_parts = parse_url( $url ); $subdomain = $url_parts['host']; return ( // Starts with. fs_starts_with( $subdomain, 'local.' ) || fs_starts_with( $subdomain, 'dev.' ) || fs_starts_with( $subdomain, 'test.' ) || fs_starts_with( $subdomain, 'stage.' ) || fs_starts_with( $subdomain, 'staging.' ) || // Ends with. fs_ends_with( $subdomain, '.dev' ) || fs_ends_with( $subdomain, '.test' ) || fs_ends_with( $subdomain, '.staging' ) || fs_ends_with( $subdomain, '.local' ) || fs_ends_with( $subdomain, '.example' ) || fs_ends_with( $subdomain, '.invalid' ) || // GoDaddy test/dev. fs_ends_with( $subdomain, '.myftpupload.com' ) || // ngrok tunneling. fs_ends_with( $subdomain, '.ngrok.io' ) || // wpsandbox. fs_ends_with( $subdomain, '.wpsandbox.pro' ) || // SiteGround staging. fs_starts_with( $subdomain, 'staging' ) || // WPEngine staging. fs_ends_with( $subdomain, '.staging.wpengine.com' ) || fs_ends_with( $subdomain, '.dev.wpengine.com' ) || fs_ends_with( $subdomain, '.wpengine.com' ) || // Pantheon ( fs_ends_with( $subdomain, 'pantheonsite.io' ) && ( fs_starts_with( $subdomain, 'test-' ) || fs_starts_with( $subdomain, 'dev-' ) ) ) || // Cloudways fs_ends_with( $subdomain, '.cloudwaysapps.com' ) || // Kinsta ( ( fs_starts_with( $subdomain, 'staging-' ) || fs_starts_with( $subdomain, 'env-' ) ) && ( fs_ends_with( $subdomain, '.kinsta.com' ) || fs_ends_with( $subdomain, '.kinsta.cloud' ) ) ) || // DesktopServer fs_ends_with( $subdomain, '.dev.cc' ) || // Pressable fs_ends_with( $subdomain, '.mystagingwebsite.com' ) || // WPMU DEV ( fs_ends_with( $subdomain, '.tempurl.host' ) || fs_ends_with( $subdomain, '.wpmudev.host' ) ) || // Vendasta ( fs_ends_with( $subdomain, '.websitepro-staging.com' ) || fs_ends_with( $subdomain, '.websitepro.hosting' ) ) || // InstaWP fs_ends_with( $subdomain, '.instawp.xyz' ) ); } function is_localhost() { return ( WP_FS__IS_LOCALHOST_FOR_SERVER || self::is_localhost_by_address( $this->url ) ); } /** * Check if site in trial. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_trial() { return is_numeric( $this->trial_plan_id ) && ( strtotime( $this->trial_ends ) > WP_FS__SCRIPT_START_TIME ); } /** * Check if user already utilized the trial with the current install. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_trial_utilized() { return is_numeric( $this->trial_plan_id ); } /** * @author Edgar Melkonyan * * @return bool */ function is_beta() { return ( isset( $this->is_beta ) && true === $this->is_beta ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.1 * * @param string $site_url * * @return bool */ function is_clone( $site_url ) { $clone_install_url = trailingslashit( fs_strip_url_protocol( $this->url ) ); return ( $clone_install_url !== $site_url ); } }PK!!H{vv.freemius/includes/entities/class-fs-plugin.phpnu[is_premium = false; $this->is_live = true; if ( empty( $this->premium_slug ) && ! empty( $plugin->slug ) ) { $this->premium_slug = "{$this->slug}-premium"; } if ( empty( $this->premium_suffix ) ) { $this->premium_suffix = '(Premium)'; } if ( isset( $plugin->info ) && is_object( $plugin->info ) ) { $this->info = new FS_Plugin_Info( $plugin->info ); } } /** * Check if plugin is an add-on (has parent). * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool */ function is_addon() { return isset( $this->parent_plugin_id ) && is_numeric( $this->parent_plugin_id ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.3 * * @return bool */ function has_affiliate_program() { return ( ! empty( $this->affiliate_moderation ) ); } static function get_type() { return 'plugin'; } }PK!73freemius/includes/entities/class-fs-plugin-info.phpnu[monthly_price ) && $this->monthly_price > 0 ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return bool */ function has_annual() { return ( is_numeric( $this->annual_price ) && $this->annual_price > 0 ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return bool */ function has_lifetime() { return ( is_numeric( $this->lifetime_price ) && $this->lifetime_price > 0 ); } /** * Check if unlimited licenses pricing. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return bool */ function is_unlimited() { return is_null( $this->licenses ); } /** * Check if pricing has more than one billing cycle. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return bool */ function is_multi_cycle() { $cycles = 0; if ( $this->has_monthly() ) { $cycles ++; } if ( $this->has_annual() ) { $cycles ++; } if ( $this->has_lifetime() ) { $cycles ++; } return $cycles > 1; } /** * Get annual over monthly discount. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return int */ function annual_discount_percentage() { return floor( $this->annual_savings() / ( $this->monthly_price * 12 * ( $this->is_unlimited() ? 1 : $this->licenses ) ) * 100 ); } /** * Get annual over monthly savings. * * @author Vova Feldman (@svovaf) * @since 1.1.8 * * @return float */ function annual_savings() { return ( $this->monthly_price * 12 - $this->annual_price ) * ( $this->is_unlimited() ? 1 : $this->licenses ); } /** * @author Leo Fajardo (@leorw) * @since 2.3.1 * * @return bool */ function is_usd() { return ( 'usd' === $this->currency ); } }PK!9n1freemius/includes/entities/class-fs-affiliate.phpnu[status ); } /** * @author Leo Fajardo * * @return bool */ function is_pending() { return ( 'pending' === $this->status ); } /** * @author Leo Fajardo * * @return bool */ function is_suspended() { return ( 'suspended' === $this->status ); } /** * @author Leo Fajardo * * @return bool */ function is_rejected() { return ( 'rejected' === $this->status ); } /** * @author Leo Fajardo * * @return bool */ function is_blocked() { return ( 'blocked' === $this->status ); } }PK!c[, ` ` 4freemius/includes/entities/class-fs-subscription.phpnu[is_canceled() ) { return false; } return ( ! empty( $this->next_payment ) && strtotime( $this->next_payment ) > WP_FS__SCRIPT_START_TIME ); } /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @return bool */ function is_canceled() { return ! is_null( $this->canceled_at ); } /** * Subscription considered to be new without any payments * if the next payment should be made within less than 24 hours * from the subscription creation. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_first_payment_pending() { return ( WP_FS__TIME_24_HOURS_IN_SEC >= strtotime( $this->next_payment ) - strtotime( $this->created ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7 */ function has_trial() { return ! is_null( $this->trial_ends ); } }PK!7freemius/includes/entities/class-fs-affiliate-terms.phpnu[commission_type ) ? ( '$' . $this->commission ) : ( $this->commission . '%' ); } /** * @author Leo Fajardo (@leorw) * * @return bool */ function has_lifetime_commission() { return ( 0 !== $this->future_payments_days ); } /** * @author Leo Fajardo (@leorw) * * @return bool */ function is_session_cookie() { return ( 0 == $this->cookie_days ); } /** * @author Leo Fajardo (@leorw) * * @return bool */ function has_renewals_commission() { return ( is_null( $this->commission_renewals_days ) || $this->commission_renewals_days > 0 ); } }PK!얀!!6freemius/includes/entities/class-fs-plugin-license.phpnu[is_features_enabled() ) { return 0; } if ( $this->is_unlimited() ) { return 999; } return ( $this->quota - $this->activated - ( $this->is_free_localhost ? 0 : $this->activated_local ) ); } /** * Check if single site license. * * @author Vova Feldman (@svovaf) * @since 1.1.8.1 * * @return bool */ function is_single_site() { return ( is_numeric( $this->quota ) && 1 == $this->quota ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @return bool */ function is_expired() { return ! $this->is_lifetime() && ( strtotime( $this->expiration ) < WP_FS__SCRIPT_START_TIME ); } /** * Check if license is not expired. * * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @return bool */ function is_valid() { return ! $this->is_expired(); } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool */ function is_lifetime() { return is_null( $this->expiration ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.0 * * @return bool */ function is_unlimited() { return is_null( $this->quota ); } /** * Check if license is fully utilized. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param bool|null $is_localhost * * @return bool */ function is_utilized( $is_localhost = null ) { if ( is_null( $is_localhost ) ) { $is_localhost = WP_FS__IS_LOCALHOST_FOR_SERVER; } if ( $this->is_unlimited() ) { return false; } return ! ( $this->is_free_localhost && $is_localhost ) && ( $this->quota <= $this->activated + ( $this->is_free_localhost ? 0 : $this->activated_local ) ); } /** * Check if license can be activated. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param bool|null $is_localhost * * @return bool */ function can_activate( $is_localhost = null ) { return ! $this->is_utilized( $is_localhost ) && $this->is_features_enabled(); } /** * Check if license can be activated on a given number of production and localhost sites. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $production_count * @param int $localhost_count * * @return bool */ function can_activate_bulk( $production_count, $localhost_count ) { if ( $this->is_unlimited() ) { return true; } /** * For simplicity, the logic will work as following: when given X sites to activate the license on, if it's * possible to activate on ALL of them, do the activation. If it's not possible to activate on ALL of them, * do NOT activate on any of them. */ return ( $this->quota >= $this->activated + $production_count + ( $this->is_free_localhost ? 0 : $this->activated_local + $localhost_count ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @return bool */ function is_active() { return ( ! $this->is_cancelled ); } /** * Check if license's plan features are enabled. * * - Either if plan not expired * - If expired, based on the configuration to block features or not. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool */ function is_features_enabled() { return $this->is_active() && ( ! $this->is_block_features || ! $this->is_expired() ); } /** * Subscription considered to be new without any payments * if the license expires in less than 24 hours * from the license creation. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ function is_first_payment_pending() { return ( WP_FS__TIME_24_HOURS_IN_SEC >= strtotime( $this->expiration ) - strtotime( $this->created ) ); } /** * @return int */ function total_activations() { return ( $this->activated + $this->activated_local ); } /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @return string */ function get_html_escaped_masked_secret_key() { return self::mask_secret_key_for_html( $this->secret_key ); } /** * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @param string $secret_key * * @return string */ static function mask_secret_key_for_html( $secret_key ) { return ( // Initial 6 chars - sk_ABC htmlspecialchars( substr( $secret_key, 0, 6 ) ) . // Masking str_pad( '', ( strlen( $secret_key ) - 9 ) * 6, '•' ) . // Last 3 chars. htmlspecialchars( substr( $secret_key, - 3 ) ) ); } } PK!g[/freemius/includes/entities/class-fs-payment.phpnu[bound_payment_id ) && 0 > $this->gross ); } /** * Checks if the payment was migrated from another platform. * * @author Vova Feldman (@svovaf) * @since 2.0.2 * * @return bool */ function is_migrated() { return ( 0 != $this->source ); } /** * Returns the gross in this format: * `{symbol}{amount | 2 decimal digits} {currency | uppercase}` * * Examples: £9.99 GBP, -£9.99 GBP. * * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @return string */ function formatted_gross() { return ( ( $this->gross < 0 ? '-' : '' ) . $this->get_symbol() . number_format( abs( $this->gross ), 2, '.', ',' ) . ' ' . strtoupper( $this->currency ) ); } /** * A map between supported currencies with their symbols. * * @var array */ static $CURRENCY_2_SYMBOL; /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @return string */ private function get_symbol() { if ( ! isset( self::$CURRENCY_2_SYMBOL ) ) { // Lazy load. self::$CURRENCY_2_SYMBOL = array( self::CURRENCY_USD => '$', self::CURRENCY_GBP => '£', self::CURRENCY_EUR => '€', ); } return self::$CURRENCY_2_SYMBOL[ $this->currency ]; } }PK!# ))2freemius/includes/entities/class-fs-plugin-tag.phpnu[release_mode ); } }PK!qe//-freemius/includes/class-freemius-abstract.phpnu[is_registered() && $fs->is_tracking_allowed()` * * @since 1.0.1 * * @param bool $ignore_anonymous_state Since 2.5.1 * * @return bool */ abstract function is_registered( $ignore_anonymous_state = false ); /** * Check if the user skipped connecting the account with Freemius. * * @since 1.0.7 * * @return bool */ abstract function is_anonymous(); /** * Check if the user currently in activation mode. * * @since 1.0.7 * * @return bool */ abstract function is_activation_mode(); #endregion #---------------------------------------------------------------------------------- #region Module Type #---------------------------------------------------------------------------------- /** * Checks if the plugin's type is "plugin". The other type is "theme". * * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool */ abstract function is_plugin(); /** * Checks if the module type is "theme". The other type is "plugin". * * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool */ function is_theme() { return ( ! $this->is_plugin() ); } #endregion #---------------------------------------------------------------------------------- #region Permissions #---------------------------------------------------------------------------------- /** * Check if plugin must be WordPress.org compliant. * * @since 1.0.7 * * @return bool */ abstract function is_org_repo_compliant(); /** * Check if plugin is allowed to install executable files. * * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @return bool */ function is_allowed_to_install() { return ( $this->is_premium() || ! $this->is_org_repo_compliant() ); } #endregion /** * Check if user in trial or in free plan (not paying). * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @return bool */ function is_not_paying() { return ( $this->is_trial() || $this->is_free_plan() ); } /** * Check if the user has an activated and valid paid license on current plugin's install. * * @since 1.0.9 * * @return bool */ abstract function is_paying(); /** * Check if the user is paying or in trial. * * @since 1.0.9 * * @return bool */ function is_paying_or_trial() { return ( $this->is_paying() || $this->is_trial() ); } /** * Check if user in a trial or have feature enabled license. * * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @return bool */ abstract function can_use_premium_code(); #---------------------------------------------------------------------------------- #region Premium Only #---------------------------------------------------------------------------------- /** * All logic wrapped in methods with "__premium_only()" suffix will be only * included in the premium code. * * Example: * if ( freemius()->is__premium_only() ) { * ... * } */ /** * Returns true when running premium plugin code. * * @since 1.0.9 * * @return bool */ function is__premium_only() { return $this->is_premium(); } /** * Check if the user has an activated and valid paid license on current plugin's install. * * @since 1.0.9 * * @return bool * */ function is_paying__premium_only() { return ( $this->is__premium_only() && $this->is_paying() ); } /** * All code wrapped in this statement will be only included in the premium code. * * @since 1.0.9 * * @param string $plan Plan name. * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ function is_plan__premium_only( $plan, $exact = false ) { return ( $this->is_premium() && $this->is_plan( $plan, $exact ) ); } /** * Check if plan matches active license' plan or active trial license' plan. * * All code wrapped in this statement will be only included in the premium code. * * @since 1.0.9 * * @param string $plan Plan name. * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ function is_plan_or_trial__premium_only( $plan, $exact = false ) { return ( $this->is_premium() && $this->is_plan_or_trial( $plan, $exact ) ); } /** * Check if the user is paying or in trial. * * All code wrapped in this statement will be only included in the premium code. * * @since 1.0.9 * * @return bool */ function is_paying_or_trial__premium_only() { return $this->is_premium() && $this->is_paying_or_trial(); } /** * Check if the user has an activated and valid paid license on current plugin's install. * * @since 1.0.4 * * @return bool * * @deprecated Method name is confusing since it's not clear from the name the code will be removed. * @using Alias to is_paying__premium_only() */ function is_paying__fs__() { return $this->is_paying__premium_only(); } /** * Check if user in a trial or have feature enabled license. * * All code wrapped in this statement will be only included in the premium code. * * @author Vova Feldman (@svovaf) * @since 1.1.9 * * @return bool */ function can_use_premium_code__premium_only() { return $this->is_premium() && $this->can_use_premium_code(); } #endregion #---------------------------------------------------------------------------------- #region Trial #---------------------------------------------------------------------------------- /** * Check if the user in a trial. * * @since 1.0.3 * * @return bool */ abstract function is_trial(); /** * Check if trial already utilized. * * @since 1.0.9 * * @return bool */ abstract function is_trial_utilized(); #endregion #---------------------------------------------------------------------------------- #region Plans #---------------------------------------------------------------------------------- /** * Check if the user is on the free plan of the product. * * @since 1.0.4 * * @return bool */ abstract function is_free_plan(); /** * @since 1.0.2 * * @param string $plan Plan name. * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ abstract function is_plan( $plan, $exact = false ); /** * Check if plan based on trial. If not in trial mode, should return false. * * @since 1.0.9 * * @param string $plan Plan name. * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ abstract function is_trial_plan( $plan, $exact = false ); /** * Check if plan matches active license' plan or active trial license' plan. * * @since 1.0.9 * * @param string $plan Plan name. * @param bool $exact If true, looks for exact plan. If false, also check "higher" plans. * * @return bool */ function is_plan_or_trial( $plan, $exact = false ) { return $this->is_plan( $plan, $exact ) || $this->is_trial_plan( $plan, $exact ); } /** * Check if plugin has any paid plans. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool */ abstract function has_paid_plan(); /** * Check if plugin has any free plan, or is it premium only. * * Note: If no plans configured, assume plugin is free. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool */ abstract function has_free_plan(); /** * Check if plugin is premium only (no free plans). * * NOTE: is__premium_only() is very different method, don't get confused. * * @author Vova Feldman (@svovaf) * @since 1.1.9 * * @return bool */ abstract function is_only_premium(); /** * Check if module has a premium code version. * * Serviceware module might be freemium without any * premium code version, where the paid features * are all part of the service. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @return bool */ abstract function has_premium_version(); /** * Check if module has any release on Freemius, * or all plugin's code is on WordPress.org (Serviceware). * * @return bool */ function has_release_on_freemius() { return ! $this->is_org_repo_compliant() || $this->has_premium_version(); } /** * Checks if it's a freemium plugin. * * @author Vova Feldman (@svovaf) * @since 1.1.9 * * @return bool */ function is_freemium() { return $this->has_paid_plan() && $this->has_free_plan(); } /** * Check if module has only one plan. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 * * @return bool */ abstract function is_single_plan(); #endregion /** * Check if running payments in sandbox mode. * * @since 1.0.4 * * @return bool */ abstract function is_payments_sandbox(); /** * Check if running test vs. live plugin. * * @since 1.0.5 * * @return bool */ abstract function is_live(); /** * Check if running premium plugin code. * * @since 1.0.5 * * @return bool */ abstract function is_premium(); /** * Get upgrade URL. * * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @param string $period Billing cycle. * * @return string */ abstract function get_upgrade_url( $period = WP_FS__PERIOD_ANNUALLY ); /** * Check if Freemius was first added in a plugin update. * * @author Vova Feldman (@svovaf) * @since 1.1.5 * * @return bool */ function is_plugin_update() { return ! $this->is_plugin_new_install(); } /** * Check if Freemius was part of the plugin when the user installed it first. * * @author Vova Feldman (@svovaf) * @since 1.1.5 * * @return bool */ abstract function is_plugin_new_install(); #---------------------------------------------------------------------------------- #region Marketing #---------------------------------------------------------------------------------- /** * Check if current user purchased any other plugins before. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ abstract function has_purchased_before(); /** * Check if current user classified as an agency. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ abstract function is_agency(); /** * Check if current user classified as a developer. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ abstract function is_developer(); /** * Check if current user classified as a business. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @return bool */ abstract function is_business(); #endregion }PK!B-freemius/includes/class-fs-plugin-updater.phpnu[get_id(); if ( ! isset( self::$_INSTANCES[ $key ] ) ) { self::$_INSTANCES[ $key ] = new self( $freemius ); } return self::$_INSTANCES[ $key ]; } #endregion private function __construct( Freemius $freemius ) { $this->_fs = $freemius; $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->filters(); } /** * Initiate required filters. * * @author Vova Feldman (@svovaf) * @since 1.0.4 */ private function filters() { // Override request for plugin information add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 ); $this->add_transient_filters(); /** * If user has the premium plugin's code but do NOT have an active license, * encourage him to upgrade by showing that there's a new release, but instead * of showing an update link, show upgrade link to the pricing page. * * @since 1.1.6 * */ // WP 2.9+ add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array( &$this, 'catch_plugin_update_row' ), 9 ); add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array( &$this, 'edit_and_echo_plugin_update_row' ), 11, 2 ); if ( ! $this->_fs->has_any_active_valid_license() ) { add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) ); } if ( ! WP_FS__IS_PRODUCTION_MODE ) { add_filter( 'http_request_host_is_external', array( $this, 'http_request_host_is_external_filter' ), 10, 3 ); } if ( $this->_fs->is_premium() ) { if ( ! $this->is_correct_folder_name() ) { add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 ); } add_filter( 'upgrader_pre_install', array( 'FS_Plugin_Updater', '_store_basename_for_source_adjustment' ), 1, 2 ); add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 ); if ( ! $this->_fs->has_any_active_valid_license() ) { add_filter( 'wp_prepare_themes_for_js', array( &$this, 'change_theme_update_info_html' ), 10, 1 ); } } } /** * @author Leo Fajardo (@leorw) * @since 2.1.4 */ function catch_plugin_information_dialog_contents() { if ( 'plugin-information' !== fs_request_get( 'tab', false ) || $this->_fs->get_slug() !== fs_request_get_raw( 'plugin', false ) ) { return; } add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 ); ob_start(); } /** * @author Leo Fajardo (@leorw) * @since 2.1.4 * * @param string $hook_suffix */ 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_raw( 'plugin', false ) ) { return; } $license = $this->_fs->_get_license(); $subscription = ( is_object( $license ) && ! $license->is_lifetime() ) ? $this->_fs->_get_subscription( $license->id ) : null; $contents = ob_get_clean(); $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 !== $install_or_update_button_id_attribute_pos ) { $install_or_update_button_start_pos = strrpos( substr( $contents, 0, $install_or_update_button_id_attribute_pos ), '', $install_or_update_button_id_attribute_pos ) + strlen( '' ) ); /** * The part of the contents without the update button. * * @author Leo Fajardo (@leorw) * @since 2.2.5 */ $modified_contents = substr( $contents, 0, $install_or_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. */ $install_or_update_button = preg_replace( '/(\)(.+)(\<\/a>)/is', is_object( $license ) ? sprintf( '$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 ) : WP_FS__PERIOD_LIFETIME, false, array( 'licenses' => $license->quota ) ), fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() ) ) : sprintf( '$1$4%s$6%s$8', $this->_fs->pricing_url(), fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() ) ), $install_or_update_button ); /** * Append the modified button. * * @author Leo Fajardo (@leorw) * @since 2.2.5 */ $modified_contents .= $install_or_update_button; /** * Append the remaining part of the contents after the update button. * * @author Leo Fajardo (@leorw) * @since 2.2.5 */ $modified_contents .= substr( $contents, $install_or_update_button_end_pos ); $contents = $modified_contents; } echo $contents; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 */ private function add_transient_filters() { 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; } add_filter( 'pre_set_site_transient_update_plugins', array( &$this, 'pre_set_site_transient_update_plugins_filter' ) ); add_filter( 'pre_set_site_transient_update_themes', array( &$this, 'pre_set_site_transient_update_plugins_filter' ) ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 */ private function remove_transient_filters() { remove_filter( 'pre_set_site_transient_update_plugins', array( &$this, 'pre_set_site_transient_update_plugins_filter' ) ); remove_filter( 'pre_set_site_transient_update_themes', array( &$this, 'pre_set_site_transient_update_plugins_filter' ) ); } /** * Capture plugin update row by turning output buffering. * * @author Vova Feldman (@svovaf) * @since 1.1.6 */ function catch_plugin_update_row() { ob_start(); } /** * Overrides default update message format with "renew your license" message. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $file * @param array $plugin_data */ function edit_and_echo_plugin_update_row( $file, $plugin_data ) { $plugin_update_row = ob_get_clean(); $current = get_site_transient( 'update_plugins' ); if ( ! isset( $current->response[ $file ] ) ) { echo $plugin_update_row; return; } $r = $current->response[ $file ]; $has_beta_update = $this->_fs->has_beta_update(); if ( $this->_fs->has_any_active_valid_license() ) { if ( $has_beta_update ) { /** * Turn the "new version" text into "new Beta version". * * Sample input: * There is a new version of Awesome Plugin available. update now. * Output: * There is a new Beta version of Awesome Plugin available. update now. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $plugin_update_row = preg_replace( '/(\)(.+)(\.+\)/is', ( '$1' . sprintf( fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ), $has_beta_update ? fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) : fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ), $this->_fs->get_plugin_title() ) . ' ' . '$3' . '$6' ), $plugin_update_row ); } } else { /** * Turn the "new version" text into a link that opens the plugin information dialog when clicked and * make the "View version x details" text link to the checkout page instead of opening the plugin * information dialog when clicked. * * Sample input: * There is a new version of Awesome Plugin available. update now. * Output: * There is a Buy a license now to access version x.y.z security & feature updates, and support. * OR * There is a Buy a license now to access version x.y.z security & feature updates, and support. * * @author Leo Fajardo (@leorw) */ $plugin_update_row = preg_replace( '/(\)(.+)(\.+\)/is', ( '$1' . sprintf( fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ), sprintf( '%s', '$5', $has_beta_update ? fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) : fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ) ), $this->_fs->get_plugin_title() ) . ' ' . $this->_fs->version_upgrade_checkout_link( $r->new_version ) . '$6' ), $plugin_update_row ); } if ( $this->_fs->is_plugin() && isset( $r->upgrade_notice ) && strlen( trim( $r->upgrade_notice ) ) > 0 ) { $slug = $this->_fs->get_slug(); $upgrade_notice_html = sprintf( '

    %3$s %4$s

    ', $slug, $this->_fs->get_module_type(), fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ), esc_html( $r->upgrade_notice ) ); $plugin_update_row = str_replace( '', '' . $upgrade_notice_html, $plugin_update_row ); } echo $plugin_update_row; } /** * @author Leo Fajardo (@leorw) * @since 2.0.2 * * @param array $prepared_themes * * @return array */ function change_theme_update_info_html( $prepared_themes ) { $theme_basename = $this->_fs->get_plugin_basename(); if ( ! isset( $prepared_themes[ $theme_basename ] ) ) { return $prepared_themes; } $themes_update = get_site_transient( 'update_themes' ); if ( ! isset( $themes_update->response[ $theme_basename ] ) || empty( $themes_update->response[ $theme_basename ]['package'] ) ) { return $prepared_themes; } $prepared_themes[ $theme_basename ]['update'] = preg_replace( '/(\)(.+)(\)/is', '$1 $2 ' . $this->_fs->version_upgrade_checkout_link( $themes_update->response[ $theme_basename ]['new_version'] ) . '$4', $prepared_themes[ $theme_basename ]['update'] ); // Set to false to prevent the "Update now" link for the context theme from being shown on the "Themes" page. $prepared_themes[ $theme_basename ]['hasPackage'] = false; return $prepared_themes; } /** * Since WP version 3.6, a new security feature was added that denies access to repository with a local ip. * During development mode we want to be able updating plugin versions via our localhost repository. This * filter white-list all domains including "api.freemius". * * @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/ * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param bool $allow * @param string $host * @param string $url * * @return bool */ function http_request_host_is_external_filter( $allow, $host, $url ) { return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow; } /** * Check for Updates at the defined API endpoint and modify the update array. * * This function dives into the update api just when WordPress creates its update array, * then adds a custom API call and injects the custom plugin data retrieved from the API. * It is reassembled from parts of the native WordPress plugin update code. * See wp-includes/update.php line 121 for the original wp_update_plugins() function. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @uses FS_Api * * @param object $transient_data Update array build by WordPress. * * @return object Modified update array with custom plugin data. */ function pre_set_site_transient_update_plugins_filter( $transient_data ) { $this->_logger->entrance(); /** * "plugins" or "themes". * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ $module_type = $this->_fs->get_module_type() . 's'; /** * Ensure that we don't mix plugins update info with themes update info. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) { return $transient_data; } if ( empty( $transient_data ) || defined( 'WP_FS__UNINSTALL_MODE' ) ) { return $transient_data; } global $wp_current_filter; $current_plugin_version = $this->_fs->get_plugin_version(); if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) { if ( is_null( $this->_update_details ) || ( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version ) ) { /** * After an update, clear the stored update details and reparse the plugin's main file in order to get * the updated version's information and prevent the previous update information from showing up on the * updates page. * * @author Leo Fajardo (@leorw) * @since 2.3.1 */ $this->_update_details = null; $current_plugin_version = $this->_fs->get_plugin_version( true ); } } if ( ! isset( $this->_update_details ) ) { // Get plugin's newest update. $new_version = $this->_fs->get_update( false, fs_request_get_bool( 'force-check' ), WP_FS__TIME_24_HOURS_IN_SEC / 24, $current_plugin_version ); $this->_update_details = false; if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) { $this->_logger->log( 'Found newer plugin version ' . $new_version->version ); /** * Cache plugin details locally since set_site_transient( 'update_plugins' ) * called multiple times and the non wp.org plugins are filtered after the * call to .org. * * @since 1.1.8.1 */ $this->_update_details = $this->get_update_details( $new_version ); } } // Alias. $basename = $this->_fs->premium_plugin_basename(); if ( is_object( $this->_update_details ) ) { if ( isset( $transient_data->no_update ) ) { unset( $transient_data->no_update[ $basename ] ); } if ( ! isset( $transient_data->response ) ) { $transient_data->response = array(); } // Add plugin to transient data. $transient_data->response[ $basename ] = $this->_fs->is_plugin() ? $this->_update_details : (array) $this->_update_details; } else { if ( isset( $transient_data->response ) ) { /** * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ unset( $transient_data->response[ $basename ] ); } if ( ! isset( $transient_data->no_update ) ) { $transient_data->no_update = array(); } /** * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI. * * @since 2.4.1 * @link https://make.wordpress.org/core/2020/07/30/recommended-usage-of-the-updates-api-to-support-the-auto-updates-ui-for-plugins-and-themes-in-wordpress-5-5/ */ $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ? (object) array( 'id' => $basename, 'slug' => $this->_fs->get_slug(), 'plugin' => $basename, 'new_version' => $this->_fs->get_plugin_version(), 'url' => '', 'package' => '', 'icons' => array(), 'banners' => array(), 'banners_rtl' => array(), 'tested' => '', 'requires_php' => '', 'compatibility' => new stdClass(), ) : array( 'theme' => $basename, 'new_version' => $this->_fs->get_plugin_version(), 'url' => '', 'package' => '', 'requires' => '', 'requires_php' => '', ); } $slug = $this->_fs->get_slug(); if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) { if ( ! isset( $this->_translation_updates ) ) { $this->_translation_updates = array(); $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug ); if ( ! empty( $translation_updates ) ) { $this->_translation_updates = $translation_updates; } } if ( ! empty( $this->_translation_updates ) ) { $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ? $transient_data->translations : array(); $current_plugin_translation_updates_map = array(); foreach ( $all_translation_updates as $key => $translation_update ) { if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) { $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update; unset( $all_translation_updates[ $key ] ); } } 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'], '>' ) ) { $current_plugin_translation_updates_map[ $lang ] = $translation_update; } } $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) ); } } return $transient_data; } /** * Get module's required data for the updates' mechanism. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_Plugin_Tag $new_version * * @return object */ 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 = 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(); if ( ! empty( $icon ) ) { $update->icons = array( // '1x' => $icon, // '2x' => $icon, 'default' => $icon, ); } if ( $this->_fs->is_premium() ) { $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false ); if ( isset( $latest_tag->readme ) && isset( $latest_tag->readme->upgrade_notice ) && ! empty( $latest_tag->readme->upgrade_notice ) ) { $update->upgrade_notice = $latest_tag->readme->upgrade_notice; } } $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename(); return $update; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param FS_Plugin_Tag $new_version * * @return bool */ private function is_new_version_premium( FS_Plugin_Tag $new_version ) { $query_str = parse_url( $new_version->url, PHP_URL_QUERY ); if ( empty( $query_str ) ) { return false; } parse_str( $query_str, $params ); return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] ); } /** * Update the updates transient with the module's update information. * * This method is required for multisite environment. * If a module is site activated (not network) and not on the main site, * the module will NOT be executed on the network level, therefore, the * custom updates logic will not be executed as well, so unless we force * the injection of the update into the updates transient, premium updates * will not work. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param \FS_Plugin_Tag $new_version */ function set_update_data( FS_Plugin_Tag $new_version ) { $this->_logger->entrance(); if ( ! $this->is_new_version_premium( $new_version ) ) { return; } $transient_key = "update_{$this->_fs->get_module_type()}s"; $transient_data = get_site_transient( $transient_key ); $transient_data = is_object( $transient_data ) ? $transient_data : new stdClass(); // Alias. $basename = $this->_fs->get_plugin_basename(); $is_plugin = $this->_fs->is_plugin(); if ( ! isset( $transient_data->response ) || ! is_array( $transient_data->response ) ) { $transient_data->response = array(); } else if ( ! empty( $transient_data->response[ $basename ] ) ) { $version = $is_plugin ? ( ! empty( $transient_data->response[ $basename ]->new_version ) ? $transient_data->response[ $basename ]->new_version : null ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ? $transient_data->response[ $basename ]['new_version'] : null ); if ( $version == $new_version->version ) { // The update data is already set. return; } } // Remove the added filters. $this->remove_transient_filters(); $this->_update_details = $this->get_update_details( $new_version ); // Set update data in transient. $transient_data->response[ $basename ] = $is_plugin ? $this->_update_details : (array) $this->_update_details; if ( ! isset( $transient_data->checked ) || ! is_array( $transient_data->checked ) ) { $transient_data->checked = array(); } // Flag the module as if it was already checked. $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version(); $transient_data->last_checked = time(); set_site_transient( $transient_key, $transient_data ); $this->add_transient_filters(); } /** * @author Leo Fajardo (@leorw) * @since 2.0.2 */ function delete_update_data() { $this->_logger->entrance(); $transient_key = "update_{$this->_fs->get_module_type()}s"; $transient_data = get_site_transient( $transient_key ); // Alias $basename = $this->_fs->get_plugin_basename(); if ( ! is_object( $transient_data ) || ! isset( $transient_data->response ) || ! is_array( $transient_data->response ) || empty( $transient_data->response[ $basename ] ) ) { return; } unset( $transient_data->response[ $basename ] ); // Remove the added filters. $this->remove_transient_filters(); set_site_transient( $transient_key, $transient_data ); $this->add_transient_filters(); } /** * Try to fetch plugin's info from .org repository. * * @author Vova Feldman (@svovaf) * @since 1.0.5 * * @param string $action * @param object $args * * @return bool|mixed */ static function _fetch_plugin_info_from_repository( $action, $args ) { $url = $http_url = 'http://api.wordpress.org/plugins/info/1.2/'; $url = add_query_arg( array( 'action' => $action, 'request' => $args, ), $url ); 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 = 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 ) || isset( $res->error ) ) { return false; } return $res; } /** * Fetches module translation updates from wordpress.org. * * @author Leo Fajardo (@leorw) * @since 2.1.2 * * @param string $module_type * @param string $slug * * @return array|null */ private function fetch_wp_org_module_translation_updates( $module_type, $slug ) { $plugin_data = $this->_fs->get_plugin_data(); $locales = array_values( get_available_languages() ); $locales = apply_filters( "{$module_type}_update_check_locales", $locales ); $locales = array_unique( $locales ); $plugin_basename = $this->_fs->get_plugin_basename(); if ( 'themes' === $module_type ) { $plugin_basename = $slug; } global $wp_version; $request_args = array( 'timeout' => 15, 'body' => array( "{$module_type}" => json_encode( array( "{$module_type}" => array( $plugin_basename => array( 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ), 'Author' => $plugin_data['Author'], ) ) ) ), 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ), 'locale' => json_encode( $locales ) ), 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) ) ); $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/"; if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) { $url = set_url_scheme( $url, 'https' ); } $raw_response = Freemius::safe_remote_post( $url, $request_args, WP_FS__TIME_24_HOURS_IN_SEC, WP_FS__TIME_12_HOURS_IN_SEC, false ); if ( is_wp_error( $raw_response ) ) { return null; } $response = json_decode( wp_remote_retrieve_body( $raw_response ), true ); if ( ! is_array( $response ) ) { return null; } if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) { return null; } return $response['translations']; } /** * @author Leo Fajardo (@leorw) * @since 2.1.2 * * @param string $module_type * @param string $slug * * @return array */ private function get_installed_translations( $module_type, $slug ) { if ( function_exists( 'wp_get_installed_translations' ) ) { return wp_get_installed_translations( $module_type ); } $dir = "/{$module_type}"; if ( ! is_dir( WP_LANG_DIR . $dir ) ) return array(); $files = scandir( WP_LANG_DIR . $dir ); if ( ! $files ) return array(); $language_data = array(); foreach ( $files as $file ) { if ( 0 !== strpos( $file, $slug ) ) { continue; } if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) { continue; } if ( substr( $file, -3 ) !== '.po' ) { continue; } if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) { continue; } if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) { continue; } list( , $textdomain, $language ) = $match; if ( '' === $textdomain ) { $textdomain = 'default'; } $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" ); } return $language_data; } /** * Updates information on the "View version x.x details" page with custom data. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @uses FS_Api * * @param object $data * @param string $action * @param mixed $args * * @return object */ function plugins_api_filter( $data, $action = '', $args = null ) { $this->_logger->entrance(); if ( ( 'plugin_information' !== $action ) || ! isset( $args->slug ) ) { return $data; } $addon = false; $is_addon = false; $addon_version = false; if ( $this->_fs->get_slug() !== $args->slug ) { $addon = $this->_fs->get_addon_by_slug( $args->slug ); if ( ! is_object( $addon ) ) { return $data; } if ( $this->_fs->is_addon_activated( $addon->id ) ) { $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version(); } else if ( $this->_fs->is_addon_installed( $addon->id ) ) { $addon_plugin_data = get_plugin_data( ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ), false, false ); if ( ! empty( $addon_plugin_data ) ) { $addon_version = $addon_plugin_data['Version']; } } $is_addon = true; } $plugin_in_repo = false; if ( ! $is_addon ) { // Try to fetch info from .org repository. $data = self::_fetch_plugin_info_from_repository( $action, $args ); $plugin_in_repo = ( false !== $data ); } if ( ! $plugin_in_repo ) { $data = $args; // Fetch as much as possible info from local files. $plugin_local_data = $this->_fs->get_plugin_data(); $data->name = $plugin_local_data['Name']; $data->author = $plugin_local_data['Author']; $data->sections = array( 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.', ); // @todo Store extra plugin info on Freemius or parse readme.txt markup. /*$info = $this->_fs->get_api_site_scope()->call('/information.json'); if ( !isset($info->error) ) { $data = $info; }*/ } $plugin_version = $is_addon ? $addon_version : $this->_fs->get_plugin_version(); // Get plugin's newest update. $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version ); if ( ! is_object( $new_version ) || empty( $new_version->version ) ) { $data->version = $plugin_version; } else { if ( $is_addon ) { $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ); $data->slug = $addon->slug; $data->url = WP_FS__ADDRESS; $data->package = $new_version->url; } 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; } $data->version = $new_version->version; $data->download_link = $new_version->url; if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) { $new_version_readme_data = $new_version->readme; if ( isset( $new_version_readme_data->sections ) ) { $new_version_readme_data->sections = (array) $new_version_readme_data->sections; } else { $new_version_readme_data->sections = array(); } if ( isset( $data->sections ) ) { if ( isset( $data->sections['screenshots'] ) ) { $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots']; } if ( isset( $data->sections['reviews'] ) ) { $new_version_readme_data->sections['reviews'] = $data->sections['reviews']; } } if ( isset( $new_version_readme_data->banners ) ) { $new_version_readme_data->banners = (array) $new_version_readme_data->banners; } else if ( isset( $data->banners ) ) { $new_version_readme_data->banners = $data->banners; } $wp_org_sections = array( 'author', 'author_profile', 'rating', 'ratings', 'num_ratings', 'support_threads', 'support_threads_resolved', 'active_installs', 'added', 'homepage' ); foreach ( $wp_org_sections as $wp_org_section ) { if ( isset( $data->{$wp_org_section} ) ) { $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section}; } } $data = $new_version_readme_data; } } 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 * * @param number|bool $addon_id * @param bool|string $newer_than Since 2.2.1 * @param bool|string $fetch_readme Since 2.2.1 * * @return object */ private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) { return $this->_fs->_fetch_latest_version( $addon_id, true, WP_FS__TIME_24_HOURS_IN_SEC, $newer_than, $fetch_readme ); } /** * Checks if a given basename has a matching folder name * with the current context plugin. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @return bool */ private function is_correct_folder_name() { return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) ); } /** * This is a special after upgrade handler for migrating modules * that didn't use the '-premium' suffix folder structure before * the migration. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @param bool $response Install response. * @param array $hook_extra Extra arguments passed to hooked filters. * @param array $result Installation result data. * * @return bool */ function _maybe_update_folder_name( $response, $hook_extra, $result ) { $basename = $this->_fs->get_plugin_basename(); if ( true !== $response || empty( $hook_extra ) || empty( $hook_extra['plugin'] ) || $basename !== $hook_extra['plugin'] ) { return $response; } $active_plugins_basenames = get_option( 'active_plugins' ); foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) { if ( $basename === $active_plugin_basename ) { // Get filename including extension. $filename = basename( $basename ); $new_basename = plugin_basename( trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) . $filename ); // Verify that the expected correct path exists. if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) { // Override active plugin name. $active_plugins_basenames[ $key ] = $new_basename; update_option( 'active_plugins', $active_plugins_basenames ); } break; } } return $response; } #---------------------------------------------------------------------------------- #region Auto Activation #---------------------------------------------------------------------------------- /** * Installs and active a plugin when explicitly requested that from a 3rd party service. * * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin. * * @link http://tgmpluginactivation.com/ * * @author Vova Feldman * @since 1.2.1.7 * * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/ * * @uses WP_Filesystem * @uses WP_Error * @uses WP_Upgrader * @uses Plugin_Upgrader * @uses Plugin_Installer_Skin * @uses Plugin_Upgrader_Skin * * @param number|bool $plugin_id * * @return array */ function install_and_activate_plugin( $plugin_id = false ) { if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) { // Invalid plugin ID. return array( 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), 'code' => 'invalid_module_id', ); } $is_addon = false; if ( FS_Plugin::is_valid_id( $plugin_id ) && $plugin_id != $this->_fs->get_id() ) { $addon = $this->_fs->get_addon( $plugin_id ); if ( ! is_object( $addon ) ) { // Invalid add-on ID. return array( 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ), 'code' => 'invalid_module_id', ); } $slug = $addon->slug; $premium_slug = $addon->premium_slug; $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ); $is_addon = true; } else { $slug = $this->_fs->get_slug(); $premium_slug = $this->_fs->get_premium_slug(); $title = $this->_fs->get_plugin_title() . ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' ); } if ( $this->is_premium_plugin_active( $plugin_id ) ) { // Premium version already activated. return array( 'message' => $is_addon ? $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) : $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ), 'code' => 'premium_installed', ); } $latest_version = $this->get_latest_download_details( $plugin_id, false, false ); $target_folder = $premium_slug; // Prep variables for Plugin_Installer_Skin class. $extra = array(); $extra['slug'] = $target_folder; $source = $latest_version->url; $api = null; $install_url = add_query_arg( array( 'action' => 'install-plugin', 'plugin' => urlencode( $slug ), ), 'update.php' ); if ( ! class_exists( 'Plugin_Upgrader', false ) ) { // Include required resources for the installation. require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } $skin_args = array( 'type' => 'web', 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ), 'url' => esc_url_raw( $install_url ), 'nonce' => 'install-plugin_' . $slug, 'plugin' => '', 'api' => $api, 'extra' => $extra, ); // $skin = new Automatic_Upgrader_Skin( $skin_args ); // $skin = new Plugin_Installer_Skin( $skin_args ); $skin = new WP_Ajax_Upgrader_Skin( $skin_args ); // Create a new instance of Plugin_Upgrader. $upgrader = new Plugin_Upgrader( $skin ); // Perform the action and install the plugin from the $source urldecode(). add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 ); $install_result = $upgrader->install( $source ); remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 ); if ( is_wp_error( $install_result ) ) { return array( 'message' => $install_result->get_error_message(), 'code' => $install_result->get_error_code(), ); } elseif ( is_wp_error( $skin->result ) ) { return array( 'message' => $skin->result->get_error_message(), 'code' => $skin->result->get_error_code(), ); } elseif ( $skin->get_errors()->get_error_code() ) { return array( 'message' => $skin->get_error_messages(), 'code' => 'unknown', ); } elseif ( is_null( $install_result ) ) { global $wp_filesystem; $error_code = 'unable_to_connect_to_filesystem'; $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' ); // Pass through the error from WP_Filesystem if one was raised. if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { $error_message = $wp_filesystem->errors->get_error_message(); } return array( 'message' => $error_message, 'code' => $error_code, ); } // Grab the full path to the main plugin's file. $plugin_activate = $upgrader->plugin_info(); // Try to activate the plugin. $activation_result = $this->try_activate_plugin( $plugin_activate ); if ( is_wp_error( $activation_result ) ) { return array( 'message' => $activation_result->get_error_message(), 'code' => $activation_result->get_error_code(), ); } return $skin->get_upgrade_messages(); } /** * Tries to activate a plugin. If fails, returns the error. * * @author Vova Feldman * @since 1.2.1.7 * * @param string $file_path Path within wp-plugins/ to main plugin file. * This determines the styling of the output messages. * * @return bool|WP_Error */ protected function try_activate_plugin( $file_path ) { $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() ); return is_wp_error( $activate ) ? $activate : true; } /** * Check if a premium module version is already active. * * @author Vova Feldman * @since 1.2.1.7 * * @param number|bool $plugin_id * * @return bool */ private function is_premium_plugin_active( $plugin_id = false ) { if ( $plugin_id != $this->_fs->get_id() ) { return $this->_fs->is_addon_activated( $plugin_id, true ); } return is_plugin_active( $this->_fs->premium_plugin_basename() ); } /** * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below. * * @author Leo Fajardo (@leorw) * @since 2.2.1 * * @param bool|WP_Error $response Response. * @param array $hook_extra Extra arguments passed to hooked filters. * * @return bool|WP_Error */ static function _store_basename_for_source_adjustment( $response, $hook_extra ) { if ( isset( $hook_extra['plugin'] ) ) { self::$_upgrade_basename = $hook_extra['plugin']; } else if ( isset( $hook_extra['theme'] ) ) { self::$_upgrade_basename = $hook_extra['theme']; } else { self::$_upgrade_basename = null; } return $response; } /** * Adjust the plugin directory name if necessary. * Assumes plugin has a folder (not a single file plugin). * * The final destination directory of a plugin is based on the subdirectory name found in the * (un)zipped source. In some cases this subdirectory name is not the same as the expected * slug and the plugin will not be recognized as installed. This is fixed by adjusting * the temporary unzipped source subdirectory name to the expected plugin slug. * * @author Vova Feldman * @since 1.2.1.7 * @since 2.2.1 The method was converted to static since when the admin update bulk products via the Updates section, the logic applies the `upgrader_source_selection` filter for every product that is being updated. * * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/. * @param string $remote_source Path to upgrade/zip-file-name.tmp. * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin. * * @return string|WP_Error */ static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) { if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) { return $source; } $basename = self::$_upgrade_basename; $is_theme = false; // Figure out what the slug is supposed to be. if ( isset( $upgrader->skin->options['extra'] ) ) { // Set by the auto-install logic. $desired_slug = $upgrader->skin->options['extra']['slug']; } else if ( ! empty( $basename ) ) { /** * If it doesn't end with ".php", it's a theme. * * @author Leo Fajardo (@leorw) * @since 2.2.1 */ $is_theme = ( ! fs_ends_with( $basename, '.php' ) ); $desired_slug = ( ! $is_theme ) ? dirname( $basename ) : // Theme slug $basename; } else { // Can't figure out the desired slug, stop the execution. return $source; } if ( is_multisite() ) { /** * If we are running in a multisite environment and the product is not network activated, * the instance will not exist anyway. Therefore, try to update the source if necessary * regardless if the Freemius instance of the product exists or not. * * @author Vova Feldman */ } else if ( ! empty( $basename ) ) { $fs = Freemius::get_instance_by_file( $basename, $is_theme ? WP_FS__MODULE_TYPE_THEME : WP_FS__MODULE_TYPE_PLUGIN ); if ( ! is_object( $fs ) ) { /** * If the Freemius instance does not exist on a non-multisite network environment, it means that: * 1. The product is not powered by Freemius; OR * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change. * * @author Leo Fajardo (@leorw) * @since 2.2.1 */ return $source; } } $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) ); if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) { $from_path = untrailingslashit( $source ); $to_path = trailingslashit( $remote_source ) . $desired_slug; if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) { return trailingslashit( $to_path ); } return new WP_Error( 'rename_failed', fs_text_inline( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'module-package-rename-failure' ), array( 'found' => $subdir_name, 'expected' => $desired_slug ) ); } return $source; } #endregion } PK!2r7freemius/includes/managers/class-fs-license-manager.phpnu[get_slug() ); // // if ( ! isset( self::$_instances[ $slug ] ) ) { // self::$_instances[ $slug ] = new FS_License_Manager( $slug, $fs ); // } // // return self::$_instances[ $slug ]; // } // //// private function __construct($slug) { //// parent::__construct($slug); //// } // // function entry_id() { // return 'licenses'; // } // // function sync( $id ) { // // } // // /** // * @author Vova Feldman (@svovaf) // * @since 1.0.5 // * @uses FS_Api // * // * @param number|bool $plugin_id // * // * @return FS_Plugin_License[]|stdClass Licenses or API error. // */ // function api_get_user_plugin_licenses( $plugin_id = false ) { // $api = $this->_fs->get_api_user_scope(); // // if ( ! is_numeric( $plugin_id ) ) { // $plugin_id = $this->_fs->get_id(); // } // // $result = $api->call( "/plugins/{$plugin_id}/licenses.json" ); // // if ( ! isset( $result->error ) ) { // for ( $i = 0, $len = count( $result->licenses ); $i < $len; $i ++ ) { // $result->licenses[ $i ] = new FS_Plugin_License( $result->licenses[ $i ] ); // } // // $result = $result->licenses; // } // // return $result; // } // // function api_get_many() { // // } // // function api_activate( $id ) { // // } // // function api_deactivate( $id ) { // // } /** * @param FS_Plugin_License[] $licenses * * @return bool */ static function has_premium_license( $licenses ) { if ( is_array( $licenses ) ) { foreach ( $licenses as $license ) { /** * @var FS_Plugin_License $license */ if ( ! $license->is_utilized() && $license->is_features_enabled() ) { return true; } } } return false; } }PK!'$$5freemius/includes/managers/class-fs-cache-manager.phpnu[_logger = FS_Logger::get_logger( WP_FS__SLUG . '_cach_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_logger->entrance(); $this->_logger->log( 'id = ' . $id ); $this->_options = FS_Option_Manager::get_manager( $id, true, true, false ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param $id * * @return FS_Cache_Manager */ static function get_manager( $id ) { $id = strtolower( $id ); if ( ! isset( self::$_MANAGERS[ $id ] ) ) { self::$_MANAGERS[ $id ] = new FS_Cache_Manager( $id ); } return self::$_MANAGERS[ $id ]; } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @return bool */ function is_empty() { $this->_logger->entrance(); return $this->_options->is_empty(); } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 */ function clear() { $this->_logger->entrance(); $this->_options->clear( true ); } /** * Delete cache manager from DB. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ function delete() { $this->_options->delete(); } /** * Check if there's a cached item. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key * * @return bool */ function has( $key ) { $cache_entry = $this->_options->get_option( $key, false ); return ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) ); } /** * Check if there's a valid cached item. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key * @param null|int $expiration Since 1.2.2.7 * * @return bool */ function has_valid( $key, $expiration = null ) { $cache_entry = $this->_options->get_option( $key, false ); $is_valid = ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) && $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME ); if ( $is_valid && is_numeric( $expiration ) && isset( $cache_entry->created ) && is_numeric( $cache_entry->created ) && $cache_entry->created + $expiration < WP_FS__SCRIPT_START_TIME ) { /** * Even if the cache is still valid, since we are checking for validity * with an explicit expiration period, if the period has past, return * `false` as if the cache is invalid. * * @since 1.2.2.7 */ $is_valid = false; } return $is_valid; } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key * @param mixed $default * * @return mixed */ function get( $key, $default = null ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = $this->_options->get_option( $key, false ); if ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) ) { return $cache_entry->result; } return is_object( $default ) ? clone $default : $default; } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key * @param mixed $default * * @return mixed */ function get_valid( $key, $default = null ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = $this->_options->get_option( $key, false ); if ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) && $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME ) { return $cache_entry->result; } return is_object( $default ) ? clone $default : $default; } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key * @param mixed $value * @param int $expiration * @param int $created Since 2.0.0 Cache creation date. */ function set( $key, $value, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $created = WP_FS__SCRIPT_START_TIME ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = new stdClass(); $cache_entry->result = $value; $cache_entry->created = $created; $cache_entry->timestamp = $created + $expiration; $this->_options->set_option( $key, $cache_entry, true ); } /** * Get cached record expiration, or false if not cached or expired. * * @author Vova Feldman (@svovaf) * @since 1.1.7.3 * * @param string $key * * @return bool|int */ function get_record_expiration( $key ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = $this->_options->get_option( $key, false ); if ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) && $cache_entry->timestamp > WP_FS__SCRIPT_START_TIME ) { return $cache_entry->timestamp; } return false; } /** * Purge cached item. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param string $key */ function purge( $key ) { $this->_logger->entrance( 'key = ' . $key ); $this->_options->unset_option( $key, true ); } /** * Extend cached item caching period. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $key * @param int $expiration * * @return bool */ function update_expiration( $key, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = $this->_options->get_option( $key, false ); if ( ! is_object( $cache_entry ) || ! isset( $cache_entry->timestamp ) || ! is_numeric( $cache_entry->timestamp ) ) { return false; } $this->set( $key, $cache_entry->result, $expiration, $cache_entry->created ); return true; } /** * Set cached item as expired. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @param string $key */ function expire( $key ) { $this->_logger->entrance( 'key = ' . $key ); $cache_entry = $this->_options->get_option( $key, false ); if ( is_object( $cache_entry ) && isset( $cache_entry->timestamp ) && is_numeric( $cache_entry->timestamp ) ) { // Set to expired. $cache_entry->timestamp = WP_FS__SCRIPT_START_TIME; $this->_options->set_option( $key, $cache_entry, true ); } } #-------------------------------------------------------------------------------- #region Migration #-------------------------------------------------------------------------------- /** * Migrate options from site level. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function migrate_to_network() { $this->_options->migrate_to_network(); } #endregion }PK!>P@H@H<freemius/includes/managers/class-fs-admin-notice-manager.phpnu[ 0 ) { $key .= ":{$network_level_or_blog_id}"; } else { $network_level_or_blog_id = get_current_blog_id(); $key .= ":{$network_level_or_blog_id}"; } } if ( ! isset( self::$_instances[ $key ] ) ) { self::$_instances[ $key ] = new FS_Admin_Notice_Manager( $id, $title, $module_unique_affix, $is_network_and_blog_admins, $network_level_or_blog_id ); } return self::$_instances[ $key ]; } /** * @param string $id * @param string $title * @param string $module_unique_affix * @param bool $is_network_and_blog_admins Whether or not the message should be shown both on network and * blog admin pages. * @param bool|int $network_level_or_blog_id */ protected function __construct( $id, $title = '', $module_unique_affix = '', $is_network_and_blog_admins = false, $network_level_or_blog_id = false ) { $this->_id = $id; $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->_id . '_data', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_title = ! empty( $title ) ? $title : ''; $this->_module_unique_affix = $module_unique_affix; $this->_sticky_storage = FS_Key_Value_Storage::instance( 'admin_notices', $this->_id, $network_level_or_blog_id ); if ( is_multisite() ) { $this->_is_network_notices = ( true === $network_level_or_blog_id ); if ( is_numeric( $network_level_or_blog_id ) ) { $this->_blog_id = $network_level_or_blog_id; } } else { $this->_is_network_notices = false; } $is_network_admin = fs_is_network_admin(); $is_blog_admin = fs_is_blog_admin(); if ( ( $this->_is_network_notices && $is_network_admin ) || ( ! $this->_is_network_notices && $is_blog_admin ) || ( $is_network_and_blog_admins && ( $is_network_admin || $is_blog_admin ) ) ) { if ( 0 < count( $this->_sticky_storage ) ) { $ajax_action_suffix = str_replace( ':', '-', $this->_id ); // If there are sticky notices for the current slug, add a callback // to the AJAX action that handles message dismiss. add_action( "wp_ajax_fs_dismiss_notice_action_{$ajax_action_suffix}", array( &$this, 'dismiss_notice_ajax_callback' ) ); foreach ( $this->_sticky_storage as $msg ) { // Add admin notice. $this->add( $msg['message'], $msg['title'], $msg['type'], true, $msg['id'], false, isset( $msg['wp_user_id'] ) ? $msg['wp_user_id'] : null, ! empty( $msg['plugin'] ) ? $msg['plugin'] : null, $is_network_and_blog_admins, isset( $msg['dismissible'] ) ? $msg['dismissible'] : null ); } } } } /** * Remove sticky message by ID. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * */ function dismiss_notice_ajax_callback() { check_admin_referer( 'fs_dismiss_notice_action' ); if ( ! is_numeric( $_POST['message_id'] ) ) { $this->_sticky_storage->remove( $_POST['message_id'] ); } wp_die(); } /** * Rendered sticky message dismiss JavaScript. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ static function _add_sticky_dismiss_javascript() { $params = array(); fs_require_once_template( 'sticky-admin-notice-js.php', $params ); } private static $_added_sticky_javascript = false; /** * Hook to the admin_footer to add sticky message dismiss JavaScript handler. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ private static function has_sticky_messages() { if ( ! self::$_added_sticky_javascript ) { add_action( 'admin_footer', array( 'FS_Admin_Notice_Manager', '_add_sticky_dismiss_javascript' ) ); } } /** * Handle admin_notices by printing the admin messages stacked in the queue. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * */ function _admin_notices_hook() { if ( function_exists( 'current_user_can' ) && ! current_user_can( 'manage_options' ) ) { // Only show messages to admins. return; } foreach ( $this->_notices as $id => $msg ) { if ( isset( $msg['wp_user_id'] ) && is_numeric( $msg['wp_user_id'] ) ) { if ( get_current_user_id() != $msg['wp_user_id'] ) { continue; } } /** * Added a filter to control the visibility of admin notices. * * Usage example: * * /** * * @param bool $show * * @param array $msg { * * @var string $message The actual message. * * @var string $title An optional message title. * * @var string $type The type of the message ('success', 'update', 'warning', 'promotion'). * * @var string $id The unique identifier of the message. * * @var string $manager_id The unique identifier of the notices manager. For plugins it would be the plugin's slug, for themes - `-theme`. * * @var string $plugin The product's title. * * @var string $wp_user_id An optional WP user ID that this admin notice is for. * * } * * * * @return bool * *\/ * function my_custom_show_admin_notice( $show, $msg ) { * if ('trial_promotion' != $msg['id']) { * return false; * } * * return $show; * } * * my_fs()->add_filter( 'show_admin_notice', 'my_custom_show_admin_notice', 10, 2 ); * * @author Vova Feldman * @since 2.2.0 */ $show_notice = call_user_func_array( 'fs_apply_filter', array( $this->_module_unique_affix, 'show_admin_notice', $this->show_admin_notices(), $msg ) ); if ( true !== $show_notice ) { continue; } fs_require_template( 'admin-notice.php', $msg ); if ( $msg['sticky'] ) { self::has_sticky_messages(); } } } /** * Enqueue common stylesheet to style admin notice. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ function _enqueue_styles() { fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); } /** * Check if the current page is the Gutenberg block editor. * * @author Vova Feldman (@svovaf) * @since 2.2.3 * * @return bool */ function is_gutenberg_page() { if ( function_exists( 'is_gutenberg_page' ) && is_gutenberg_page() ) { // The Gutenberg plugin is on. return true; } $current_screen = get_current_screen(); if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) { // Gutenberg page on 5+. return true; } return false; } /** * Check if admin notices should be shown on page. E.g., we don't want to show notices in the Visual Editor. * * @author Xiaheng Chen (@xhchen) * @since 2.4.2 * * @return bool */ function show_admin_notices() { global $pagenow; if ( 'about.php' === $pagenow ) { // Don't show admin notices on the About page. return false; } if ( $this->is_gutenberg_page() ) { // Don't show admin notices in Gutenberg (visual editor). return false; } return true; } /** * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $message * @param string $title * @param string $type * @param bool $is_sticky * @param string $id Message ID * @param bool $store_if_sticky * @param number|null $wp_user_id * @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|null $is_dismissible * @param array $data * * @uses add_action() */ function add( $message, $title = '', $type = 'success', $is_sticky = false, $id = '', $store_if_sticky = true, $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false, $is_dismissible = null, $data = array() ) { $notices_type = $this->get_notices_type(); if ( empty( $this->_notices ) ) { if ( ! $is_network_and_blog_admins ) { add_action( $notices_type, array( &$this, "_admin_notices_hook" ) ); } else { add_action( 'network_admin_notices', array( &$this, "_admin_notices_hook" ) ); add_action( 'admin_notices', array( &$this, "_admin_notices_hook" ) ); } add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_styles' ) ); } if ( '' === $id ) { $id = md5( $title . ' ' . $message . ' ' . $type ); } $message_object = array( 'message' => $message, 'title' => $title, 'type' => $type, 'sticky' => $is_sticky, 'id' => $id, 'manager_id' => $this->_id, 'plugin' => ( ! is_null( $plugin_title ) ? $plugin_title : $this->_title ), 'wp_user_id' => $wp_user_id, 'dismissible' => $is_dismissible, 'data' => $data ); if ( $is_sticky && $store_if_sticky ) { $this->_sticky_storage->{$id} = $message_object; } $this->_notices[ $id ] = $message_object; } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string|string[] $ids * @param bool $store */ function remove_sticky( $ids, $store = true ) { if ( ! is_array( $ids ) ) { $ids = array( $ids ); } foreach ( $ids as $id ) { // Remove from sticky storage. $this->_sticky_storage->remove( $id, $store ); if ( isset( $this->_notices[ $id ] ) ) { unset( $this->_notices[ $id ] ); } } } /** * Check if sticky message exists by id. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param $id * * @return bool */ function has_sticky( $id ) { return isset( $this->_sticky_storage[ $id ] ); } /** * Adds sticky admin notification. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $message * @param string $id Message ID * @param string $title * @param string $type * @param number|null $wp_user_id * @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_dimissible * @param array $data */ function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false, $is_dimissible = true, $data = array() ) { if ( ! empty( $this->_module_unique_affix ) ) { $message = fs_apply_filter( $this->_module_unique_affix, "sticky_message_{$id}", $message ); $title = fs_apply_filter( $this->_module_unique_affix, "sticky_title_{$id}", $title ); } $this->add( $message, $title, $type, true, $id, true, $wp_user_id, $plugin_title, $is_network_and_blog_admins, $is_dimissible, $data ); } /** * Retrieves the data of an sticky notice. * * @author Leo Fajardo (@leorw) * @since 2.4.3 * * @param string $id Message ID. * * @return array|null */ function get_sticky( $id ) { return isset( $this->_sticky_storage->{$id} ) ? $this->_sticky_storage->{$id} : null; } /** * Clear all sticky messages. * * @author Vova Feldman (@svovaf) * @since 1.0.8 * * @param bool $is_temporary @since 2.5.1 */ function clear_all_sticky( $is_temporary = false ) { if ( $is_temporary ) { $this->_notices = array(); } else { $this->_sticky_storage->clear_all(); } } #-------------------------------------------------------------------------------- #region Helper Method #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ private function get_notices_type() { return $this->_is_network_notices ? 'network_admin_notices' : 'admin_notices'; } #endregion } PK!GM  4freemius/includes/managers/class-fs-plan-manager.phpnu[is_utilized() && $license->is_features_enabled() ) { return true; } } } return false; } /** * Check if plugin has any paid plans. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param FS_Plugin_Plan[] $plans * * @return bool */ function has_paid_plan( $plans ) { if ( ! is_array( $plans ) || 0 === count( $plans ) ) { return false; } /** * @var FS_Plugin_Plan[] $plans */ for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { if ( ! $plans[ $i ]->is_free() ) { return true; } } return false; } /** * Check if plugin has any free plan, or is it premium only. * * Note: If no plans configured, assume plugin is free. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param FS_Plugin_Plan[] $plans * * @return bool */ function has_free_plan( $plans ) { if ( ! is_array( $plans ) || 0 === count( $plans ) ) { return true; } /** * @var FS_Plugin_Plan[] $plans */ for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { if ( $plans[ $i ]->is_free() ) { return true; } } return false; } /** * Find all plans that have trial. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param FS_Plugin_Plan[] $plans * * @return FS_Plugin_Plan[] */ function get_trial_plans( $plans ) { $trial_plans = array(); if ( is_array( $plans ) && 0 < count( $plans ) ) { /** * @var FS_Plugin_Plan[] $plans */ for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { if ( $plans[ $i ]->has_trial() ) { $trial_plans[] = $plans[ $i ]; } } } return $trial_plans; } /** * Check if plugin has any trial plan. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param FS_Plugin_Plan[] $plans * * @return bool */ function has_trial_plan( $plans ) { if ( ! is_array( $plans ) || 0 === count( $plans ) ) { return true; } /** * @var FS_Plugin_Plan[] $plans */ for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) { if ( $plans[ $i ]->has_trial() ) { return true; } } return false; } }PK!+ 886freemius/includes/managers/class-fs-option-manager.phpnu[_logger = FS_Logger::get_logger( WP_FS__SLUG . '_opt_mngr_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_logger->entrance(); $this->_logger->log( 'id = ' . $id ); $this->_id = $id; $this->_autoload = $autoload; if ( is_multisite() ) { $this->_is_network_storage = ( true === $network_level_or_blog_id ); if ( is_numeric( $network_level_or_blog_id ) ) { $this->_blog_id = $network_level_or_blog_id; } } else { $this->_is_network_storage = false; } if ( $load ) { $this->load(); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param string $id * @param bool $load * @param bool|int $network_level_or_blog_id Since 2.0.0 * @param bool|null $autoload * * @return \FS_Option_Manager */ static function get_manager( $id, $load = false, $network_level_or_blog_id = false, $autoload = null ) { $key = strtolower( $id ); if ( is_multisite() ) { if ( true === $network_level_or_blog_id ) { $key .= ':ms'; } else if ( is_numeric( $network_level_or_blog_id ) && $network_level_or_blog_id > 0 ) { $key .= ":{$network_level_or_blog_id}"; } else { $network_level_or_blog_id = get_current_blog_id(); $key .= ":{$network_level_or_blog_id}"; } } if ( ! isset( self::$_MANAGERS[ $key ] ) ) { self::$_MANAGERS[ $key ] = new FS_Option_Manager( $id, $load, $network_level_or_blog_id, $autoload ); } // If load required but not yet loaded, load. else if ( $load && ! self::$_MANAGERS[ $key ]->is_loaded() ) { self::$_MANAGERS[ $key ]->load(); } return self::$_MANAGERS[ $key ]; } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param bool $flush */ function load( $flush = false ) { $this->_logger->entrance(); if ( ! $flush && isset( $this->_options ) ) { return; } if ( isset( $this->_options ) ) { // Clear prev options. $this->clear(); } $option_name = $this->get_option_manager_name(); if ( $this->_is_network_storage ) { $this->_options = get_site_option( $option_name ); } else if ( $this->_blog_id > 0 ) { $this->_options = get_blog_option( $this->_blog_id, $option_name ); } else { $this->_options = get_option( $option_name ); } if ( is_string( $this->_options ) ) { $this->_options = json_decode( $this->_options ); } // $this->_logger->info('get_option = ' . var_export($this->_options, true)); if ( false === $this->_options ) { $this->clear(); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @return bool */ function is_loaded() { return isset( $this->_options ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @return bool */ function is_empty() { return ( $this->is_loaded() && false === $this->_options ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param bool $flush */ function clear( $flush = false ) { $this->_logger->entrance(); $this->_options = array(); if ( $flush ) { $this->store(); } } /** * Delete options manager from DB. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ function delete() { $option_name = $this->get_option_manager_name(); if ( $this->_is_network_storage ) { delete_site_option( $option_name ); } else if ( $this->_blog_id > 0 ) { delete_blog_option( $this->_blog_id, $option_name ); } else { delete_option( $option_name ); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param string $option * @param bool $flush * * @return bool */ function has_option( $option, $flush = false ) { if ( ! $this->is_loaded() || $flush ) { $this->load( $flush ); } return array_key_exists( $option, $this->_options ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param string $option * @param mixed $default * @param bool $flush * * @return mixed */ function get_option( $option, $default = null, $flush = false ) { $this->_logger->entrance( 'option = ' . $option ); if ( ! $this->is_loaded() || $flush ) { $this->load( $flush ); } if ( is_array( $this->_options ) ) { $value = isset( $this->_options[ $option ] ) ? $this->_options[ $option ] : $default; } else if ( is_object( $this->_options ) ) { $value = isset( $this->_options->{$option} ) ? $this->_options->{$option} : $default; } else { $value = $default; } /** * If it's an object, return a clone of the object, otherwise, * external changes of the object will actually change the value * of the object in the option manager which may lead to an unexpected * behaviour and data integrity when a store() call is triggered. * * Example: * $object1 = $options->get_option( 'object1' ); * $object1->x = 123; * * $object2 = $options->get_option( 'object2' ); * $object2->y = 'dummy'; * * $options->set_option( 'object2', $object2, true ); * * If we don't return a clone of option 'object1', setting 'object2' * will also store the updated value of 'object1' which is quite not * an expected behaviour. * * @author Vova Feldman */ return is_object( $value ) ? clone $value : $value; } /** * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param string $option * @param mixed $value * @param bool $flush */ function set_option( $option, $value, $flush = false ) { $this->_logger->entrance( 'option = ' . $option ); if ( ! $this->is_loaded() ) { $this->clear(); } /** * If it's an object, store a clone of the object, otherwise, * external changes of the object will actually change the value * of the object in the options manager which may lead to an unexpected * behaviour and data integrity when a store() call is triggered. * * Example: * $object1 = new stdClass(); * $object1->x = 123; * * $options->set_option( 'object1', $object1 ); * * $object1->x = 456; * * $options->set_option( 'object2', $object2, true ); * * If we don't set the option as a clone of option 'object1', setting 'object2' * will also store the updated value of 'object1' ($object1->x = 456 instead of * $object1->x = 123) which is quite not an expected behaviour. * * @author Vova Feldman */ $copy = is_object( $value ) ? clone $value : $value; if ( is_array( $this->_options ) ) { $this->_options[ $option ] = $copy; } else if ( is_object( $this->_options ) ) { $this->_options->{$option} = $copy; } if ( $flush ) { $this->store(); } } /** * Unset option. * * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @param string $option * @param bool $flush */ function unset_option( $option, $flush = false ) { $this->_logger->entrance( 'option = ' . $option ); if ( is_array( $this->_options ) ) { if ( ! isset( $this->_options[ $option ] ) ) { return; } unset( $this->_options[ $option ] ); } else if ( is_object( $this->_options ) ) { if ( ! isset( $this->_options->{$option} ) ) { return; } unset( $this->_options->{$option} ); } if ( $flush ) { $this->store(); } } /** * Dump options to database. * * @author Vova Feldman (@svovaf) * @since 1.0.3 */ function store() { $this->_logger->entrance(); $option_name = $this->get_option_manager_name(); if ( $this->_logger->is_on() ) { $this->_logger->info( $option_name . ' = ' . var_export( $this->_options, true ) ); } // Update DB. if ( $this->_is_network_storage ) { update_site_option( $option_name, $this->_options ); } else if ( $this->_blog_id > 0 ) { update_blog_option( $this->_blog_id, $option_name, $this->_options ); } else { update_option( $option_name, $this->_options, $this->_autoload ); } } /** * Get options keys. * * @author Vova Feldman (@svovaf) * @since 1.0.3 * * @return string[] */ function get_options_keys() { if ( is_array( $this->_options ) ) { return array_keys( $this->_options ); } else if ( is_object( $this->_options ) ) { return array_keys( get_object_vars( $this->_options ) ); } return array(); } #-------------------------------------------------------------------------------- #region Migration #-------------------------------------------------------------------------------- /** * Migrate options from site level. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function migrate_to_network() { $site_options = FS_Option_Manager::get_manager($this->_id, true, false); $options = is_object( $site_options->_options ) ? get_object_vars( $site_options->_options ) : $site_options->_options; if ( ! empty( $options ) ) { foreach ( $options as $key => $val ) { $this->set_option( $key, $val, false ); } $this->store(); } } #endregion #-------------------------------------------------------------------------------- #region Helper Methods #-------------------------------------------------------------------------------- /** * @return string */ private function get_option_manager_name() { return $this->_id; } #endregion } PK!p2WW$freemius/includes/managers/index.phpnu[_storage = FS_Option_Manager::get_manager( WP_FS__GDPR_OPTION_NAME, true, true ); $this->_wp_user_id = Freemius::get_current_wp_user_id(); $this->_option_name = "u{$this->_wp_user_id}"; $this->_data = $this->_storage->get_option( $this->_option_name, array() ); $this->_notices = FS_Admin_Notices::instance( 'all_admins', '', '', true ); if ( ! is_array( $this->_data ) ) { $this->_data = array(); } } /** * Update a GDPR option for the current admin and store it. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @param string $name * @param mixed $value */ private function update_option( $name, $value ) { $this->_data[ $name ] = $value; $this->_storage->set_option( $this->_option_name, $this->_data, true ); } /** * @author Leo Fajardo (@leorw) * @since 2.1.0 * * @param bool $is_required */ public function store_is_required( $is_required ) { $this->update_option( 'required', $is_required ); } /** * Checks if the GDPR opt-in sticky notice is currently shown. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @return bool */ public function is_opt_in_notice_shown() { return $this->_notices->has_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); } /** * Remove the GDPR opt-in sticky notice. * * @author Vova Feldman (@svovaf) * @since 2.1.0 */ public function remove_opt_in_notice() { $this->_notices->remove_sticky( "gdpr_optin_actions_{$this->_wp_user_id}", true ); $this->disable_opt_in_notice(); } /** * Prevents the opt-in message from being added/shown. * * @author Leo Fajardo (@leorw) * @since 2.1.0 */ public function disable_opt_in_notice() { $this->update_option( 'show_opt_in_notice', false ); } /** * Checks if a GDPR opt-in message needs to be shown to the current admin. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @return bool */ public function should_show_opt_in_notice() { return ( ! isset( $this->_data['show_opt_in_notice'] ) || true === $this->_data['show_opt_in_notice'] ); } /** * Get the last time the GDPR opt-in notice was shown. * * @author Vova Feldman (@svovaf) * @since 2.1.0 * * @return false|int */ public function last_time_notice_was_shown() { return isset( $this->_data['notice_shown_at'] ) ? $this->_data['notice_shown_at'] : false; } /** * Update the timestamp of the last time the GDPR opt-in message was shown to now. * * @author Vova Feldman (@svovaf) * @since 2.1.0 */ public function notice_was_just_shown() { $this->update_option( 'notice_shown_at', WP_FS__SCRIPT_START_TIME ); } /** * @param string $message * @param string|null $plugin_title * * @author Vova Feldman (@svovaf) * @since 2.1.0 */ public function add_opt_in_sticky_notice( $message, $plugin_title = null ) { $this->_notices->add_sticky( $message, "gdpr_optin_actions_{$this->_wp_user_id}", '', 'promotion', true, $this->_wp_user_id, $plugin_title, true ); } }PK!&6freemius/includes/managers/class-fs-plugin-manager.phpnu[_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_' . 'plugins', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_module_id = $module_id; $this->load(); } protected function get_option_manager() { return FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true, true ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @param string|bool $module_type "plugin", "theme", or "false" for all modules. * * @return array */ protected function get_all_modules( $module_type = false ) { $option_manager = $this->get_option_manager(); if ( false !== $module_type ) { return fs_get_entities( $option_manager->get_option( $module_type . 's', array() ), FS_Plugin::get_class_name() ); } return array( self::OPTION_NAME_PLUGINS => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_PLUGINS, array() ), FS_Plugin::get_class_name() ), self::OPTION_NAME_THEMES => fs_get_entities( $option_manager->get_option( self::OPTION_NAME_THEMES, array() ), FS_Plugin::get_class_name() ), ); } /** * Load plugin data from local DB. * * @author Vova Feldman (@svovaf) * @since 1.0.6 */ function load() { $all_modules = $this->get_all_modules(); if ( ! is_numeric( $this->_module_id ) ) { unset( $all_modules[ self::OPTION_NAME_THEMES ] ); } foreach ( $all_modules as $modules ) { /** * @since 1.2.2 * * @var $modules FS_Plugin[] */ foreach ( $modules as $module ) { $found_module = false; /** * If module ID is not numeric, it must be a plugin's slug. * * @author Leo Fajardo (@leorw) * @since 1.2.2 */ if ( ! is_numeric( $this->_module_id ) ) { if ( $this->_module_id === $module->slug ) { $this->_module_id = $module->id; $found_module = true; } } else if ( $this->_module_id == $module->id ) { $found_module = true; } if ( $found_module ) { $this->_module = $module; break; } } } } /** * Store plugin on local DB. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param bool|FS_Plugin $module * @param bool $flush * * @return bool|\FS_Plugin */ function store( $module = false, $flush = true ) { if ( false !== $module ) { $this->_module = $module; } $all_modules = $this->get_all_modules( $this->_module->type ); $all_modules[ $this->_module->slug ] = $this->_module; $options_manager = $this->get_option_manager(); $options_manager->set_option( $this->_module->type . 's', $all_modules, $flush ); return $this->_module; } /** * Update local plugin data if different. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param \FS_Plugin $plugin * @param bool $store * * @return bool True if plugin was updated. */ function update( FS_Plugin $plugin, $store = true ) { if ( ! ($this->_module instanceof FS_Plugin ) || $this->_module->slug != $plugin->slug || $this->_module->public_key != $plugin->public_key || $this->_module->secret_key != $plugin->secret_key || $this->_module->parent_plugin_id != $plugin->parent_plugin_id || $this->_module->title != $plugin->title ) { $this->store( $plugin, $store ); return true; } return false; } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param FS_Plugin $plugin * @param bool $store */ function set( FS_Plugin $plugin, $store = false ) { $this->_module = $plugin; if ( $store ) { $this->store(); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @return bool|\FS_Plugin */ function get() { if ( isset( $this->_module ) ) { return $this->_module; } if ( empty( $this->_module_id ) ) { return false; } /** * Return an FS_Plugin entity that has its `id` and `is_live` properties set (`is_live` is initialized in the FS_Plugin constructor) to avoid triggering an error that is relevant to these properties when the FS_Plugin entity is used before the `parse_settings()` method is called. This can happen when creating a regular WordPress site by cloning a subsite of a multisite network and the data that is stored in the network-level storage is not cloned. * * @author Leo Fajardo (@leorw) * @since 2.5.0 */ $plugin = new FS_Plugin(); $plugin->id = $this->_module_id; return $plugin; } }PK!aa:freemius/includes/managers/class-fs-permission-manager.phpnu[ */ private static $_instances = array(); const PERMISSION_USER = 'user'; const PERMISSION_SITE = 'site'; const PERMISSION_EVENTS = 'events'; const PERMISSION_ESSENTIALS = 'essentials'; const PERMISSION_DIAGNOSTIC = 'diagnostic'; const PERMISSION_EXTENSIONS = 'extensions'; const PERMISSION_NEWSLETTER = 'newsletter'; /** * @param Freemius $fs * * @return self */ static function instance( Freemius $fs ) { $id = $fs->get_id(); if ( ! isset( self::$_instances[ $id ] ) ) { self::$_instances[ $id ] = new self( $fs ); } return self::$_instances[ $id ]; } /** * @param Freemius $fs */ protected function __construct( Freemius $fs ) { $this->_fs = $fs; $this->_storage = FS_Storage::instance( $fs->get_module_type(), $fs->get_slug() ); } /** * @return string[] */ static function get_all_permission_ids() { return array( self::PERMISSION_USER, self::PERMISSION_SITE, self::PERMISSION_EVENTS, self::PERMISSION_ESSENTIALS, self::PERMISSION_DIAGNOSTIC, self::PERMISSION_EXTENSIONS, self::PERMISSION_NEWSLETTER, ); } /** * @return string[] */ static function get_api_managed_permission_ids() { return array( self::PERMISSION_USER, self::PERMISSION_SITE, self::PERMISSION_EXTENSIONS, ); } /** * @param string $permission * * @return bool */ static function is_supported_permission( $permission ) { return in_array( $permission, self::get_all_permission_ids() ); } /** * @since 2.5.3 * * @return bool */ function is_premium_context() { return ( $this->_fs->is_premium() || $this->_fs->can_use_premium_code() ); } /** * @param bool $is_license_activation * @param array[] $extra_permissions * * @return array[] */ function get_permissions( $is_license_activation, array $extra_permissions = array() ) { return $is_license_activation ? $this->get_license_activation_permissions( $extra_permissions ) : $this->get_opt_in_permissions( $extra_permissions ); } #-------------------------------------------------------------------------------- #region Opt-In Permissions #-------------------------------------------------------------------------------- /** * @param array[] $extra_permissions * * @return array[] */ function get_opt_in_permissions( array $extra_permissions = array(), $load_default_from_storage = false, $is_optional = false ) { $permissions = array_merge( $this->get_opt_in_required_permissions( $load_default_from_storage ), $this->get_opt_in_optional_permissions( $load_default_from_storage, $is_optional ), $extra_permissions ); return $this->get_sorted_permissions_by_priority( $permissions ); } /** * @param bool $load_default_from_storage * * @return array[] */ function get_opt_in_required_permissions( $load_default_from_storage = false ) { return array( $this->get_user_permission( $load_default_from_storage ) ); } /** * @param bool $load_default_from_storage * @param bool $is_optional * * @return array[] */ function get_opt_in_optional_permissions( $load_default_from_storage = false, $is_optional = false ) { return array_merge( $this->get_opt_in_diagnostic_permissions( $load_default_from_storage, $is_optional ), array( $this->get_extensions_permission( false, false, $load_default_from_storage ) ) ); } /** * @param bool $load_default_from_storage * @param bool $is_optional * * @return array[] */ function get_opt_in_diagnostic_permissions( $load_default_from_storage = false, $is_optional = false ) { // Alias. $fs = $this->_fs; $permissions = array(); $permissions[] = $this->get_permission( self::PERMISSION_SITE, 'admin-links', $fs->get_text_inline( 'View Basic Website Info', 'permissions-site' ), $fs->get_text_inline( 'Homepage URL & title, WP & PHP versions, and site language', 'permissions-site_desc' ), sprintf( /* translators: %s: 'Plugin' or 'Theme' */ $fs->get_text_inline( 'To provide additional functionality that\'s relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.', 'permissions-site_tooltip' ), $fs->get_module_label( true ) ), 10, $is_optional, true, $load_default_from_storage ); $permissions[] = $this->get_permission( self::PERMISSION_EVENTS, 'admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), sprintf( $fs->get_text_inline( 'View Basic %s Info', 'permissions-events' ), $fs->get_module_label() ), sprintf( /* translators: %s: 'Plugin' or 'Theme' */ $fs->get_text_inline( 'Current %s & SDK versions, and if active or uninstalled', 'permissions-events_desc' ), $fs->get_module_label( true ) ), '', 20, $is_optional, true, $load_default_from_storage ); return $permissions; } #endregion #-------------------------------------------------------------------------------- #region License Activation Permissions #-------------------------------------------------------------------------------- /** * @param array[] $extra_permissions * * @return array[] */ function get_license_activation_permissions( array $extra_permissions = array(), $include_optional_label = true ) { $permissions = array_merge( $this->get_license_required_permissions(), $this->get_license_optional_permissions( $include_optional_label ), $extra_permissions ); return $this->get_sorted_permissions_by_priority( $permissions ); } /** * @param bool $load_default_from_storage * * @return array[] */ function get_license_required_permissions( $load_default_from_storage = false ) { // Alias. $fs = $this->_fs; $permissions = array(); $permissions[] = $this->get_permission( self::PERMISSION_ESSENTIALS, 'admin-links', $fs->get_text_inline( 'View License Essentials', 'permissions-essentials' ), $fs->get_text_inline( sprintf( /* translators: %s: 'Plugin' or 'Theme' */ 'Homepage URL, %s version, SDK version', $fs->get_module_label() ), 'permissions-essentials_desc' ), sprintf( /* translators: %s: 'Plugin' or 'Theme' */ $fs->get_text_inline( 'To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.', 'permissions-essentials_tooltip' ), $fs->get_module_label( true ) ), 10, false, true, $load_default_from_storage ); $permissions[] = $this->get_permission( self::PERMISSION_EVENTS, 'admin-' . ( $fs->is_plugin() ? 'plugins' : 'appearance' ), sprintf( $fs->get_text_inline( 'View %s State', 'permissions-events' ), $fs->get_module_label() ), sprintf( /* translators: %s: 'Plugin' or 'Theme' */ $fs->get_text_inline( 'Is active, deactivated, or uninstalled', 'permissions-events_desc-paid' ), $fs->get_module_label( true ) ), sprintf( $fs->get_text_inline( 'So you can reuse the license when the %s is no longer active.', 'permissions-events_tooltip' ), $fs->get_module_label( true ) ), 20, false, true, $load_default_from_storage ); return $permissions; } /** * @return array[] */ function get_license_optional_permissions( $include_optional_label = false, $load_default_from_storage = false ) { return array( $this->get_diagnostic_permission( $include_optional_label, $load_default_from_storage ), $this->get_extensions_permission( true, $include_optional_label, $load_default_from_storage ), ); } /** * @param bool $include_optional_label * @param bool $load_default_from_storage * * @return array */ function get_diagnostic_permission( $include_optional_label = false, $load_default_from_storage = false ) { return $this->get_permission( self::PERMISSION_DIAGNOSTIC, 'wordpress-alt', $this->_fs->get_text_inline( 'View Diagnostic Info', 'permissions-diagnostic' ) . ( $include_optional_label ? ' (' . $this->_fs->get_text_inline( 'optional' ) . ')' : '' ), $this->_fs->get_text_inline( 'WordPress & PHP versions, site language & title', 'permissions-diagnostic_desc' ), sprintf( /* translators: %s: 'Plugin' or 'Theme' */ $this->_fs->get_text_inline( 'To avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.', 'permissions-diagnostic_tooltip' ), $this->_fs->get_module_label( true ) ), 25, true, true, $load_default_from_storage ); } #endregion #-------------------------------------------------------------------------------- #region Common Permissions #-------------------------------------------------------------------------------- /** * @param bool $is_license_activation * @param bool $include_optional_label * @param bool $load_default_from_storage * * @return array */ function get_extensions_permission( $is_license_activation, $include_optional_label = false, $load_default_from_storage = false ) { $is_on_by_default = ! $is_license_activation; return $this->get_permission( self::PERMISSION_EXTENSIONS, 'block-default', $this->_fs->get_text_inline( 'View Plugins & Themes List', 'permissions-extensions' ) . ( $is_license_activation ? ( $include_optional_label ? ' (' . $this->_fs->get_text_inline( 'optional' ) . ')' : '' ) : '' ), $this->_fs->get_text_inline( 'Names, slugs, versions, and if active or not', 'permissions-extensions_desc' ), $this->_fs->get_text_inline( 'To ensure compatibility and avoid conflicts with your installed plugins and themes.', 'permissions-events_tooltip' ), 25, true, $is_on_by_default, $load_default_from_storage ); } /** * @param bool $load_default_from_storage * * @return array */ function get_user_permission( $load_default_from_storage = false ) { return $this->get_permission( self::PERMISSION_USER, 'admin-users', $this->_fs->get_text_inline( 'View Basic Profile Info', 'permissions-profile' ), $this->_fs->get_text_inline( 'Your WordPress user\'s: first & last name, and email address', 'permissions-profile_desc' ), $this->_fs->get_text_inline( 'Never miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.', 'permissions-profile_tooltip' ), 5, false, true, $load_default_from_storage ); } #endregion #-------------------------------------------------------------------------------- #region Optional Permissions #-------------------------------------------------------------------------------- /** * @return array[] */ function get_newsletter_permission() { return $this->get_permission( self::PERMISSION_NEWSLETTER, 'email-alt', $this->_fs->get_text_inline( 'Newsletter', 'permissions-newsletter' ), $this->_fs->get_text_inline( 'Updates, announcements, marketing, no spam', 'permissions-newsletter_desc' ), '', 15 ); } #endregion #-------------------------------------------------------------------------------- #region Permissions Storage #-------------------------------------------------------------------------------- /** * @param int|null $blog_id * * @return bool */ function is_extensions_tracking_allowed( $blog_id = null ) { return $this->is_permission_allowed( self::PERMISSION_EXTENSIONS, ! $this->_fs->is_premium(), $blog_id ); } /** * @param int|null $blog_id * * @return bool */ function is_essentials_tracking_allowed( $blog_id = null ) { return $this->is_permission_allowed( self::PERMISSION_ESSENTIALS, true, $blog_id ); } /** * @param bool $default * * @return bool */ function is_diagnostic_tracking_allowed( $default = true ) { return $this->is_premium_context() ? $this->is_permission_allowed( self::PERMISSION_DIAGNOSTIC, $default ) : $this->is_permission_allowed( self::PERMISSION_SITE, $default ); } /** * @param int|null $blog_id * * @return bool */ function is_homepage_url_tracking_allowed( $blog_id = null ) { return $this->is_permission_allowed( $this->get_site_permission_name(), true, $blog_id ); } /** * @param int|null $blog_id * * @return bool */ function update_site_tracking( $is_enabled, $blog_id = null, $only_if_not_set = false ) { $permissions = $this->get_site_tracking_permission_names(); $result = true; foreach ( $permissions as $permission ) { if ( ! $only_if_not_set || ! $this->is_permission_set( $permission, $blog_id ) ) { $result = ( $result && $this->update_permission_tracking_flag( $permission, $is_enabled, $blog_id ) ); } } return $result; } /** * @param string $permission * @param bool $default * @param int|null $blog_id * * @return bool */ function is_permission_allowed( $permission, $default = false, $blog_id = null ) { if ( ! self::is_supported_permission( $permission ) ) { return $default; } return $this->is_permission( $permission, true, $blog_id ); } /** * @param string $permission * @param bool $is_allowed * @param int|null $blog_id * * @return bool */ function is_permission( $permission, $is_allowed, $blog_id = null ) { if ( ! self::is_supported_permission( $permission ) ) { return false; } $tag = "is_{$permission}_tracking_allowed"; return ( $is_allowed === $this->_fs->apply_filters( $tag, $this->_storage->get( $tag, $this->get_permission_default( $permission ), $blog_id, FS_Storage::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED ) ) ); } /** * @param string $permission * @param int|null $blog_id * * @return bool */ function is_permission_set( $permission, $blog_id = null ) { $tag = "is_{$permission}_tracking_allowed"; $permission = $this->_storage->get( $tag, null, $blog_id, FS_Storage::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED ); return is_bool( $permission ); } /** * @param string[] $permissions * @param bool $is_allowed * * @return bool `true` if all given permissions are in sync with `$is_allowed`. */ function are_permissions( $permissions, $is_allowed, $blog_id = null ) { foreach ( $permissions as $permission ) { if ( ! $this->is_permission( $permission, $is_allowed, $blog_id ) ) { return false; } } return true; } /** * @param string $permission * @param bool $is_enabled * @param int|null $blog_id * * @return bool `false` if permission not supported or `$is_enabled` is not a boolean. */ function update_permission_tracking_flag( $permission, $is_enabled, $blog_id = null ) { if ( is_bool( $is_enabled ) && self::is_supported_permission( $permission ) ) { $this->_storage->store( "is_{$permission}_tracking_allowed", $is_enabled, $blog_id, FS_Storage::OPTION_LEVEL_NETWORK_ACTIVATED_NOT_DELEGATED ); return true; } return false; } /** * @param array $permissions */ function update_permissions_tracking_flag( $permissions ) { foreach ( $permissions as $permission => $is_enabled ) { $this->update_permission_tracking_flag( $permission, $is_enabled ); } } #endregion /** * @param string $permission * * @return bool */ function get_permission_default( $permission ) { if ( $this->_fs->is_premium() && self::PERMISSION_EXTENSIONS === $permission ) { return false; } // All permissions except for the extensions in paid version are on by default when the user opts in to usage tracking. return true; } /** * @return string */ function get_site_permission_name() { return $this->is_premium_context() ? self::PERMISSION_ESSENTIALS : self::PERMISSION_SITE; } /** * @return string[] */ function get_site_tracking_permission_names() { return $this->is_premium_context() ? array( FS_Permission_Manager::PERMISSION_ESSENTIALS, FS_Permission_Manager::PERMISSION_EVENTS, ) : array( FS_Permission_Manager::PERMISSION_SITE ); } #-------------------------------------------------------------------------------- #region Rendering #-------------------------------------------------------------------------------- /** * @param array $permission */ function render_permission( array $permission ) { fs_require_template( 'connect/permission.php', $permission ); } /** * @param array $permissions_group */ function render_permissions_group( array $permissions_group ) { $permissions_group[ 'fs' ] = $this->_fs; fs_require_template( 'connect/permissions-group.php', $permissions_group ); } function require_permissions_js() { fs_require_once_template( 'js/permissions.php', $params ); } #endregion #-------------------------------------------------------------------------------- #region Helper Methods #-------------------------------------------------------------------------------- /** * @param string $id * @param string $dashicon * @param string $label * @param string $desc * @param string $tooltip * @param int $priority * @param bool $is_optional * @param bool $is_on_by_default * @param bool $load_from_storage * * @return array */ private function get_permission( $id, $dashicon, $label, $desc, $tooltip = '', $priority = 10, $is_optional = false, $is_on_by_default = true, $load_from_storage = false ) { $is_on = $load_from_storage ? $this->is_permission_allowed( $id, $is_on_by_default ) : $is_on_by_default; return array( 'id' => $id, 'icon-class' => $this->_fs->apply_filters( "permission_{$id}_icon", "dashicons dashicons-{$dashicon}" ), 'label' => $this->_fs->apply_filters( "permission_{$id}_label", $label ), 'tooltip' => $this->_fs->apply_filters( "permission_{$id}_tooltip", $tooltip ), 'desc' => $this->_fs->apply_filters( "permission_{$id}_desc", $desc ), 'priority' => $this->_fs->apply_filters( "permission_{$id}_priority", $priority ), 'optional' => $is_optional, 'default' => $this->_fs->apply_filters( "permission_{$id}_default", $is_on ), ); } /** * @param array $permissions * * @return array[] */ private function get_sorted_permissions_by_priority( array $permissions ) { // Allow filtering of the permissions list. $permissions = $this->_fs->apply_filters( 'permission_list', $permissions ); // Sort by priority. uasort( $permissions, 'fs_sort_by_priority' ); return $permissions; } #endregion }PK!~5freemius/includes/managers/class-fs-clone-manager.phpnu[_storage = FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true ); $this->_network_storage = FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true, true ); $this->maybe_migrate_options(); $this->_notices = FS_Admin_Notices::instance( 'global_clone_resolution_notices', '', '', true ); $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . '_clone_manager', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); } /** * Migrate clone resolution options from 2.5.0 array-based structure, to a new flat structure. * * The reason this logic is not in a separate migration script is that we want to be 100% sure data is migrated before any execution of clone logic. * * @todo Delete this one in the future. */ private function maybe_migrate_options() { $storages = array( $this->_storage, $this->_network_storage ); foreach ( $storages as $storage ) { $clone_data = $storage->get_option( self::OPTION_NAME ); if ( is_array( $clone_data ) && ! empty( $clone_data ) ) { foreach ( $clone_data as $key => $val ) { if ( ! is_null( $val ) ) { $storage->set_option( $key, $val ); } } $storage->unset_option( self::OPTION_NAME, true ); } } } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function _init() { if ( is_admin() ) { if ( Freemius::is_admin_post() ) { add_action( 'admin_post_fs_clone_resolution', array( $this, '_handle_clone_resolution' ) ); } if ( Freemius::is_ajax() ) { Freemius::add_ajax_action_static( 'handle_clone_resolution', array( $this, '_clone_resolution_action_ajax_handler' ) ); } else { if ( empty( $this->get_clone_identification_timestamp() ) && ( ! fs_is_network_admin() || ! ( $this->is_clone_resolution_options_notice_shown() || $this->is_temporary_duplicate_notice_shown() ) ) ) { $this->hide_clone_admin_notices(); } else if ( ! Freemius::is_cron() && ! Freemius::is_admin_post() ) { $this->try_resolve_clone_automatically(); $this->maybe_show_clone_admin_notice(); add_action( 'admin_footer', array( $this, '_add_clone_resolution_javascript' ) ); } } } } /** * Retrieves the timestamp that was stored when a clone was identified. * * @return int|null */ function get_clone_identification_timestamp() { return $this->get_option( 'clone_identification_timestamp', true ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.1 * * @param string $sdk_last_version */ function maybe_update_clone_resolution_support_flag( $sdk_last_version ) { if ( null !== $this->hide_manual_resolution ) { return; } $this->hide_manual_resolution = ( ! empty( $sdk_last_version ) && version_compare( $sdk_last_version, '2.5.0', '<' ) ); } /** * Stores the time when a clone was identified. */ function store_clone_identification_timestamp() { $this->clone_identification_timestamp = time(); } /** * Retrieves the timestamp for the temporary duplicate mode's expiration. * * @return int */ function get_temporary_duplicate_expiration_timestamp() { $temporary_duplicate_mode_start_timestamp = $this->was_temporary_duplicate_mode_selected() ? $this->temporary_duplicate_mode_selection_timestamp : $this->get_clone_identification_timestamp(); return ( $temporary_duplicate_mode_start_timestamp + self::TEMPORARY_DUPLICATE_PERIOD ); } /** * Determines if the SDK should handle clones. The SDK handles clones only up to 3 times with 3 min interval. * * @return bool */ private function should_handle_clones() { if ( ! isset( $this->request_handler_timestamp ) ) { return true; } if ( $this->request_handler_retries_count >= self::CLONE_RESOLUTION_MAX_RETRIES ) { return false; } // Give the logic that handles clones enough time to finish (it is given 3 minutes for now). return ( time() > ( $this->request_handler_timestamp + self::CLONE_RESOLUTION_MAX_EXECUTION_TIME ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.1 * * @return bool */ function should_hide_manual_resolution() { return ( true === $this->hide_manual_resolution ); } /** * Executes the clones handler logic if it should be executed, i.e., based on the return value of the should_handle_clones() method. * * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function maybe_run_clone_resolution() { if ( ! $this->should_handle_clones() ) { return; } $this->request_handler_retries_count = isset( $this->request_handler_retries_count ) ? ( $this->request_handler_retries_count + 1 ) : 1; $this->request_handler_timestamp = time(); $handler_id = ( rand() . microtime() ); $this->request_handler_id = $handler_id; // Add cookies to trigger request with the same user access permissions. $cookies = array(); foreach ( $_COOKIE as $name => $value ) { $cookies[] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value, ) ); } wp_remote_post( admin_url( 'admin-post.php' ), array( 'method' => 'POST', 'body' => array( 'action' => 'fs_clone_resolution', 'handler_id' => $handler_id, ), 'timeout' => 0.01, 'blocking' => false, 'sslverify' => false, 'cookies' => $cookies, ) ); } /** * Executes the clones handler logic. * * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function _handle_clone_resolution() { $handler_id = fs_request_get( 'handler_id' ); if ( empty( $handler_id ) ) { return; } if ( ! isset( $this->request_handler_id ) || $this->request_handler_id !== $handler_id ) { return; } if ( ! $this->try_automatic_resolution() ) { $this->clear_temporary_duplicate_notice_shown_timestamp(); } } #-------------------------------------------------------------------------------- #region Automatic Clone Resolution #-------------------------------------------------------------------------------- /** * @var array All installs cache. */ private $all_installs; /** * Checks if a given instance's install is a clone of another subsite in the network. * * @author Vova Feldman (@svovaf) * * @return FS_Site */ private function find_network_subsite_clone_install( Freemius $instance ) { if ( ! is_multisite() ) { // Not a multi-site network. return null; } if ( ! isset( $this->all_installs ) ) { $this->all_installs = Freemius::get_all_modules_sites(); } // Check if there's another blog that has the same site. $module_type = $instance->get_module_type(); $sites_by_module_type = ! empty( $this->all_installs[ $module_type ] ) ? $this->all_installs[ $module_type ] : array(); $slug = $instance->get_slug(); $sites_by_slug = ! empty( $sites_by_module_type[ $slug ] ) ? $sites_by_module_type[ $slug ] : array(); $current_blog_id = get_current_blog_id(); $current_install = $instance->get_site(); foreach ( $sites_by_slug as $site ) { if ( $current_install->id == $site->id && $current_blog_id != $site->blog_id ) { // Clone is identical to an install on another subsite in the network. return $site; } } return null; } /** * Tries to find a different install of the context product that is associated with the current URL and loads it. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param Freemius $instance * @param string $url * * @return object */ private function find_other_install_by_url( Freemius $instance, $url ) { $result = $instance->get_api_user_scope()->get( "/plugins/{$instance->get_id()}/installs.json?url=" . urlencode( $url ) . "&all=true", true ); $current_install = $instance->get_site(); if ( $instance->is_api_result_object( $result, 'installs' ) ) { foreach ( $result->installs as $install ) { if ( $install->id == $current_install->id ) { continue; } if ( $instance->is_only_premium() && ! FS_Plugin_License::is_valid_id( $install->license_id ) ) { continue; } // When searching for installs by a URL, the API will first strip any paths and search for any matching installs by the subdomain. Therefore, we need to test if there's a match between the current URL and the install's URL before continuing. if ( $url !== fs_strip_url_protocol( untrailingslashit( $install->url ) ) ) { continue; } // Found a different install that is associated with the current URL, load it and replace the current install with it if no updated install is found. return $install; } } return null; } /** * Delete the current install associated with a given instance and opt-in/activate-license to create a fresh install. * * @author Vova Feldman (@svovaf) * @since 2.5.0 * * @param Freemius $instance * @param string|false $license_key * * @return bool TRUE if successfully connected. FALSE if failed and had to restore install from backup. */ private function delete_install_and_connect( Freemius $instance, $license_key = false ) { $user = Freemius::_get_user_by_id( $instance->get_site()->user_id ); $instance->delete_current_install( true ); if ( ! is_object( $user ) ) { // Get logged-in WordPress user. $current_user = Freemius::_get_current_wp_user(); // Find the relevant FS user by email address. $user = Freemius::_get_user_by_email( $current_user->user_email ); } if ( is_object( $user ) ) { // When a clone is found, we prefer to use the same user of the original install for the opt-in. $instance->install_with_user( $user, $license_key, false, false ); } else { // If no user is found, activate with the license. $instance->opt_in( false, false, false, $license_key ); } if ( is_object( $instance->get_site() ) ) { // Install successfully created. return true; } // Restore from backup. $instance->restore_backup_site(); return false; } /** * Try to resolve the clone situation automatically. * * @param Freemius $instance * @param string $current_url * @param bool $is_localhost * @param bool|null $is_clone_of_network_subsite * * @return bool If managed to automatically resolve the clone. */ private function try_resolve_clone_automatically_by_instance( Freemius $instance, $current_url, $is_localhost, $is_clone_of_network_subsite = null ) { // Try to find a different install of the context product that is associated with the current URL. $associated_install = $this->find_other_install_by_url( $instance, $current_url ); if ( is_object( $associated_install ) ) { // Replace the current install with a different install that is associated with the current URL. $instance->store_site( new FS_Site( clone $associated_install ) ); $instance->sync_install( array( 'is_new_site' => true ), true ); return true; } if ( ! $instance->is_premium() ) { // For free products, opt-in with the context user to create new install. return $this->delete_install_and_connect( $instance ); } $license = $instance->_get_license(); $can_activate_license = ( is_object( $license ) && ! $license->is_utilized( $is_localhost ) ); if ( ! $can_activate_license ) { // License can't be activated, therefore, can't be automatically resolved. return false; } if ( ! WP_FS__IS_LOCALHOST_FOR_SERVER && ! $is_localhost ) { $is_clone_of_network_subsite = ( ! is_null( $is_clone_of_network_subsite ) ) ? $is_clone_of_network_subsite : is_object( $this->find_network_subsite_clone_install( $instance ) ); if ( ! $is_clone_of_network_subsite ) { return false; } } // If the site is a clone of another subsite in the network, or a localhost one, try to auto activate the license. return $this->delete_install_and_connect( $instance, $license->secret_key ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ private function try_resolve_clone_automatically() { $clone_action = $this->get_clone_resolution_action_from_config(); if ( ! empty( $clone_action ) ) { $this->try_resolve_clone_automatically_by_config( $clone_action ); return; } $this->try_automatic_resolution(); } /** * Tries to resolve the clone situation automatically based on the config in the wp-config.php file. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param string $clone_action */ private function try_resolve_clone_automatically_by_config( $clone_action ) { $fs_instances = array(); if ( self::OPTION_LONG_TERM_DUPLICATE === $clone_action ) { $instances = Freemius::_get_all_instances(); foreach ( $instances as $instance ) { if ( ! $instance->is_registered() ) { continue; } if ( ! $instance->is_clone() ) { continue; } $license = $instance->has_features_enabled_license() ? $instance->_get_license() : null; if ( is_object( $license ) && ! $license->is_utilized( ( WP_FS__IS_LOCALHOST_FOR_SERVER || FS_Site::is_localhost_by_address( Freemius::get_unfiltered_site_url() ) ) ) ) { $fs_instances[] = $instance; } } if ( empty( $fs_instances ) ) { return; } } $this->resolve_cloned_sites( $clone_action, $fs_instances ); } /** * @author Leo Fajard (@leorw) * @since 2.5.0 * * @return string|null */ private function get_clone_resolution_action_from_config() { if ( ! defined( 'FS__RESOLVE_CLONE_AS' ) ) { return null; } if ( ! in_array( FS__RESOLVE_CLONE_AS, array( self::OPTION_NEW_HOME, self::OPTION_TEMPORARY_DUPLICATE, self::OPTION_LONG_TERM_DUPLICATE, ) ) ) { return null; } return FS__RESOLVE_CLONE_AS; } /** * Tries to recover the install of a newly created subsite or resolve it if it's a clone. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param Freemius $instance */ function maybe_resolve_new_subsite_install_automatically( Freemius $instance ) { if ( ! $instance->is_user_in_admin() ) { // Try to recover an install or resolve a clone only when there's a user in admin to prevent doing it prematurely (e.g., the install can get replaced with clone data again). return; } if ( ! is_multisite() ) { return; } $new_blog_install_map = $this->new_blog_install_map; if ( empty( $new_blog_install_map ) || ! is_array( $new_blog_install_map ) ) { return; } $is_network_admin = fs_is_network_admin(); if ( ! $is_network_admin ) { // If not in network admin, handle the current site. $blog_id = get_current_blog_id(); } else { // If in network admin, handle only the first site. $blog_ids = array_keys( $new_blog_install_map ); $blog_id = $blog_ids[0]; } if ( ! isset( $new_blog_install_map[ $blog_id ] ) ) { // There's no site to handle. return; } $expected_install_id = $new_blog_install_map[ $blog_id ]['install_id']; $current_install = $instance->get_install_by_blog_id( $blog_id ); $current_install_id = is_object( $current_install ) ? $current_install->id : null; if ( $expected_install_id == $current_install_id ) { // Remove the current site's information from the map to prevent handling it again. $this->remove_new_blog_install_info_from_storage( $blog_id ); return; } require_once WP_FS__DIR_INCLUDES . '/class-fs-lock.php'; $lock = new FS_Lock( self::OPTION_NAME . '_subsite' ); if ( ! $lock->try_lock(60) ) { return; } $instance->switch_to_blog( $blog_id ); $current_url = untrailingslashit( Freemius::get_unfiltered_site_url( null, true ) ); $current_install_url = is_object( $current_install ) ? fs_strip_url_protocol( untrailingslashit( $current_install->url ) ) : null; // This can be `false` even if the install is a clone as the URL can be updated as part of the cloning process. $is_clone = ( ! is_null( $current_install_url ) && $current_url !== $current_install_url ); if ( ! FS_Site::is_valid_id( $expected_install_id ) ) { $expected_install = null; } else { $expected_install = $instance->fetch_install_by_id( $expected_install_id ); } if ( FS_Api::is_api_result_entity( $expected_install ) ) { // Replace the current install with the expected install. $instance->store_site( new FS_Site( clone $expected_install ) ); $instance->sync_install( array( 'is_new_site' => true ), true ); } else { $network_subsite_clone_install = null; if ( ! $is_clone ) { // It is possible that `$is_clone` is `false` but the install is actually a clone as the following call checks the install ID and not the URL. $network_subsite_clone_install = $this->find_network_subsite_clone_install( $instance ); } if ( $is_clone || is_object( $network_subsite_clone_install ) ) { // If there's no expected install (or it couldn't be fetched) and the current install is a clone, try to resolve the clone automatically. $is_localhost = FS_Site::is_localhost_by_address( $current_url ); $resolved = $this->try_resolve_clone_automatically_by_instance( $instance, $current_url, $is_localhost, is_object( $network_subsite_clone_install ) ); if ( ! $resolved && is_object( $network_subsite_clone_install ) ) { if ( empty( $this->get_clone_identification_timestamp() ) ) { $this->store_clone_identification_timestamp(); } // Since the clone couldn't be identified based on the URL, replace the stored install with the cloned install so that the manual clone resolution notice will appear. $instance->store_site( clone $network_subsite_clone_install ); } } } $instance->restore_current_blog(); // Remove the current site's information from the map to prevent handling it again. $this->remove_new_blog_install_info_from_storage( $blog_id ); $lock->unlock(); } /** * If a new install was created after creating a new subsite, its ID is stored in the blog-install map so that it can be recovered in case it's replaced with a clone install (e.g., when the newly created subsite is a clone). The IDs of the clone subsites that were created while not running this version of the SDK or a higher version will also be stored in the said map so that the clone manager can also try to resolve them later on. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param int $blog_id * @param FS_Site $site */ function store_blog_install_info( $blog_id, $site = null ) { $new_blog_install_map = $this->new_blog_install_map; if ( empty( $new_blog_install_map ) || ! is_array( $new_blog_install_map ) ) { $new_blog_install_map = array(); } $install_id = null; if ( is_object( $site ) ) { $install_id = $site->id; } $new_blog_install_map[ $blog_id ] = array( 'install_id' => $install_id ); $this->new_blog_install_map = $new_blog_install_map; } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param int $blog_id */ private function remove_new_blog_install_info_from_storage( $blog_id ) { $new_blog_install_map = $this->new_blog_install_map; unset( $new_blog_install_map[ $blog_id ] ); $this->new_blog_install_map = $new_blog_install_map; } /** * Tries to resolve all clones automatically. * * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @return bool If managed to automatically resolve all clones. */ private function try_automatic_resolution() { $this->_logger->entrance(); require_once WP_FS__DIR_INCLUDES . '/class-fs-lock.php'; $lock = new FS_Lock( self::OPTION_NAME ); /** * Try to acquire lock for the next 60 sec based on the thread ID. */ if ( ! $lock->try_lock( 60 ) ) { return false; } $current_url = untrailingslashit( Freemius::get_unfiltered_site_url( null, true ) ); $is_localhost = FS_Site::is_localhost_by_address( $current_url ); $require_manual_resolution = false; $instances = Freemius::_get_all_instances(); foreach ( $instances as $instance ) { if ( ! $instance->is_registered() ) { continue; } if ( ! $instance->is_clone() ) { continue; } if ( ! $this->try_resolve_clone_automatically_by_instance( $instance, $current_url, $is_localhost ) ) { $require_manual_resolution = true; } } // Create a 1-day lock. $lock->lock( WP_FS__TIME_24_HOURS_IN_SEC ); return ( ! $require_manual_resolution ); } #endregion #-------------------------------------------------------------------------------- #region Manual Clone Resolution #-------------------------------------------------------------------------------- /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function _add_clone_resolution_javascript() { $vars = array( 'ajax_action' => Freemius::get_ajax_action_static( 'handle_clone_resolution' ) ); fs_require_once_template( 'clone-resolution-js.php', $vars ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function _clone_resolution_action_ajax_handler() { $this->_logger->entrance(); check_ajax_referer( Freemius::get_ajax_action_static( 'handle_clone_resolution' ), 'security' ); $clone_action = fs_request_get( 'clone_action' ); $blog_id = is_multisite() ? fs_request_get( 'blog_id' ) : 0; if ( is_multisite() && $blog_id == get_current_blog_id() ) { $blog_id = 0; } if ( empty( $clone_action ) ) { Freemius::shoot_ajax_failure( array( 'message' => fs_text_inline( 'Invalid clone resolution action.', 'invalid-clone-resolution-action-error' ), 'redirect_url' => '', ) ); } $result = $this->resolve_cloned_sites( $clone_action, array(), $blog_id ); Freemius::shoot_ajax_success( $result ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @param string $clone_action * @param Freemius[] $fs_instances * @param int $blog_id * * @return array */ private function resolve_cloned_sites( $clone_action, $fs_instances = array(), $blog_id = 0 ) { $this->_logger->entrance(); $result = array(); $instances_with_clone = array(); $instances_with_clone_count = 0; $install_by_instance_id = array(); $instances = ( ! empty( $fs_instances ) ) ? $fs_instances : Freemius::_get_all_instances(); $should_switch_to_blog = ( $blog_id > 0 ); foreach ( $instances as $instance ) { if ( $should_switch_to_blog ) { $instance->switch_to_blog( $blog_id ); } if ( $instance->is_registered() && $instance->is_clone() ) { $instances_with_clone[] = $instance; $instances_with_clone_count ++; $install_by_instance_id[ $instance->get_id() ] = $instance->get_site(); } } if ( self::OPTION_TEMPORARY_DUPLICATE === $clone_action ) { $this->store_temporary_duplicate_timestamp(); } else { $redirect_url = ''; foreach ( $instances_with_clone as $instance ) { if ( $should_switch_to_blog ) { $instance->switch_to_blog( $blog_id ); } $has_error = false; if ( self::OPTION_NEW_HOME === $clone_action ) { $instance->sync_install( array( 'is_new_site' => true ), true ); if ( $instance->is_clone() ) { $has_error = true; } } else { $instance->_handle_long_term_duplicate(); if ( ! is_object( $instance->get_site() ) ) { $has_error = true; } } if ( $has_error && 1 === $instances_with_clone_count ) { $redirect_url = $instance->get_activation_url(); } } $result = ( array( 'redirect_url' => $redirect_url ) ); } foreach ( $instances_with_clone as $instance ) { if ( $should_switch_to_blog ) { $instance->switch_to_blog( $blog_id ); } // No longer a clone, send an update. if ( ! $instance->is_clone() ) { $instance->send_clone_resolution_update( $clone_action, $install_by_instance_id[ $instance->get_id() ] ); } } if ( 'temporary_duplicate_license_activation' !== $clone_action ) { $this->remove_clone_resolution_options_notice(); } else { $this->remove_temporary_duplicate_notice(); } if ( $should_switch_to_blog ) { foreach ( $instances as $instance ) { $instance->restore_current_blog(); } } return $result; } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ private function hide_clone_admin_notices() { $this->remove_clone_resolution_options_notice( false ); $this->remove_temporary_duplicate_notice( false ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.0 */ function maybe_show_clone_admin_notice() { $this->_logger->entrance(); if ( fs_is_network_admin() ) { $existing_notice_ids = $this->maybe_remove_notices(); if ( ! empty( $existing_notice_ids ) ) { fs_enqueue_local_style( 'fs_clone_resolution_notice', '/admin/clone-resolution.css' ); } return; } $first_instance_with_clone = null; $site_urls = array(); $sites_with_license_urls = array(); $sites_with_premium_version_count = 0; $product_ids = array(); $product_titles = array(); $instances = Freemius::_get_all_instances(); foreach ( $instances as $instance ) { if ( ! $instance->is_registered() ) { continue; } if ( ! $instance->is_clone( true ) ) { continue; } $install = $instance->get_site(); $site_urls[] = $install->url; $product_ids[] = $instance->get_id(); $product_titles[] = $instance->get_plugin_title(); if ( is_null( $first_instance_with_clone ) ) { $first_instance_with_clone = $instance; } if ( is_object( $instance->_get_license() ) ) { $sites_with_license_urls[] = $install->url; } if ( $instance->is_premium() ) { $sites_with_premium_version_count ++; } } if ( empty( $site_urls ) && empty( $sites_with_license_urls ) ) { $this->hide_clone_admin_notices(); return; } $site_urls = array_unique( $site_urls ); $sites_with_license_urls = array_unique( $sites_with_license_urls ); $module_label = fs_text_inline( 'products', 'products' ); $admin_notice_module_title = null; $has_temporary_duplicate_mode_expired = $this->has_temporary_duplicate_mode_expired(); if ( ! $this->was_temporary_duplicate_mode_selected() || $has_temporary_duplicate_mode_expired ) { if ( ! empty( $site_urls ) ) { fs_enqueue_local_style( 'fs_clone_resolution_notice', '/admin/clone-resolution.css' ); $doc_url = 'https://freemius.com/help/documentation/wordpress-sdk/safe-mode-clone-resolution-duplicate-website/'; if ( 1 === count( $instances ) ) { $doc_url = fs_apply_filter( $first_instance_with_clone->get_unique_affix(), 'clone_resolution_documentation_url', $doc_url ); } $this->add_manual_clone_resolution_admin_notice( $product_ids, $product_titles, $site_urls, Freemius::get_unfiltered_site_url(), ( count( $site_urls ) === count( $sites_with_license_urls ) ), ( count( $site_urls ) === $sites_with_premium_version_count ), $doc_url ); } return; } if ( empty( $sites_with_license_urls ) ) { return; } if ( ! $this->is_temporary_duplicate_notice_shown() ) { $last_time_temporary_duplicate_notice_shown = $this->temporary_duplicate_notice_shown_timestamp; $was_temporary_duplicate_notice_shown_before = is_numeric( $last_time_temporary_duplicate_notice_shown ); if ( $was_temporary_duplicate_notice_shown_before ) { $temporary_duplicate_mode_expiration_timestamp = $this->get_temporary_duplicate_expiration_timestamp(); $current_time = time(); if ( $current_time > $temporary_duplicate_mode_expiration_timestamp || $current_time < ( $temporary_duplicate_mode_expiration_timestamp - ( 2 * WP_FS__TIME_24_HOURS_IN_SEC ) ) ) { // Do not show the notice if the temporary duplicate mode has already expired or it will expire more than 2 days from now. return; } } } if ( 1 === count( $sites_with_license_urls ) ) { $module_label = $first_instance_with_clone->get_module_label( true ); $admin_notice_module_title = $first_instance_with_clone->get_plugin_title(); } fs_enqueue_local_style( 'fs_clone_resolution_notice', '/admin/clone-resolution.css' ); $this->add_temporary_duplicate_sticky_notice( $product_ids, $this->get_temporary_duplicate_admin_notice_string( $sites_with_license_urls, $product_titles, $module_label ), $admin_notice_module_title ); } /** * Removes the notices from the storage if the context product is either no longer active on the context subsite or it's active but there's no longer any clone. This prevents the notices from being shown on the network-level admin page when they are no longer relevant. * * @author Leo Fajardo (@leorw) * @since 2.5.1 * * @return string[] */ private function maybe_remove_notices() { $notices = array( 'clone_resolution_options_notice' => $this->_notices->get_sticky( 'clone_resolution_options_notice', true ), 'temporary_duplicate_notice' => $this->_notices->get_sticky( 'temporary_duplicate_notice', true ), ); $instances = Freemius::_get_all_instances(); foreach ( $notices as $id => $notice ) { if ( ! is_array( $notice ) ) { unset( $notices[ $id ] ); continue; } if ( empty( $notice['data'] ) || ! is_array( $notice['data'] ) ) { continue; } if ( empty( $notice['data']['product_ids'] ) || empty( $notice['data']['blog_id'] ) ) { continue; } $product_ids = $notice['data']['product_ids']; $blog_id = $notice['data']['blog_id']; $has_clone = false; if ( ! is_null( get_site( $blog_id ) ) ) { foreach ( $product_ids as $product_id ) { if ( ! isset( $instances[ 'm_' . $product_id ] ) ) { continue; } $instance = $instances[ 'm_' . $product_id ]; $plugin_basename = $instance->get_plugin_basename(); $is_plugin_active = is_plugin_active_for_network( $plugin_basename ); if ( ! $is_plugin_active ) { switch_to_blog( $blog_id ); $is_plugin_active = is_plugin_active( $plugin_basename ); restore_current_blog(); } if ( ! $is_plugin_active ) { continue; } $install = $instance->get_install_by_blog_id( $blog_id ); if ( ! is_object( $install ) ) { continue; } $subsite_url = Freemius::get_unfiltered_site_url( $blog_id, true, true ); $has_clone = ( fs_strip_url_protocol( trailingslashit( $install->url ) ) !== $subsite_url ); } } if ( ! $has_clone ) { $this->_notices->remove_sticky( $id, true, false ); unset( $notices[ $id ] ); } } return array_keys( $notices ); } /** * Adds a notice that provides the logged-in WordPress user with manual clone resolution options. * * @param number[] $product_ids * @param string[] $site_urls * @param string $current_url * @param bool $has_license * @param bool $is_premium * @param string $doc_url */ private function add_manual_clone_resolution_admin_notice( $product_ids, $product_titles, $site_urls, $current_url, $has_license, $is_premium, $doc_url ) { $this->_logger->entrance(); $total_sites = count( $site_urls ); $sites_list = ''; $total_products = count( $product_titles ); $products_list = ''; if ( 1 === $total_products ) { $notice_header = sprintf( '

    %s

    ', fs_esc_html_inline( '%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.', 'single-cloned-site-safe-mode-message' ) ); } else { $notice_header = sprintf( '

    %s

    ', ( 1 === $total_sites ) ? fs_esc_html_inline( 'The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$s', 'multiple-products-cloned-site-safe-mode-message' ) : fs_esc_html_inline( 'The products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$s', 'multiple-products-multiple-cloned-sites-safe-mode-message' ) ); foreach ( $product_titles as $product_title ) { $products_list .= sprintf( '
  • %s
  • ', $product_title ); } $products_list = '
      ' . $products_list . '
    '; foreach ( $site_urls as $site_url ) { $sites_list .= sprintf( '
  • %s
  • ', $site_url, fs_strip_url_protocol( $site_url ) ); } $sites_list = '
      ' . $sites_list . '
    '; } $remote_site_link = '' . (1 === $total_sites ? sprintf( '%s', $site_urls[0], fs_strip_url_protocol( $site_urls[0] ) ) : fs_text_inline( 'the above-mentioned sites', 'above-mentioned-sites' )) . ''; $current_site_link = sprintf( '%s', $current_url, fs_strip_url_protocol( $current_url ) ); $button_template = ''; $option_template = '
    %s

    %s

    %s
    '; $duplicate_option = sprintf( $option_template, fs_esc_html_inline( 'Is %2$s a duplicate of %4$s?', 'duplicate-site-confirmation-message' ), fs_esc_html_inline( 'Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.', 'duplicate-site-message' ), ( $this->has_temporary_duplicate_mode_expired() ? sprintf( $button_template, 'long_term_duplicate', fs_text_inline( 'Long-Term Duplicate', 'long-term-duplicate' ) ) : sprintf( $button_template, 'temporary_duplicate', fs_text_inline( 'Duplicate Website', 'duplicate-site' ) ) ) ); $migration_option = sprintf( $option_template, fs_esc_html_inline( 'Is %2$s the new home of %4$s?', 'migrate-site-confirmation-message' ), sprintf( fs_esc_html_inline( 'Yes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.', 'migrate-site-message' ), ( $has_license ? fs_text_inline( 'license', 'license' ) : fs_text_inline( 'data', 'data' ) ) ), sprintf( $button_template, 'new_home', $has_license ? fs_text_inline( 'Migrate License', 'migrate-product-license' ) : fs_text_inline( 'Migrate', 'migrate-product-data' ) ) ); $new_website = sprintf( $option_template, fs_esc_html_inline( 'Is %2$s a new website?', 'new-site-confirmation-message' ), fs_esc_html_inline( 'Yes, %2$s is a new and different website that is separate from %4$s.', 'new-site-message' ) . ($is_premium ? ' ' . fs_text_inline( 'It requires license activation.', 'new-site-requires-license-activation-message' ) : '' ), sprintf( $button_template, 'new_website', ( ! $is_premium || ! $has_license ) ? fs_text_inline( 'New Website', 'new-website' ) : fs_text_inline( 'Activate License', 'activate-license' ) ) ); $blog_id = get_current_blog_id(); /** * %1$s - single product's title or product titles list. * %2$s - site's URL. * %3$s - single install's URL or install URLs list. * %4$s - Clone site's link or "the above-mentioned sites" if there are multiple clone sites. */ $message = sprintf( $notice_header . '
    ' . $duplicate_option . $migration_option . $new_website . '
    ' . sprintf( '
    Unsure what to do? Read more here.
    ', $doc_url ), // %1$s ( 1 === $total_products ? sprintf( '%s', $product_titles[0] ) : ( 1 === $total_sites ? sprintf( '
    %s
    ', $products_list ) : sprintf( '

    %s:

    %s
    ', fs_esc_html_x_inline( 'Products', 'Clone resolution admin notice products list label', 'products' ), $products_list ) ) ), // %2$s $current_site_link, // %3$s ( 1 === $total_sites ? $remote_site_link : $sites_list ), // %4$s $remote_site_link ); $this->_notices->add_sticky( $message, 'clone_resolution_options_notice', '', 'warn', true, null, null, true, // Intentionally not dismissible. false, array( 'product_ids' => $product_ids, 'blog_id' => $blog_id ) ); } #endregion #-------------------------------------------------------------------------------- #region Temporary Duplicate (Short Term) #-------------------------------------------------------------------------------- /** * @author Leo Fajardo (@leorw) * @since 2.5.0 * * @return string */ private function get_temporary_duplicate_admin_notice_string( $site_urls, $product_titles, $module_label ) { $this->_logger->entrance(); $temporary_duplicate_end_date = $this->get_temporary_duplicate_expiration_timestamp(); $temporary_duplicate_end_date = date( 'M j, Y', $temporary_duplicate_end_date ); $current_url = Freemius::get_unfiltered_site_url(); $current_site_link = sprintf( '%s', $current_url, fs_strip_url_protocol( $current_url ) ); $total_sites = count( $site_urls ); $sites_list = ''; $total_products = count( $product_titles ); $products_list = ''; if ( $total_sites > 1 ) { foreach ( $site_urls as $site_url ) { $sites_list .= sprintf( '
  • %s
  • ', $site_url, fs_strip_url_protocol( $site_url ) ); } $sites_list = '
      ' . $sites_list . '
    '; } if ( $total_products > 1 ) { foreach ( $product_titles as $product_title ) { $products_list .= sprintf( '
  • %s
  • ', $product_title ); } $products_list = '
      ' . $products_list . '
    '; } return sprintf( sprintf( '
    %s
    ', ( 1 === $total_sites ? sprintf( '

    %s

    ', fs_esc_html_inline( 'You marked this website, %s, as a temporary duplicate of %s.', 'temporary-duplicate-message' ) ) : sprintf( '

    %s:

    ', fs_esc_html_inline( 'You marked this website, %s, as a temporary duplicate of these sites', 'temporary-duplicate-of-sites-message' ) ) . '%s' ) ) . '%s', $current_site_link, ( 1 === $total_sites ? sprintf( '%s', $site_urls[0], fs_strip_url_protocol( $site_urls[0] ) ) : $sites_list ), sprintf( '

    %s

    %s

    %s

    ', esc_attr( admin_url( 'admin-ajax.php?_fs_network_admin=false', 'relative' ) ), sprintf( fs_esc_html_inline( "%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).", 'duplicate-site-confirmation-message' ), ( 1 === $total_products ? sprintf( fs_esc_html_x_inline( "The %s's", '"The ", e.g.: "The plugin"', 'the-product-x'), "{$module_label}" ) : fs_esc_html_inline( "The following products'", 'the-following-products' ) ), sprintf( '%s', $temporary_duplicate_end_date ) ), ( 1 === $total_products ? '' : sprintf( '
    %s
    ', $products_list ) ), sprintf( fs_esc_html_inline( 'If this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.', 'duplicate-site-message' ), sprintf( '%s', $temporary_duplicate_end_date), sprintf( '%s', fs_esc_html_inline( 'activate a license here', 'activate-license-here' ) ) ) ) ); } /** * Determines if the temporary duplicate mode has already expired. * * @return bool */ function has_temporary_duplicate_mode_expired() { $temporary_duplicate_mode_start_timestamp = $this->was_temporary_duplicate_mode_selected() ? $this->get_option( 'temporary_duplicate_mode_selection_timestamp', true ) : $this->get_clone_identification_timestamp(); if ( ! is_numeric( $temporary_duplicate_mode_start_timestamp ) ) { return false; } return ( time() > ( $temporary_duplicate_mode_start_timestamp + self::TEMPORARY_DUPLICATE_PERIOD ) ); } /** * Determines if the logged-in WordPress user manually selected the temporary duplicate mode for the site. * * @return bool */ function was_temporary_duplicate_mode_selected() { return is_numeric( $this->temporary_duplicate_mode_selection_timestamp ); } /** * Stores the time when the logged-in WordPress user selected the temporary duplicate mode for the site. */ private function store_temporary_duplicate_timestamp() { $this->temporary_duplicate_mode_selection_timestamp = time(); } /** * Removes the notice that is shown when the logged-in WordPress user has selected the temporary duplicate mode for the site. * * @param bool $store */ function remove_clone_resolution_options_notice( $store = true ) { $this->_notices->remove_sticky( 'clone_resolution_options_notice', true, $store ); } /** * Removes the notice that is shown when the logged-in WordPress user has selected the temporary duplicate mode for the site. * * @param bool $store */ function remove_temporary_duplicate_notice( $store = true ) { $this->_notices->remove_sticky( 'temporary_duplicate_notice', true, $store ); } /** * Determines if the manual clone resolution options notice is currently being shown. * * @return bool */ function is_clone_resolution_options_notice_shown() { return $this->_notices->has_sticky( 'clone_resolution_options_notice', true ); } /** * Determines if the temporary duplicate notice is currently being shown. * * @return bool */ function is_temporary_duplicate_notice_shown() { return $this->_notices->has_sticky( 'temporary_duplicate_notice', true ); } /** * Determines if a site was marked as a temporary duplicate and if it's still a temporary duplicate. * * @return bool */ function is_temporary_duplicate_by_blog_id( $blog_id ) { $timestamp = $this->get_option( 'temporary_duplicate_mode_selection_timestamp', false, $blog_id ); return ( is_numeric( $timestamp ) && time() < ( $timestamp + self::TEMPORARY_DUPLICATE_PERIOD ) ); } /** * Determines the last time the temporary duplicate notice was shown. * * @return int|null */ function last_time_temporary_duplicate_notice_was_shown() { return $this->temporary_duplicate_notice_shown_timestamp; } /** * Clears the time that has been stored when the temporary duplicate notice was shown. */ function clear_temporary_duplicate_notice_shown_timestamp() { unset( $this->temporary_duplicate_notice_shown_timestamp ); } /** * Adds a temporary duplicate notice that provides the logged-in WordPress user with an option to activate a license for the site. * * @param number[] $product_ids * @param string $message * @param string|null $plugin_title */ function add_temporary_duplicate_sticky_notice( $product_ids, $message, $plugin_title = null ) { $this->_logger->entrance(); $this->_notices->add_sticky( $message, 'temporary_duplicate_notice', '', 'promotion', true, null, $plugin_title, true, true, array( 'product_ids' => $product_ids, 'blog_id' => get_current_blog_id() ) ); $this->temporary_duplicate_notice_shown_timestamp = time(); } #endregion /** * @author Leo Fajardo * @since 2.5.0 * * @param string $key * * @return bool */ private function should_use_network_storage( $key ) { return ( 'new_blog_install_map' === $key ); } /** * @param string $key * @param number|null $blog_id * * @return FS_Option_Manager */ private function get_storage( $key, $blog_id = null ) { if ( is_numeric( $blog_id ) ){ return FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true, $blog_id ); } return $this->should_use_network_storage( $key ) ? $this->_network_storage : $this->_storage; } /** * @param string $name * @param bool $flush * @param number|null $blog_id * * @return mixed */ private function get_option( $name, $flush = false, $blog_id = null ) { return $this->get_storage( $name, $blog_id )->get_option( $name, null, $flush ); } #-------------------------------------------------------------------------------- #region Magic methods #-------------------------------------------------------------------------------- /** * @param string $name * @param int|string $value */ function __set( $name, $value ) { $this->get_storage( $name )->set_option( $name, $value, true ); } /** * @param string $name * * @return bool */ function __isset( $name ) { return $this->get_storage( $name )->has_option( $name, true ); } /** * @param string $name */ function __unset( $name ) { $this->get_storage( $name )->unset_option( $name, true ); } /** * @param string $name * * @return null|int|string */ function __get( $name ) { return $this->get_option( $name, // Reload storage from DB when accessing request_handler_* options to avoid race conditions. fs_starts_with( $name, 'request_handler' ) ); } #endregion } PK!9A$((9freemius/includes/managers/class-fs-key-value-storage.phpnu[ 0 ) { $key .= ":{$network_level_or_blog_id}"; } else { $network_level_or_blog_id = get_current_blog_id(); $key .= ":{$network_level_or_blog_id}"; } } if ( ! isset( self::$_instances[ $key ] ) ) { self::$_instances[ $key ] = new FS_Key_Value_Storage( $id, $secondary_id, $network_level_or_blog_id ); } return self::$_instances[ $key ]; } protected function __construct( $id, $secondary_id, $network_level_or_blog_id = false ) { $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $secondary_id . '_' . $id, WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_id = $id; $this->_secondary_id = $secondary_id; if ( is_multisite() ) { $this->_is_multisite_storage = ( true === $network_level_or_blog_id ); if ( is_numeric( $network_level_or_blog_id ) ) { $this->_blog_id = $network_level_or_blog_id; } } else { $this->_is_multisite_storage = false; } $this->load(); } protected function get_option_manager() { return FS_Option_Manager::get_manager( WP_FS__ACCOUNTS_OPTION_NAME, true, $this->_is_multisite_storage ? true : ( $this->_blog_id > 0 ? $this->_blog_id : false ) ); } protected function get_all_data() { return $this->get_option_manager()->get_option( $this->_id, array() ); } /** * Load plugin data from local DB. * * @author Vova Feldman (@svovaf) * @since 1.0.7 */ function load() { $all_plugins_data = $this->get_all_data(); $this->_data = isset( $all_plugins_data[ $this->_secondary_id ] ) ? $all_plugins_data[ $this->_secondary_id ] : array(); } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $key * @param mixed $value * @param bool $flush */ function store( $key, $value, $flush = true ) { if ( $this->_logger->is_on() ) { $this->_logger->entrance( $key . ' = ' . var_export( $value, true ) ); } if ( array_key_exists( $key, $this->_data ) && $value === $this->_data[ $key ] ) { // No need to store data if the value wasn't changed. return; } $all_data = $this->get_all_data(); $this->_data[ $key ] = $value; $all_data[ $this->_secondary_id ] = $this->_data; $options_manager = $this->get_option_manager(); $options_manager->set_option( $this->_id, $all_data, $flush ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 */ function save() { $this->get_option_manager()->store(); } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param bool $store * @param string[] $exceptions Set of keys to keep and not clear. */ function clear_all( $store = true, $exceptions = array() ) { $new_data = array(); foreach ( $exceptions as $key ) { if ( isset( $this->_data[ $key ] ) ) { $new_data[ $key ] = $this->_data[ $key ]; } } $this->_data = $new_data; if ( $store ) { $all_data = $this->get_all_data(); $all_data[ $this->_secondary_id ] = $this->_data; $options_manager = $this->get_option_manager(); $options_manager->set_option( $this->_id, $all_data, true ); } } /** * Delete key-value storage. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ function delete() { $this->_data = array(); $all_data = $this->get_all_data(); unset( $all_data[ $this->_secondary_id ] ); $options_manager = $this->get_option_manager(); $options_manager->set_option( $this->_id, $all_data, true ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $key * @param bool $store */ function remove( $key, $store = true ) { if ( ! array_key_exists( $key, $this->_data ) ) { return; } unset( $this->_data[ $key ] ); if ( $store ) { $all_data = $this->get_all_data(); $all_data[ $this->_secondary_id ] = $this->_data; $options_manager = $this->get_option_manager(); $options_manager->set_option( $this->_id, $all_data, true ); } } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $key * @param mixed $default * * @return bool|\FS_Plugin */ function get( $key, $default = false ) { return array_key_exists( $key, $this->_data ) ? $this->_data[ $key ] : $default; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return string */ function get_secondary_id() { return $this->_secondary_id; } /* ArrayAccess + Magic Access (better for refactoring) -----------------------------------------------------------------------------------*/ function __set( $k, $v ) { $this->store( $k, $v ); } function __isset( $k ) { return array_key_exists( $k, $this->_data ); } function __unset( $k ) { $this->remove( $k ); } function __get( $k ) { return $this->get( $k, null ); } #[ReturnTypeWillChange] function offsetSet( $k, $v ) { if ( is_null( $k ) ) { throw new Exception( 'Can\'t append value to request params.' ); } else { $this->{$k} = $v; } } #[ReturnTypeWillChange] function offsetExists( $k ) { return array_key_exists( $k, $this->_data ); } #[ReturnTypeWillChange] function offsetUnset( $k ) { unset( $this->$k ); } #[ReturnTypeWillChange] function offsetGet( $k ) { return $this->get( $k, null ); } /** * (PHP 5 >= 5.0.0)
    * Return the current element * * @link http://php.net/manual/en/iterator.current.php * @return mixed Can return any type. */ #[ReturnTypeWillChange] public function current() { return current( $this->_data ); } /** * (PHP 5 >= 5.0.0)
    * Move forward to next element * * @link http://php.net/manual/en/iterator.next.php * @return void Any returned value is ignored. */ #[ReturnTypeWillChange] public function next() { next( $this->_data ); } /** * (PHP 5 >= 5.0.0)
    * Return the key of the current element * * @link http://php.net/manual/en/iterator.key.php * @return mixed scalar on success, or null on failure. */ #[ReturnTypeWillChange] public function key() { return key( $this->_data ); } /** * (PHP 5 >= 5.0.0)
    * Checks if current position is valid * * @link http://php.net/manual/en/iterator.valid.php * @return boolean The return value will be casted to boolean and then evaluated. * Returns true on success or false on failure. */ #[ReturnTypeWillChange] public function valid() { $key = key( $this->_data ); return ( $key !== null && $key !== false ); } /** * (PHP 5 >= 5.0.0)
    * Rewind the Iterator to the first element * * @link http://php.net/manual/en/iterator.rewind.php * @return void Any returned value is ignored. */ #[ReturnTypeWillChange] public function rewind() { reset( $this->_data ); } /** * (PHP 5 >= 5.1.0)
    * Count elements of an object * * @link http://php.net/manual/en/countable.count.php * @return int The custom count as an integer. *

    *

    * The return value is cast to an integer. */ #[ReturnTypeWillChange] public function count() { return count( $this->_data ); } }PK!ff:freemius/includes/managers/class-fs-admin-menu-manager.phpnu[ */ private $_default_submenu_items; /** * @since 1.1.3 * * @var string */ private $_first_time_path; /** * @since 1.2.2 * * @var bool */ private $_menu_exists; /** * @since 2.0.0 * * @var bool */ private $_network_menu_exists; #endregion Properties /** * @var FS_Logger */ protected $_logger; #region Singleton /** * @var FS_Admin_Menu_Manager[] */ private static $_instances = array(); /** * @param number $module_id * @param string $module_type * @param string $module_unique_affix * * @return FS_Admin_Menu_Manager */ static function instance( $module_id, $module_type, $module_unique_affix ) { $key = 'm_' . $module_id; if ( ! isset( self::$_instances[ $key ] ) ) { self::$_instances[ $key ] = new FS_Admin_Menu_Manager( $module_id, $module_type, $module_unique_affix ); } return self::$_instances[ $key ]; } protected function __construct( $module_id, $module_type, $module_unique_affix ) { $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $module_id . '_admin_menu', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK ); $this->_module_id = $module_id; $this->_module_type = $module_type; $this->_module_unique_affix = $module_unique_affix; } #endregion Singleton #region Helpers private function get_option( &$options, $key, $default = false ) { return ! empty( $options[ $key ] ) ? $options[ $key ] : $default; } private function get_bool_option( &$options, $key, $default = false ) { return isset( $options[ $key ] ) && is_bool( $options[ $key ] ) ? $options[ $key ] : $default; } #endregion Helpers /** * @param array $menu * @param bool $is_addon */ function init( $menu, $is_addon = false ) { $this->_menu_exists = ( isset( $menu['slug'] ) && ! empty( $menu['slug'] ) ); $this->_network_menu_exists = ( ! empty( $menu['network'] ) && true === $menu['network'] ); $this->_menu_slug = ( $this->_menu_exists ? $menu['slug'] : $this->_module_unique_affix ); $this->_default_submenu_items = array(); // @deprecated $this->_type = 'page'; $this->_is_top_level = true; $this->_is_override_exact = false; $this->_parent_slug = false; // @deprecated $this->_parent_type = 'page'; if ( isset( $menu ) ) { if ( ! $is_addon ) { $this->_default_submenu_items = array( 'contact' => $this->get_bool_option( $menu, 'contact', true ), 'support' => $this->get_bool_option( $menu, 'support', true ), 'affiliation' => $this->get_bool_option( $menu, 'affiliation', true ), 'account' => $this->get_bool_option( $menu, 'account', true ), 'pricing' => $this->get_bool_option( $menu, 'pricing', true ), 'addons' => $this->get_bool_option( $menu, 'addons', true ), ); // @deprecated $this->_type = $this->get_option( $menu, 'type', 'page' ); } $this->_is_override_exact = $this->get_bool_option( $menu, 'override_exact' ); if ( isset( $menu['parent'] ) ) { $this->_parent_slug = $this->get_option( $menu['parent'], 'slug' ); // @deprecated $this->_parent_type = $this->get_option( $menu['parent'], 'type', 'page' ); // If parent's slug is different, then it's NOT a top level menu item. $this->_is_top_level = ( $this->_parent_slug === $this->_menu_slug ); } else { /** * If no parent then top level if: * - Has custom admin menu ('page') * - CPT menu type ('cpt') */ // $this->_is_top_level = in_array( $this->_type, array( // 'cpt', // 'page' // ) ); } $first_path = $this->get_option( $menu, 'first-path', false ); if ( ! empty( $first_path ) && is_string( $first_path ) ) { $this->_first_time_path = $first_path; } } } /** * Check if top level menu. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return bool False if submenu item. */ function is_top_level() { return $this->_is_top_level; } /** * Check if the page should be override on exact URL match. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return bool False if submenu item. */ function is_override_exact() { return $this->_is_override_exact; } /** * Get the path of the page the user should be forwarded to after first activation. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param bool $is_network Since 2.4.5 * * @return string */ function get_first_time_path( $is_network = false ) { if ( empty ( $this->_first_time_path ) ) { return $this->_first_time_path; } if ( $is_network ) { return network_admin_url( $this->_first_time_path ); } else { return admin_url( $this->_first_time_path ); } } /** * Check if plugin's menu item is part of a custom top level menu. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return bool */ function has_custom_parent() { return ! $this->_is_top_level && is_string( $this->_parent_slug ); } /** * @author Leo Fajardo (@leorw) * @since 1.2.2 * * @return bool */ function has_menu() { return $this->_menu_exists; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @return bool */ function has_network_menu() { return $this->_network_menu_exists; } /** * @author Leo Fajardo (@leorw) * * @param string $menu_slug * * @since 2.1.3 */ function set_slug_and_network_menu_exists_flag($menu_slug ) { $this->_menu_slug = $menu_slug; $this->_network_menu_exists = false; } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string $id * @param bool $default * @param bool $ignore_menu_existence Since 1.2.2.7 If true, check if the submenu item visible even if there's no parent menu. * * @return bool */ function is_submenu_item_visible( $id, $default = true, $ignore_menu_existence = false ) { if ( ! $ignore_menu_existence && ! $this->has_menu() ) { return false; } return fs_apply_filter( $this->_module_unique_affix, 'is_submenu_visible', $this->get_bool_option( $this->_default_submenu_items, $id, $default ), $id ); } /** * Calculates admin settings menu slug. * If plugin's menu slug is a file (e.g. CPT), uses plugin's slug as the menu slug. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @param string $page * * @return string */ function get_slug( $page = '' ) { return ( ( false === strpos( $this->_menu_slug, '.php?' ) ) ? $this->_menu_slug : $this->_module_unique_affix ) . ( empty( $page ) ? '' : ( '-' . $page ) ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_parent_slug() { return $this->_parent_slug; } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_type() { return $this->_type; } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return bool */ function is_cpt() { return ( 0 === strpos( $this->_menu_slug, 'edit.php?post_type=' ) || // Back compatibility. 'cpt' === $this->_type ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_parent_type() { return $this->_parent_type; } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_raw_slug() { return $this->_menu_slug; } /** * Get plugin's original menu slug. * * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_original_menu_slug() { if ( 'cpt' === $this->_type ) { return add_query_arg( array( 'post_type' => $this->_menu_slug ), 'edit.php' ); } if ( false === strpos( $this->_menu_slug, '.php?' ) ) { return $this->_menu_slug; } else { return $this->_module_unique_affix; } } /** * @author Vova Feldman (@svovaf) * @since 1.1.3 * * @return string */ function get_top_level_menu_slug() { return $this->has_custom_parent() ? $this->get_parent_slug() : $this->get_raw_slug(); } /** * Is user on plugin's admin activation page. * * @author Vova Feldman (@svovaf) * @since 1.0.8 * * @param bool $show_opt_in_on_themes_page Since 2.3.1 * * @return bool * * @deprecated Please use is_activation_page() instead. */ function is_main_settings_page( $show_opt_in_on_themes_page = false ) { return $this->is_activation_page( $show_opt_in_on_themes_page ); } /** * Is user on product's admin activation page. * * @author Vova Feldman (@svovaf) * @since 2.3.1 * * @param bool $show_opt_in_on_themes_page Since 2.3.1 * * @return bool */ function is_activation_page( $show_opt_in_on_themes_page = false ) { if ( $show_opt_in_on_themes_page ) { /** * In activation only when show_optin query string param is given. * * @since 1.2.2 */ return ( ( WP_FS__MODULE_TYPE_THEME === $this->_module_type ) && Freemius::is_themes_page() && fs_request_get_bool( $this->_module_unique_affix . '_show_optin' ) ); } if ( $this->_menu_exists && ( fs_is_plugin_page( $this->_menu_slug ) || fs_is_plugin_page( $this->_module_unique_affix ) ) ) { /** * Module has a settings menu and the context page is the main settings page, so assume it's in * activation (doesn't really check if already opted-in/skipped or not). * * @since 1.2.2 */ return true; } return false; } #region Submenu Override /** * Override submenu's action. * * @author Vova Feldman (@svovaf) * @since 1.1.0 * * @param string $parent_slug * @param string $menu_slug * @param callable $function * * @return false|string If submenu exist, will return the hook name. */ function override_submenu_action( $parent_slug, $menu_slug, $function ) { global $submenu; $menu_slug = plugin_basename( $menu_slug ); $parent_slug = plugin_basename( $parent_slug ); if ( ! isset( $submenu[ $parent_slug ] ) ) { // Parent menu not exist. return false; } $found_submenu_item = false; foreach ( $submenu[ $parent_slug ] as $submenu_item ) { if ( $menu_slug === $submenu_item[2] ) { $found_submenu_item = $submenu_item; break; } } if ( false === $found_submenu_item ) { // Submenu item not found. return false; } // Remove current function. $hookname = get_plugin_page_hookname( $menu_slug, $parent_slug ); remove_all_actions( $hookname ); // Attach new action. add_action( $hookname, $function ); return $hookname; } #endregion Submenu Override #region Top level menu Override /** * Find plugin's admin dashboard main menu item. * * @author Vova Feldman (@svovaf) * @since 1.0.2 * * @return string[]|false */ private function find_top_level_menu() { global $menu; $position = - 1; $found_menu = false; $menu_slug = $this->get_raw_slug(); $hook_name = get_plugin_page_hookname( $menu_slug, '' ); foreach ( $menu as $pos => $m ) { if ( $menu_slug === $m[2] ) { $position = $pos; $found_menu = $m; break; } } if ( false === $found_menu ) { return false; } return array( 'menu' => $found_menu, 'position' => $position, 'hook_name' => $hook_name ); } /** * Find plugin's admin dashboard main submenu item. * * @author Vova Feldman (@svovaf) * @since 1.2.1.6 * * @return array|false */ private function find_main_submenu() { global $submenu; $top_level_menu_slug = $this->get_top_level_menu_slug(); if ( ! isset( $submenu[ $top_level_menu_slug ] ) ) { return false; } $submenu_slug = $this->get_raw_slug(); $position = - 1; $found_submenu = false; $hook_name = get_plugin_page_hookname( $submenu_slug, '' ); foreach ( $submenu[ $top_level_menu_slug ] as $pos => $sub ) { if ( $submenu_slug === $sub[2] ) { $position = $pos; $found_submenu = $sub; } } if ( false === $found_submenu ) { return false; } return array( 'menu' => $found_submenu, 'parent_slug' => $top_level_menu_slug, 'position' => $position, 'hook_name' => $hook_name ); } /** * Remove all sub-menu items. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @return bool If submenu with plugin's menu slug was found. */ private function remove_all_submenu_items() { global $submenu; $menu_slug = $this->get_raw_slug(); if ( ! isset( $submenu[ $menu_slug ] ) ) { return false; } /** * This method is NOT executed for WordPress.org themes. * Since we maintain only one version of the SDK we added this small * hack to avoid the error from Theme Check since it's a false-positive. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ $submenu_ref = &$submenu; $submenu_ref[ $menu_slug ] = array(); return true; } /** * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param bool $remove_top_level_menu * * @return false|array[string]mixed */ function remove_menu_item( $remove_top_level_menu = false ) { $this->_logger->entrance(); // Find main menu item. $top_level_menu = $this->find_top_level_menu(); if ( false === $top_level_menu ) { return false; } // Remove it with its actions. remove_all_actions( $top_level_menu['hook_name'] ); // Remove all submenu items. $this->remove_all_submenu_items(); if ( $remove_top_level_menu ) { global $menu; unset( $menu[ $top_level_menu['position'] ] ); } return $top_level_menu; } /** * Get module's main admin setting page URL. * * @todo This method was only tested for wp.org compliant themes with a submenu item. Need to test for plugins with top level, submenu, and CPT top level, menu items. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 * * @return string */ function main_menu_url() { $this->_logger->entrance(); if ( $this->_is_top_level ) { $menu = $this->find_top_level_menu(); } else { $menu = $this->find_main_submenu(); } $parent_slug = isset( $menu['parent_slug'] ) ? $menu['parent_slug'] : 'admin.php'; return admin_url( $parent_slug . ( false === strpos( $parent_slug, '?' ) ? '?' : '&' ) . 'page=' . $menu['menu'][2] ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.4 * * @param callable $function * * @return false|array[string]mixed */ function override_menu_item( $function ) { $found_menu = $this->remove_menu_item(); if ( false === $found_menu ) { return false; } if ( ! $this->is_top_level() || ! $this->is_cpt() ) { $menu_slug = plugin_basename( $this->get_slug() ); $hookname = get_plugin_page_hookname( $menu_slug, '' ); // Override menu action. add_action( $hookname, $function ); } else { global $menu; // Remove original CPT menu. unset( $menu[ $found_menu['position'] ] ); // Create new top-level menu action. $hookname = self::add_page( $found_menu['menu'][3], $found_menu['menu'][0], 'manage_options', $this->get_slug(), $function, $found_menu['menu'][6], $found_menu['position'] ); } return $hookname; } /** * Adds a counter to the module's top level menu item. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param int $counter * @param string $class */ function add_counter_to_menu_item( $counter = 1, $class = '' ) { global $menu, $submenu; $mask = '%s '; /** * This method is NOT executed for WordPress.org themes. * Since we maintain only one version of the SDK we added this small * hack to avoid the error from Theme Check since it's a false-positive. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ $menu_ref = &$menu; $submenu_ref = &$submenu; if ( $this->_is_top_level ) { // Find main menu item. $found_menu = $this->find_top_level_menu(); if ( false !== $found_menu ) { // Override menu label. $menu_ref[ $found_menu['position'] ][0] = sprintf( $mask, $found_menu['menu'][0], $class, $counter ); } } else { $found_submenu = $this->find_main_submenu(); if ( false !== $found_submenu ) { // Override menu label. $submenu_ref[ $found_submenu['parent_slug'] ][ $found_submenu['position'] ][0] = sprintf( $mask, $found_submenu['menu'][0], $class, $counter ); } } } #endregion Top level menu Override /** * Add a top-level menu page. * * Note for WordPress.org Theme/Plugin reviewer: * * This is a replication of `add_menu_page()` to avoid Theme Check warning. * * Why? * ==== * Freemius is an SDK for plugin and theme developers. Since the core * of the SDK is relevant both for plugins and themes, for obvious reasons, * we only develop and maintain one code base. * * This method will not run for wp.org themes (only plugins) since theme * admin settings/options are now only allowed in the customizer. * * If you have any questions or need clarifications, please don't hesitate * pinging me on slack, my username is @svovaf. * * @author Vova Feldman (@svovaf) * @since 1.2.2 * * @param string $page_title The text to be displayed in the title tags of the page when the menu is * selected. * @param string $menu_title The text to be used for the menu. * @param string $capability The capability required for this menu to be displayed to the user. * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). * @param callable|string $function The function to be called to output the content for this page. * @param string $icon_url The URL to the icon to be used for this menu. * * Pass a base64-encoded SVG using a data URI, which will be colored to * match the color scheme. This should begin with * 'data:image/svg+xml;base64,'. * * Pass the name of a Dashicons helper class to use a font icon, * e.g. 'dashicons-chart-pie'. * * Pass 'none' to leave div.wp-menu-image empty so an icon can be added * via CSS. * @param int $position The position in the menu order this one should appear. * * @return string The resulting page's hook_suffix. */ static function add_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '', $position = null ) { $fn = 'add_menu' . '_page'; return $fn( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position ); } /** * Add page and update menu instance settings. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $page_title * @param string $menu_title * @param string $capability * @param string $menu_slug * @param callable|string $function * @param string $icon_url * @param int|null $position * * @return string */ function add_page_and_update( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '', $position = null ) { $this->_menu_slug = $menu_slug; $this->_is_top_level = true; $this->_menu_exists = true; $this->_network_menu_exists = true; return self::add_page( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position ); } /** * Add a submenu page. * * Note for WordPress.org Theme/Plugin reviewer: * * This is a replication of `add_submenu_page()` to avoid Theme Check warning. * * Why? * ==== * Freemius is an SDK for plugin and theme developers. Since the core * of the SDK is relevant both for plugins and themes, for obvious reasons, * we only develop and maintain one code base. * * This method will not run for wp.org themes (only plugins) since theme * admin settings/options are now only allowed in the customizer. * * If you have any questions or need clarifications, please don't hesitate * pinging me on slack, my username is @svovaf. * * @author Vova Feldman (@svovaf) * @since 1.2.2 * * @param string $parent_slug The slug name for the parent menu (or the file name of a standard * WordPress admin page). * @param string $page_title The text to be displayed in the title tags of the page when the menu is * selected. * @param string $menu_title The text to be used for the menu. * @param string $capability The capability required for this menu to be displayed to the user. * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu). * @param callable|string $function The function to be called to output the content for this page. * * @return false|string The resulting page's hook_suffix, or false if the user does not have the capability * required. */ static function add_subpage( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function = '' ) { $fn = 'add_submenu' . '_page'; return $fn( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function ); } /** * Add sub page and update menu instance settings. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $parent_slug * @param string $page_title * @param string $menu_title * @param string $capability * @param string $menu_slug * @param callable|string $function * * @return string */ function add_subpage_and_update( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function = '' ) { $this->_menu_slug = $menu_slug; $this->_parent_slug = $parent_slug; $this->_is_top_level = false; $this->_menu_exists = true; $this->_network_menu_exists = true; return self::add_subpage( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function ); } }PK!X6EBEB&freemius/includes/class-fs-options.phpnu[_id = $id; $this->_is_multisite = is_multisite(); if ( $this->_is_multisite ) { $this->_blog_id = get_current_blog_id(); $this->_network_options = FS_Option_Manager::get_manager( $id, $load, true ); } $this->_options = FS_Option_Manager::get_manager( $id, $load, $this->_blog_id ); } /** * Switch the context of the site level options manager. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param $blog_id */ function set_site_blog_context( $blog_id ) { $this->_blog_id = $blog_id; $this->_options = FS_Option_Manager::get_manager( $this->_id, false, $this->_blog_id ); } /** * @author Leo Fajardo (@leorw) * * @param string $option * @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::$_SITE_LEVEL_PARAMS). * * @return mixed */ function get_option( $option, $default = null, $network_level_or_blog_id = null ) { if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { return $this->_network_options->get_option( $option, $default ); } $site_options = $this->get_site_options( $network_level_or_blog_id ); return $site_options->get_option( $option, $default ); } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param string $option * @param mixed $value * @param bool $flush * @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::$_SITE_LEVEL_PARAMS). */ function set_option( $option, $value, $flush = false, $network_level_or_blog_id = null ) { if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { $this->_network_options->set_option( $option, $value, $flush ); } else { $site_options = $this->get_site_options( $network_level_or_blog_id ); $site_options->set_option( $option, $value, $flush ); } } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $option * @param bool $flush * @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::$_SITE_LEVEL_PARAMS). */ function unset_option( $option, $flush = false, $network_level_or_blog_id = null ) { if ( $this->should_use_network_storage( $option, $network_level_or_blog_id ) ) { $this->_network_options->unset_option( $option, $flush ); } else { $site_options = $this->get_site_options( $network_level_or_blog_id ); $site_options->unset_option( $option, $flush ); } } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @param bool $flush * @param bool $network_level */ function load( $flush = false, $network_level = true ) { if ( $this->_is_multisite && $network_level ) { $this->_network_options->load( $flush ); } else { $this->_options->load( $flush ); } } /** * @author Leo Fajardo (@leorw) * @since 2.0.0 * * @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`, store both network storage and the current context blog storage. */ function store( $network_level_or_blog_id = null ) { if ( ! $this->_is_multisite || false === $network_level_or_blog_id || 0 == $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) { $site_options = $this->get_site_options( $network_level_or_blog_id ); $site_options->store(); } if ( $this->_is_multisite && ( is_null( $network_level_or_blog_id ) || true === $network_level_or_blog_id ) ) { $this->_network_options->store(); } } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int|null|bool $network_level_or_blog_id * @param bool $flush */ function clear( $network_level_or_blog_id = null, $flush = false ) { if ( ! $this->_is_multisite || false === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) || is_numeric( $network_level_or_blog_id ) ) { $site_options = $this->get_site_options( $network_level_or_blog_id ); $site_options->clear( $flush ); } if ( $this->_is_multisite && ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) ) { $this->_network_options->clear( $flush ); } } /** * Migration script to the new storage data structure that is network compatible. * * IMPORTANT: * This method should be executed only after it is determined if this is a network * level compatible product activation. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id */ function migrate_to_network( $blog_id = 0 ) { if ( ! $this->_is_multisite ) { return; } $updated = false; $site_options = $this->get_site_options( $blog_id ); $keys = $site_options->get_options_keys(); foreach ( $keys as $option ) { if ( $this->is_site_option( $option ) || // Don't move admin notices to the network storage. in_array($option, array( // Don't move admin notices to the network storage. 'admin_notices', // Don't migrate the module specific data, it will be migrated by the FS_Storage. 'plugin_data', 'theme_data', )) ) { continue; } $option_updated = false; // Migrate option to the network storage. $site_option = $site_options->get_option( $option ); if ( ! $this->_network_options->has_option( $option ) ) { // Option not set on the network level, so just set it. $this->_network_options->set_option( $option, $site_option, false ); $option_updated = true; } else { // Option already set on the network level, so we need to merge it inelegantly. $network_option = $this->_network_options->get_option( $option ); if ( is_array( $network_option ) && is_array( $site_option ) ) { // Option is an array. foreach ( $site_option as $key => $value ) { if ( ! isset( $network_option[ $key ] ) ) { $network_option[ $key ] = $value; $option_updated = true; } else if ( is_array( $network_option[ $key ] ) && is_array( $value ) ) { if ( empty( $network_option[ $key ] ) ) { $network_option[ $key ] = $value; $option_updated = true; } else if ( empty( $value ) ) { // Do nothing. } else { reset($value); $first_key = key($value); if ( $value[$first_key] instanceof FS_Entity ) { // Merge entities by IDs. $network_entities_ids = array(); foreach ( $network_option[ $key ] as $entity ) { $network_entities_ids[ $entity->id ] = true; } foreach ( $value as $entity ) { if ( ! isset( $network_entities_ids[ $entity->id ] ) ) { $network_option[ $key ][] = $entity; $option_updated = true; } } } } } } } if ( $option_updated ) { $this->_network_options->set_option( $option, $network_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. */ // $site_options->unset_option($option, false); if ( $option_updated ) { $updated = true; } } if ( ! $updated ) { return; } // Update network level storage. $this->_network_options->store(); // $site_options->store(); } #-------------------------------------------------------------------------------- #region Helper Methods #-------------------------------------------------------------------------------- /** * We don't want to load the map right away since it's not even needed in a non-MS environment. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ private static function load_site_options_map() { self::$_SITE_OPTIONS_MAP = array( 'sites' => true, 'theme_sites' => true, 'unique_id' => true, 'active_plugins' => true, ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $option * * @return bool */ private function is_site_option( $option ) { if ( WP_FS__ACCOUNTS_OPTION_NAME != $this->_id ) { return false; } if ( ! isset( self::$_SITE_OPTIONS_MAP ) ) { self::load_site_options_map(); } return isset( self::$_SITE_OPTIONS_MAP[ $option ] ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id * * @return FS_Option_Manager */ private function get_site_options( $blog_id = 0 ) { if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { return $this->_options; } return FS_Option_Manager::get_manager( $this->_id, true, $blog_id ); } /** * Check if an option should be stored on the MS network storage. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $option * @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::$_SITE_LEVEL_PARAMS). * * @return bool */ private function should_use_network_storage( $option, $network_level_or_blog_id = null ) { if ( ! $this->_is_multisite ) { // Not a multisite environment. return false; } if ( is_numeric( $network_level_or_blog_id ) ) { // Explicitly asked to use a specified blog storage. return false; } if ( is_bool( $network_level_or_blog_id ) ) { // Explicitly specified whether 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_site_option( $option ); } #endregion }PK![!ѲAA+freemius/includes/fs-plugin-info-dialog.phpnu[_fs = $fs; $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' ); // Override action with custom plugins function for add-ons. 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 ); } /** * Generate add-on plugin information. * * @author Vova Feldman (@svovaf) * @since 1.0.6 * * @param array $data * @param string $action * @param object|null $args * * @return array|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() ); 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 ); if ( false === $selected_addon ) { return $data; } if ( ! isset( $selected_addon->info ) ) { // Setup some default info. $selected_addon->info = new stdClass(); $selected_addon->info->selling_point_0 = 'Selling Point 1'; $selected_addon->info->selling_point_1 = 'Selling Point 2'; $selected_addon->info->selling_point_2 = 'Selling Point 3'; $selected_addon->info->description = '

    Tell your users all about your add-on

    '; } fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' ); $data = $args; $has_free_plan = false; $has_paid_plan = false; // Load add-on pricing. $has_pricing = false; $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" ) ); 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; $plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] ); $plan = $plans[ $i ]; if ( 'free' == $plans[ $i ]->name || ! is_array( $pricing ) || 0 == count( $pricing ) ) { $has_free_plan = true; } if ( is_array( $pricing ) && 0 < count( $pricing ) ) { $filtered_pricing = array(); foreach ( $pricing as $prices ) { $prices = new FS_Pricing( $prices ); if ( ! $prices->is_usd() ) { /** * Skip non-USD pricing. * * @author Leo Fajardo (@leorw) * @since 2.3.1 */ 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 ) ) { $filtered_pricing[] = $prices; } } if ( ! empty( $filtered_pricing ) ) { $has_paid_plan = true; $plan->pricing = $filtered_pricing; $has_pricing = true; } } if ( is_array( $features ) && 0 < count( $features ) ) { $plan->features = $features; $has_features = true; } } } } $latest = null; if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) { $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository( 'plugin_information', (object) array( 'slug' => $selected_addon->slug, 'is_ssl' => is_ssl(), 'fields' => array( 'banners' => true, 'reviews' => true, 'downloaded' => false, 'active_installs' => true ) ) ); if ( ! empty( $repo_data ) ) { $data = $repo_data; $data->wp_org_missing = false; } else { // Couldn't find plugin on .org. $selected_addon->is_wp_org_compliant = false; // Plugin is missing, not on Freemius nor WP.org. $data->wp_org_missing = true; } $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 ); $current_addon_version = $fs_addon->get_plugin_version(); } 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 ) ), false, false ); if ( ! empty( $addon_plugin_data ) ) { $current_addon_version = $addon_plugin_data['Version']; } } // Fetch latest version from Freemius. $latest = $this->_fs->_fetch_latest_version( $selected_addon->id, true, WP_FS__TIME_24_HOURS_IN_SEC, $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_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 ) ); if ( $has_valid_blog_id ) { restore_current_blog(); } } /** * Check if there's a purchased license in case the add-on can only be installed/downloaded as part of a purchased bundle. * * @author Leo Fajardo (@leorw) * @since 2.4.1 */ 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 ) ) { $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 ); } $data->fs_missing = ( false === $latest && ( empty( $selected_addon->premium_releases_count ) || ! ( $selected_addon->premium_releases_count > 0 ) ) ); // Fetch as much as possible info from local files. $plugin_local_data = $this->_fs->get_plugin_data(); $data->author = $plugin_local_data['Author']; if ( ! empty( $selected_addon->info->banner_url ) ) { $data->banners = array( 'low' => $selected_addon->info->banner_url, ); } 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 ); } 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; } else if ( ! empty( $current_addon_version ) ) { $data->version = $current_addon_version; } else { // Add dummy version. $data->version = '1.0.0'; // Add message to developer to deploy the plugin through Freemius. } } $data->name = $selected_addon->title; $view_vars = array( 'plugin' => $selected_addon ); if ( is_object( $latest ) && isset( $latest->readme ) && is_object( $latest->readme ) ) { $latest_version_readme_data = $latest->readme; 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 ); if ( $has_pricing ) { // Add plans to data. $data->plans = $plans; if ( $has_features ) { $view_vars = array( 'plans' => $plans, 'plugin' => $selected_addon, ); $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars ); } } $data->has_free_plan = $has_free_plan; $data->has_paid_plan = $has_paid_plan; $data->is_paid = $has_paid_plan; $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant; $data->premium_slug = $selected_addon->premium_slug; $data->addon_id = $selected_addon->id; if ( ! isset( $data->has_purchased_license ) ) { $data->has_purchased_license = false; } return $data; } /** * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @param FS_Plugin_Plan $plan * * @return string */ private function get_billing_cycle( FS_Plugin_Plan $plan ) { $billing_cycle = null; if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) { $pricing = $plan->pricing[0]; if ( isset( $pricing->annual_price ) ) { $billing_cycle = 'annual'; } else if ( isset( $pricing->monthly_price ) ) { $billing_cycle = 'monthly'; } else if ( isset( $pricing->lifetime_price ) ) { $billing_cycle = 'lifetime'; } } else { foreach ( $plan->pricing as $pricing ) { if ( isset( $pricing->annual_price ) ) { $billing_cycle = 'annual'; } else if ( isset( $pricing->monthly_price ) ) { $billing_cycle = 'monthly'; } else if ( isset( $pricing->lifetime_price ) ) { $billing_cycle = 'lifetime'; } if ( ! is_null( $billing_cycle ) ) { break; } } } return $billing_cycle; } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param FS_Plugin_Plan $plan * @param FS_Pricing $pricing * * @return float|null|string */ 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' : '' ); } else if ( isset( $pricing->monthly_price ) ) { $price_tag = $pricing->monthly_price . ' / mo'; } else if ( isset( $pricing->lifetime_price ) ) { $price_tag = $pricing->lifetime_price; } return '$' . $price_tag; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param object $api * @param FS_Plugin_Plan $plan * * @return string */ private function get_actions_dropdown( $api, $plan = null ) { $this->actions = isset( $this->actions ) ? $this->actions : $this->get_plugin_actions( $api ); $actions = $this->actions; $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. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ if ( ! $api->has_purchased_license ) { array_unshift( $actions, $checkout_cta ); } else { $actions[] = $checkout_cta; } } if ( empty( $actions ) ) { return ''; } $total_actions = count( $actions ); if ( 1 === $total_actions ) { return $actions[0]; } ob_start(); ?>
    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 ) ) { $plan = $p; break; } } } $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 ); } $addon_checkout_url = $this->_fs->addon_checkout_url( $plan->plugin_id, $plan->pricing[0]->id, $this->get_billing_cycle( $plan ), $plan->has_trial(), ( $has_valid_blog_id ? false : null ) ); if ( $has_valid_blog_id ) { restore_current_blog(); } return '' . 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 ) ) : sprintf( /* translators: %s: N-days trial */ fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ), $this->get_trial_period( $plan ) ) ) . ''; } /** * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param object $api * * @return string[] */ private function get_plugin_actions( $api ) { $this->status = isset( $this->status ) ? $this->status : install_plugin_install_status( $api ); $is_update_available = ( 'update_available' === $this->status['status'] ); if ( $is_update_available && empty( $this->status['url'] ) ) { return array(); } $blog_id = fs_request_get( 'fs_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 ); $fs_addon = null; $is_free_installed = null; $is_premium_installed = null; $has_installed_version = ( 'install' !== $this->status['status'] ); if ( ! $api->has_paid_plan && ! $api->has_purchased_license ) { /** * Free-only add-on. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $is_free_installed = $has_installed_version; $is_premium_installed = false; } else if ( ! $api->has_free_plan ) { /** * Premium-only add-on. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $is_free_installed = false; $is_premium_installed = $has_installed_version; } else { /** * Freemium add-on. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ 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 ) : null; 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 ) { /** * 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 ) ); } } 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 ) ); } } } $has_installed_version = ( $is_free_installed || $is_premium_installed ); } $this->status['is_free_installed'] = $is_free_installed; $this->status['is_premium_installed'] = $is_premium_installed; $can_install_free_version = false; $can_install_free_version_update = false; $can_download_free_version = false; $can_activate_free_version = false; $can_install_premium_version = false; $can_install_premium_version_update = false; $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 ) { $can_install_free_version_update = true; } else if ( ! $is_premium_installed && ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) { $can_activate_free_version = true; } } else { if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() || $api->is_wp_org_compliant ) { $can_install_free_version = true; } else { $can_download_free_version = true; } } } } else { 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() ); } 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 ) { $can_install_premium_version_update = true; } else if ( ! $is_premium_installed ) { $can_install_premium_version = true; } } } if ( $can_install_premium_version || $can_install_premium_version_update ) { 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 * to work. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $this->status['url'] = self::get_blog_status_url( $blog_id, $this->status['url'], $this->status['status'] ); } /** * Add the `fs_allow_updater_and_dialog` param to the install/update URL so that the add-on can be * installed/updated. * * @author Leo Fajardo (@leorw) * @since 2.3.0 */ $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 ) { $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 ) ), true, false, $this->status['url'], '_parent' ); } 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 ) ), true, false, $this->status['url'], '_parent' ); } $download_latest_action = ''; if ( ! 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 ) ), true, false, esc_url( $api->download_link ) ); } 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 ), $can_activate_free_version ? 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 ) ) { $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 ) ) { $actions[] = $download_latest_action; $download_latest_action = ''; } $actions[] = $activate_action; } else { array_unshift( $actions, $activate_action ); } if ( ! empty ($download_latest_action ) ) { $actions[] = $download_latest_action; } } return $actions; } /** * Rebuilds the status URL based on the admin URL. * * @author Leo Fajardo (@leorw) * @since 2.3.0 * * @param int $blog_id * @param string $network_status_url * @param string $status * * @return string */ 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 ) ? 'install-plugin' : 'upgrade-plugin'; $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'] ) ) { 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}"); } /** * Helper method to get a CTA button HTML. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $label * @param bool $is_primary * @param bool $is_disabled * @param string $href * @param string $target * * @return string */ private function get_cta( $label, $is_primary = true, $is_disabled = false, $href = '', $target = '_blank' ) { $classes = array(); if ( ! $is_primary ) { $classes[] = 'left'; } else { $classes[] = 'button-primary'; $classes[] = 'right'; } if ( $is_disabled ) { $classes[] = 'disabled'; } $rel = ( '_blank' === $target ) ? ' rel="noopener noreferrer"' : ''; return sprintf( '%s', empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"' . $rel, implode( ' ', $classes ), $label ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.7 * * @param FS_Plugin_Plan $plan * * @return string */ private function get_trial_period( $plan ) { $trial_period = (int) $plan->trial_period; switch ( $trial_period ) { case 30: return 'month'; case 60: return '2 months'; default: return "{$plan->trial_period} days"; } } /** * Display plugin information in dialog box form. * * Based on core install_plugin_information() function. * * @author Vova Feldman (@svovaf) * @since 1.0.6 */ function install_plugin_information() { global $tab; if ( empty( $_REQUEST['plugin'] ) ) { return; } $args = array( 'slug' => wp_unslash( $_REQUEST['plugin'] ), 'is_ssl' => is_ssl(), 'fields' => array( 'banners' => true, 'reviews' => true, 'downloaded' => false, 'active_installs' => true ) ); if ( is_array( $args ) ) { $args = (object) $args; } if ( ! isset( $args->per_page ) ) { $args->per_page = 24; } if ( ! isset( $args->locale ) ) { $args->locale = get_locale(); } $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args ); if ( is_wp_error( $api ) ) { wp_die( $api ); } $plugins_allowedtags = array( 'a' => array( 'href' => array(), 'title' => array(), 'target' => array(), // Add image style for screenshots. 'class' => array() ), 'style' => array(), 'abbr' => array( 'title' => array() ), 'acronym' => array( 'title' => array() ), 'code' => array(), 'pre' => array(), 'em' => array(), 'strong' => array(), 'div' => array( 'class' => array() ), 'span' => array( 'class' => array() ), 'p' => array(), 'ul' => array(), 'ol' => array(), 'li' => array( 'class' => array() ), 'i' => array( 'class' => array() ), 'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'h6' => array(), 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ), // 'table' => array(), // 'td' => array(), // 'tr' => array(), // 'th' => array(), // 'thead' => array(), // 'tbody' => array(), ); $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 ), ); // Sanitize HTML // 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 ); } } // 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 ); $_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 ); } 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'] ) ) ) { $_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']; ?> '; 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 ) ) ) { continue; } if ( isset( $plugins_section_titles[ $section_name ] ) ) { $title = $plugins_section_titles[ $section_name ]; } else { $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"; } echo "
    \n"; ?>
    is_paid ) : ?> plans ) ) : ?>
    plans as $plan ) : ?> pricing ) ) { continue; } /** * @var FS_Plugin_Plan $plan */ ?> pricing[0] ?> is_multi_cycle() ?>

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

    has_annual() ?> has_monthly() ?>
    pricing[0]->annual_discount_percentage() : 0 ?> 0 ) : ?> slug ), $annual_discount . '%' ) ?>
    get_actions_dropdown( $api, $plan ) ?>
    has_trial() ) : ?> get_trial_period( $plan ) ?>
    • slug ), $trial_period ) ) ?>
    • slug ) ), $trial_period, '' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '' ) ?>

    slug ) ?>

      version ) ) { ?>
    • slug ); ?> : version; ?>
    • author ) ) { ?>
    • slug ); ?> : author, '_blank' ); ?>
    • last_updated ) ) { ?>
    • slug ); ?> : slug ), human_time_diff( strtotime( $api->last_updated ) ) ) ) ?>
    • requires ) ) { ?>
    • slug ) ?> : slug ), $api->requires ) ) ?>
    • tested ) ) { ?>
    • slug ); ?> : tested; ?>
    • requires_php ) ) { ?>
    • slug ); ?>: slug ), $api->requires_php ) ); ?>
    • downloaded ) ) { ?>
    • slug ) ?> : downloaded ) ? /* translators: %s: 1 or One (Number of times downloaded) */ fs_text_inline( '%s time', 'x-time', $api->slug ) : /* translators: %s: Number of times downloaded */ fs_text_inline( '%s times', 'x-times', $api->slug ) ), number_format_i18n( $api->downloaded ) ) ); ?>
    • slug ) && true == $api->is_wp_org_compliant ) { ?>
    • slug ) ?> »
    • homepage ) ) { ?>
    • slug ) ?> »
    • donate_link ) && empty( $api->contributors ) ) { ?>
    • slug ) ?> »
    rating ) ) { ?>

    slug ); ?>

    $api->rating, 'type' => 'percent', 'number' => $api->num_ratings ) ); ?> (slug ), sprintf( ( ( 1 == $api->num_ratings ) ? /* translators: %s: 1 or One */ fs_text_inline( '%s rating', 'x-rating', $api->slug ) : /* translators: %s: Number larger than 1 */ fs_text_inline( '%s ratings', 'x-ratings', $api->slug ) ), number_format_i18n( $api->num_ratings ) ) ) ) ?>) ratings ) && array_sum( (array) $api->ratings ) > 0 ) { foreach ( $api->ratings as $key => $ratecount ) { // Avoid div-by-zero. $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0; $stars_label = sprintf( ( ( 1 == $key ) ? /* translators: %s: 1 or One */ fs_text_inline( '%s star', 'x-star', $api->slug ) : /* translators: %s: Number larger than 1 */ fs_text_inline( '%s stars', 'x-stars', $api->slug ) ), number_format_i18n( $key ) ); ?>
    contributors ) ) { ?>

    slug ); ?>

      contributors as $contrib_username => $contrib_profile ) { if ( empty( $contrib_username ) && empty( $contrib_profile ) ) { continue; } if ( empty( $contrib_username ) ) { $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile ); } $contrib_username = sanitize_user( $contrib_username ); if ( empty( $contrib_profile ) ) { echo "
    • {$contrib_username}
    • "; } else { echo "
    • {$contrib_username}
    • "; } } ?>
    donate_link ) ) { ?> slug ) ?> »
    requires_php ) ? $api->requires_php : null; $requires_wp = isset( $api->requires ) ? $api->requires : null; $compatible_php = empty( $requires_php ) || version_compare( PHP_VERSION, $requires_php, '>=' ); // Strip off any -alpha, -RC, -beta, -src suffixes. list( $wp_version ) = explode( '-', $GLOBALS['wp_version'] ); $compatible_wp = empty( $requires_wp ) || version_compare( $wp_version, $requires_wp, '>=' ); $tested_wp = ( empty( $api->tested ) || version_compare( $wp_version, $api->tested, '<=' ) ); if ( ! $compatible_php ) { echo '

    ' . fs_text_inline( 'Error', 'error', $api->slug ) . ': ' . fs_text_inline( 'This plugin requires a newer version of PHP.', 'newer-php-required-error', $api->slug ); if ( current_user_can( 'update_php' ) ) { $wp_get_update_php_url = function_exists( 'wp_get_update_php_url' ) ? wp_get_update_php_url() : 'https://wordpress.org/support/update-php/'; printf( /* translators: %s: URL to Update PHP page. */ ' ' . fs_text_inline( 'Click here to learn more about updating PHP.', 'php-update-learn-more-link', $api->slug ), esc_url( $wp_get_update_php_url ) ); if ( function_exists( 'wp_update_php_annotation' ) ) { wp_update_php_annotation( '

    ', '' ); } } else { echo '

    '; } echo '
    '; } if ( ! $tested_wp ) { echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been tested with your current version of WordPress.', 'not-tested-warning', $api->slug ) . '

    '; } else if ( ! $compatible_wp ) { echo '

    ' . '' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ': ' . fs_text_inline( 'This plugin has not been marked as compatible with your version of WordPress.', 'not-compatible-warning', $api->slug ) . '

    '; } foreach ( (array) $api->sections as $section_name => $content ) { $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' ); $content = links_add_target( $content, '_blank' ); $san_section = esc_attr( $section_name ); $display = ( $section_name === $section ) ? 'block' : 'none'; if ( 'description' === $section_name && ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) || ( ! $api->is_wp_org_compliant && $api->fs_missing ) ) ) { $missing_notice = array( 'type' => 'error', 'id' => md5( microtime() ), 'message' => $api->is_paid ? fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) : fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ), ); fs_require_template( 'admin-notice.php', $missing_notice ); } echo "\t
    \n"; echo $content; echo "\t
    \n"; } echo "
    \n"; echo "
    \n"; echo "\n"; // #plugin-information-scrollable echo "\n"; ?> _id = $id; $this->_title = $title; $this->_module_unique_affix = $module_unique_affix; $this->_is_multisite = is_multisite(); if ( $this->_is_multisite ) { $this->_blog_id = get_current_blog_id(); $this->_network_notices = FS_Admin_Notice_Manager::instance( $id, $title, $module_unique_affix, $is_network_and_blog_admins, true ); } $this->_notices = FS_Admin_Notice_Manager::instance( $id, $title, $module_unique_affix, false, $this->_blog_id ); } /** * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $message * @param string $title * @param string $type * @param bool $is_sticky * @param string $id Message ID * @param bool $store_if_sticky * @param int|null $network_level_or_blog_id * * @uses add_action() */ function add( $message, $title = '', $type = 'success', $is_sticky = false, $id = '', $store_if_sticky = true, $network_level_or_blog_id = null, $is_dimissible = null ) { $notices = $this->get_site_or_network_notices( $id, $network_level_or_blog_id ); $notices->add( $message, $title, $type, $is_sticky, $id, $store_if_sticky, null, null, false, $is_dimissible ); } /** * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string|string[] $ids * @param int|null $network_level_or_blog_id * @param bool $store */ function remove_sticky( $ids, $network_level_or_blog_id = null, $store = true ) { if ( ! is_array( $ids ) ) { $ids = array( $ids ); } if ( $this->should_use_network_notices( $ids[0], $network_level_or_blog_id ) ) { $notices = $this->_network_notices; } else { $notices = $this->get_site_notices( $network_level_or_blog_id ); } return $notices->remove_sticky( $ids, $store ); } /** * Check if sticky message exists by id. * * @author Vova Feldman (@svovaf) * @since 1.0.9 * * @param string $id * @param int|null $network_level_or_blog_id * * @return bool */ function has_sticky( $id, $network_level_or_blog_id = null ) { $notices = $this->get_site_or_network_notices( $id, $network_level_or_blog_id ); return $notices->has_sticky( $id ); } /** * Adds sticky admin notification. * * @author Vova Feldman (@svovaf) * @since 1.0.7 * * @param string $message * @param string $id Message ID * @param string $title * @param string $type * @param int|null $network_level_or_blog_id * @param number|null $wp_user_id * @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 */ function add_sticky( $message, $id, $title = '', $type = 'success', $network_level_or_blog_id = null, $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false, $is_dismissible = true, $data = array() ) { $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 */ function get_sticky( $id, $network_level_or_blog_id ) { $notices = $this->get_site_or_network_notices( $id, $network_level_or_blog_id ); return $notices->get_sticky( $id ); } /** * Clear all sticky messages. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int|null $network_level_or_blog_id * @param bool $is_temporary */ function clear_all_sticky( $network_level_or_blog_id = null, $is_temporary = false ) { if ( ! $this->_is_multisite || false === $network_level_or_blog_id || 0 == $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) { $notices = $this->get_site_notices( $network_level_or_blog_id ); $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( $is_temporary ); } } /** * Add admin message to all admin messages queue, and hook to all_admin_notices if not yet hooked. * * @author Vova Feldman (@svovaf) * @since 1.0.4 * * @param string $message * @param string $title * @param string $type * @param bool $is_sticky * @param string $id Message ID */ function add_all( $message, $title = '', $type = 'success', $is_sticky = false, $id = '' ) { $this->add( $message, $title, $type, $is_sticky, true, $id ); } #-------------------------------------------------------------------------------- #region Helper Methods #-------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param int $blog_id * * @return FS_Admin_Notice_Manager */ private function get_site_notices( $blog_id = 0 ) { if ( 0 == $blog_id || $blog_id == $this->_blog_id ) { return $this->_notices; } return FS_Admin_Notice_Manager::instance( $this->_id, $this->_title, $this->_module_unique_affix, false, $blog_id ); } /** * Check if the network notices should be used. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $id * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite notices (if there's a network). When `false`, use the current context blog notices. When `null`, the decision which notices manager to use (MS vs. Current S) will be handled internally and determined based on the $id and the context admin (blog admin vs. network level admin). * * @return bool */ private function should_use_network_notices( $id = '', $network_level_or_blog_id = null ) { if ( ! $this->_is_multisite ) { // Not a multisite environment. return false; } if ( is_numeric( $network_level_or_blog_id ) ) { // Explicitly asked to use a specified blog storage. return false; } if ( is_bool( $network_level_or_blog_id ) ) { // Explicitly specified whether should use the network or blog level storage. return $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 }PK!aTT"freemius/includes/class-fs-api.phpnu[get_option( 'api_clock_diff', 0 ); Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff ); if ( self::$_options->get_option( 'api_force_http', false ) ) { Freemius_Api_WordPress::SetHttp(); } } /** * @param string $slug * @param string $scope 'app', 'developer', 'user' or 'install'. * @param number $id Element's id. * @param string $public_key Public key. * @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, $scope, $id, $public_key, $secret_key, $is_sandbox, $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 ); } /** * Find clock diff between server and API server, and store the diff locally. * * @param bool|int $diff * * @return bool|int False if clock diff didn't change, otherwise returns the clock diff in seconds. */ private function _sync_clock_diff( $diff = false ) { $this->_logger->entrance(); // Sync clock and store. $new_clock_diff = ( false === $diff ) ? Freemius_Api_WordPress::FindClockDiff() : $diff; if ( $new_clock_diff === self::$_clock_diff ) { return false; } self::$_clock_diff = $new_clock_diff; // Update API clock's diff. Freemius_Api_WordPress::SetClockDiff( self::$_clock_diff ); // Store new clock diff in storage. self::$_options->set_option( 'api_clock_diff', self::$_clock_diff, true ); return $new_clock_diff; } /** * Override API call to enable retry with servers' clock auto sync method. * * @param string $path * @param string $method * @param array $params * @param bool $in_retry Is in retry or first call attempt. * * @return array|mixed|string|void */ 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 { /** * @since 2.3.0 Include the SDK version with all API requests that going through the API manager. IMPORTANT: Only pass the SDK version if the caller didn't include it yet. */ if ( ! empty( $this->_sdk_version ) ) { if ( false === strpos( $path, 'sdk_version=' ) && ! isset( $params['sdk_version'] ) ) { // Always add the sdk_version param in the querystring. DO NOT INCLUDE IT IN THE BODY PARAMS, OTHERWISE, IT MAY LEAD TO AN UNEXPECTED PARAMS PARSING IN CASES WHERE THE $params IS A REGULAR NON-ASSOCIATIVE ARRAY. $path = add_query_arg( 'sdk_version', $this->_sdk_version, $path ); } } /** * @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 ( ! $in_retry && null !== $result && isset( $result->error ) && isset( $result->error->code ) ) { $retry = false; if ( 'request_expired' === $result->error->code ) { $diff = isset( $result->error->timestamp ) ? ( time() - strtotime( $result->error->timestamp ) ) : false; // Try to sync clock diff. if ( false !== $this->_sync_clock_diff( $diff ) ) { // Retry call with new synced clock. $retry = true; } } else if ( 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 ( 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; } /** * Override API call to wrap it in servers' clock sync method. * * @param string $path * @param string $method * @param array $params * * @return array|mixed|string|void * @throws Freemius_Exception */ function call( $path, $method = 'GET', $params = array() ) { return $this->_call( $path, $method, $params ); } /** * Get API request URL signed via query string. * * @param string $path * * @return string */ function get_signed_url( $path ) { return $this->_api->GetSignedUrl( $path ); } /** * @param string $path * @param bool $flush * @param int $expiration (optional) Time until expiration in seconds from now, defaults to 24 hours * * @return stdClass|mixed */ function get( $path = '/', $flush = false, $expiration = WP_FS__TIME_24_HOURS_IN_SEC ) { $this->_logger->entrance( $path ); $cache_key = $this->get_cache_key( $path ); // Always flush during development. if ( WP_FS__DEV_MODE || $this->_api->IsSandbox() ) { $flush = true; } $cached_result = self::$_cache->get( $cache_key ); if ( $flush || ! self::$_cache->has_valid( $cache_key, $expiration ) ) { $result = $this->call( $path ); if ( ! is_object( $result ) || isset( $result->error ) ) { // Api returned an error. if ( is_object( $cached_result ) && ! isset( $cached_result->error ) ) { // If there was an error during a newer data fetch, // fallback to older data version. $result = $cached_result; if ( $this->_logger->is_on() ) { $this->_logger->warn( 'Fallback to cached API result: ' . var_export( $cached_result, true ) ); } } else { if ( is_object( $result ) && isset( $result->error->http ) && 404 == $result->error->http ) { /** * If the response code is 404, cache the result for half of the `$expiration`. * * @author Leo Fajardo (@leorw) * @since 2.2.4 */ $expiration /= 2; } else { // If no older data version and the response code is not 404, return result without // caching the error. return $result; } } } self::$_cache->set( $cache_key, $result, $expiration ); $cached_result = $result; } else { $this->_logger->log( 'Using cached API result.' ); } 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. */ 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. * * @author Vova Feldman (@svovaf) * @since 1.2.1 * * @param string $path * @param string $method * @param array $params * * @return bool */ function is_cached( $path, $method = 'GET', $params = array() ) { $cache_key = $this->get_cache_key( $path, $method, $params ); return self::$_cache->has_valid( $cache_key ); } /** * Invalidate a cached version of the API request. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param string $path * @param string $method * @param array $params */ function purge_cache( $path, $method = 'GET', $params = array() ) { $this->_logger->entrance( "{$method}:{$path}" ); $cache_key = $this->get_cache_key( $path, $method, $params ); self::$_cache->purge( $cache_key ); } /** * Invalidate a cached version of the API request. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param string $path * @param int $expiration * @param string $method * @param array $params */ function update_cache_expiration( $path, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $method = 'GET', $params = array() ) { $this->_logger->entrance( "{$method}:{$path}:{$expiration}" ); $cache_key = $this->get_cache_key( $path, $method, $params ); self::$_cache->update_expiration( $cache_key, $expiration ); } /** * @param string $path * @param string $method * @param array $params * * @return string * @throws \Freemius_Exception */ private function get_cache_key( $path, $method = 'GET', $params = array() ) { $canonized = $this->_api->CanonizePath( $path ); // $exploded = explode('/', $canonized); // return $method . '_' . array_pop($exploded) . '_' . md5($canonized . json_encode($params)); return strtolower( $method . ':' . $canonized ) . ( ! empty( $params ) ? '#' . md5( json_encode( $params ) ) : '' ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.4 * * @param bool $is_http */ private function toggle_force_http( $is_http ) { self::$_options->set_option( 'api_force_http', $is_http, true ); if ( $is_http ) { Freemius_Api_WordPress::SetHttp(); } else if ( method_exists( 'Freemius_Api_WordPress', 'SetHttps' ) ) { Freemius_Api_WordPress::SetHttps(); } } /** * @author Leo Fajardo (@leorw) * @since 2.5.4 * * @param mixed $response * * @return bool */ static function is_blocked( $response ) { return ( self::is_api_error_object( $response, true ) && isset( $response->error->code ) && 'api_blocked' === $response->error->code ); } /** * Check if API is temporary down. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @return bool */ static function is_temporary_down() { self::_init(); $test = self::$_cache->get_valid( 'ping_test', null ); return ( false === $test ); } /** * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @return object */ private function get_temporary_unavailable_error() { return (object) array( 'error' => (object) array( 'type' => 'TemporaryUnavailable', 'message' => 'API is temporary unavailable, please retry in ' . ( self::$_cache->get_record_expiration( 'ping_test' ) - WP_FS__SCRIPT_START_TIME ) . ' sec.', 'code' => 'temporary_unavailable', 'http' => 503 ) ); } /** * Check if based on the API result we should try * to re-run the same request with HTTP instead of HTTPS. * * @author Vova Feldman (@svovaf) * @since 1.1.6 * * @param $result * * @return bool */ private static function should_try_with_http( $result ) { if ( ! Freemius_Api_WordPress::IsHttps() ) { return false; } return ( ! is_object( $result ) || ! isset( $result->error ) || ! isset( $result->error->code ) || ! in_array( $result->error->code, array( 'curl_missing', 'cloudflare_ddos_protection', 'maintenance_mode', 'squid_cache_block', 'too_many_requests', ) ) ); } function get_url( $path = '' ) { return Freemius_Api_WordPress::GetUrl( $path, $this->_api->IsSandbox() ); } /** * Clear API cache. * * @author Vova Feldman (@svovaf) * @since 1.0.9 */ static function clear_cache() { self::_init(); self::$_cache = FS_Cache_Manager::get_manager( WP_FS__API_CACHE_OPTION_NAME ); self::$_cache->clear(); } /** * @author Leo Fajardo (@leorw) * @since 2.5.4 */ static function clear_force_http_flag() { self::$_options->unset_option( 'api_force_http' ); } #---------------------------------------------------------------------------------- #region Error Handling #---------------------------------------------------------------------------------- /** * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $result * * @return bool Is API result contains an error. */ static function is_api_error( $result ) { return ( is_object( $result ) && isset( $result->error ) ) || is_string( $result ); } /** * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param mixed $result * @param bool $ignore_message * * @return bool Is API result contains an error. */ static function is_api_error_object( $result, $ignore_message = false ) { return ( is_object( $result ) && isset( $result->error ) && ( $ignore_message || isset( $result->error->message ) ) ); } /** * @author Leo Fajardo (@leorw) * @since 2.5.4 * * @param WP_Error|object|string $response * * @return bool */ 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] ); } } else if ( 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' ) ) ) ); } /** * Checks if given API result is a non-empty and not an error object. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $result * @param string|null $required_property Optional property we want to verify that is set. * * @return bool */ static function is_api_result_object( $result, $required_property = null ) { return ( is_object( $result ) && ! isset( $result->error ) && ( empty( $required_property ) || isset( $result->{$required_property} ) ) ); } /** * Checks if given API result is a non-empty entity object with non-empty ID. * * @author Vova Feldman (@svovaf) * @since 1.2.1.5 * * @param mixed $result * * @return bool */ static function is_api_result_entity( $result ) { return self::is_api_result_object( $result, 'id' ) && FS_Entity::is_valid_id( $result->id ); } /** * Get API result error code. If failed to get code, returns an empty string. * * @author Vova Feldman (@svovaf) * @since 2.0.0 * * @param mixed $result * * @return string */ static function get_error_code( $result ) { if ( is_object( $result ) && isset( $result->error ) && is_object( $result->error ) && ! empty( $result->error->code ) ) { return $result->error->code; } return ''; } #endregion }PK!/``freemius/includes/l10n.phpnu[ $section ) { ?> $row ) { $col_count = count( $row ); ?>
    :
    PK!_freemius/templates/pricing.phpnu[get_slug(); $timestamp = time(); $context_params = array( 'plugin_id' => $fs->get_id(), 'plugin_public_key' => $fs->get_public_key(), 'plugin_version' => $fs->get_plugin_version(), ); $bundle_id = $fs->get_bundle_id(); if ( ! is_null( $bundle_id ) ) { $context_params['bundle_id'] = $bundle_id; } // Get site context secure params. if ( $fs->is_registered() ) { $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( $fs->get_site(), $timestamp, 'upgrade' ) ); } else { $context_params['home_url'] = home_url(); } if ( $fs->is_payments_sandbox() ) // Append plugin secure token for sandbox mode authentication.) { $context_params['sandbox'] = FS_Security::instance()->get_secure_token( $fs->get_plugin(), $timestamp, 'checkout' ); } $query_params = array_merge( $context_params, $_GET, array( 'next' => $fs->_get_sync_license_url( false, false ), 'plugin_version' => $fs->get_plugin_version(), // Billing cycle. 'billing_cycle' => fs_request_get( 'billing_cycle', WP_FS__PERIOD_ANNUALLY ), 'is_network_admin' => fs_is_network_admin() ? 'true' : 'false', 'currency' => $fs->apply_filters( 'default_currency', 'usd' ), 'discounts_model' => $fs->apply_filters( 'pricing/discounts_model', 'absolute' ), ) ); $use_external_pricing = $fs->should_use_external_pricing(); if ( ! $use_external_pricing ) { $pricing_js_url = fs_asset_url( $fs->get_pricing_js_path() ); wp_enqueue_script( 'freemius-pricing', $pricing_js_url ); } else { if ( ! $fs->is_registered() ) { $template_data = array( 'id' => $fs->get_id(), ); fs_require_template( 'forms/trial-start.php', $template_data); } $view_params = array( 'id' => $VARS['id'], 'page' => strtolower( $fs->get_text_x_inline( 'Pricing', 'noun', 'pricing' ) ), ); fs_require_once_template('secure-https-header.php', $view_params); } $has_tabs = $fs->_add_tabs_before_content(); if ( $has_tabs ) { $query_params['tabs'] = 'true'; } ?>
    $fs->contact_url(), 'is_production' => ( defined( 'WP_FS__IS_PRODUCTION_MODE' ) ? WP_FS__IS_PRODUCTION_MODE : null ), 'menu_slug' => $fs->get_menu_slug(), 'mode' => 'dashboard', 'fs_wp_endpoint_url' => WP_FS__ADDRESS, 'request_handler_url' => admin_url( 'admin-ajax.php?' . http_build_query( array( 'module_id' => $fs->get_id(), 'action' => $fs->get_ajax_action( 'pricing_ajax_action' ), 'security' => $fs->get_ajax_security( 'pricing_ajax_action' ) ) ) ), 'selector' => '#fs_pricing_wrapper', 'unique_affix' => $fs->get_unique_affix(), 'show_annual_in_monthly' => $fs->apply_filters( 'pricing/show_annual_in_monthly', true ), ), $query_params ); wp_add_inline_script( 'freemius-pricing', 'Freemius.pricing.new( ' . json_encode( $pricing_config ) . ' )' ); ?>
    _add_tabs_after_content(); } $params = array( 'page' => 'pricing', 'module_id' => $fs->get_id(), 'module_type' => $fs->get_module_type(), 'module_slug' => $slug, 'module_version' => $fs->get_plugin_version(), ); fs_require_template( 'powered-by.php', $params ); PK!0 %freemius/templates/partials/index.phpnu[0Euu2freemius/templates/partials/network-activation.phpnu[get_slug(); $sites = $VARS['sites']; $require_license_key = $VARS['require_license_key']; $show_delegation_option = $fs->apply_filters( 'show_delegation_option', true ); $enable_per_site_activation = $fs->apply_filters( 'enable_per_site_activation', true ); ?> |' ?> PK!1˜!freemius/templates/powered-by.phpnu[is_whitelabeled() && ! $fs->apply_filters( 'hide_freemius_powered_by', false ) ) { wp_enqueue_script( 'jquery' ); wp_enqueue_script( 'json2' ); fs_enqueue_local_script( 'postmessage', 'nojquery.ba-postmessage.min.js' ); fs_enqueue_local_script( 'fs-postmessage', 'postmessage.js' ); ?>
    PK!, *freemius/templates/clone-resolution-js.phpnu[ PK!i%,,freemius/templates/checkout.phpnu[get_slug(); $timestamp = time(); $context_params = array( 'plugin_id' => $fs->get_id(), 'public_key' => $fs->get_public_key(), 'plugin_version' => $fs->get_plugin_version(), 'mode' => 'dashboard', 'trial' => fs_request_get_bool( 'trial' ), 'is_ms' => ( fs_is_network_admin() && $fs->is_network_active() ), ); $plan_id = fs_request_get( 'plan_id' ); if ( FS_Plugin_Plan::is_valid_id( $plan_id ) ) { $context_params['plan_id'] = $plan_id; } $licenses = fs_request_get( 'licenses' ); if ( $licenses === strval( intval( $licenses ) ) && $licenses > 0 ) { $context_params['licenses'] = $licenses; } $plugin_id = fs_request_get( 'plugin_id' ); if ( ! FS_Plugin::is_valid_id( $plugin_id ) ) { $plugin_id = $fs->get_id(); } if ( $plugin_id == $fs->get_id() ) { $is_premium = $fs->is_premium(); $bundle_id = $fs->get_bundle_id(); if ( ! is_null( $bundle_id ) ) { $context_params['bundle_id'] = $bundle_id; } } else { // Identify the module code version of the checkout context module. if ( $fs->is_addon_activated( $plugin_id ) ) { $fs_addon = Freemius::get_instance_by_id( $plugin_id ); $is_premium = $fs_addon->is_premium(); } else { // If add-on isn't activated assume the premium version isn't installed. $is_premium = false; } } // Get site context secure params. if ( $fs->is_registered() ) { $site = $fs->get_site(); if ( $plugin_id != $fs->get_id() ) { if ( $fs->is_addon_activated( $plugin_id ) ) { $fs_addon = Freemius::get_instance_by_id( $plugin_id ); $addon_site = $fs_addon->get_site(); if ( is_object( $addon_site ) ) { $site = $addon_site; } } } $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( $site, $timestamp, 'checkout' ) ); } else { $current_user = Freemius::_get_current_wp_user(); // Add site and user info to the request, this information // is NOT being stored unless the user complete the purchase // and agrees to the TOS. $context_params = array_merge( $context_params, array( 'user_firstname' => $current_user->user_firstname, 'user_lastname' => $current_user->user_lastname, 'user_email' => $current_user->user_email, 'home_url' => home_url(), ) ); $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); if ( is_object( $fs_user ) && $fs_user->is_verified() ) { $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( $fs_user, $timestamp, 'checkout' ) ); } } if ( $fs->is_payments_sandbox() ) { // Append plugin secure token for sandbox mode authentication. $context_params['sandbox'] = FS_Security::instance()->get_secure_token( $fs->get_plugin(), $timestamp, 'checkout' ); /** * @since 1.1.7.3 Add security timestamp for sandbox even for anonymous user. */ if ( empty( $context_params['s_ctx_ts'] ) ) { $context_params['s_ctx_ts'] = $timestamp; } } $return_url = $fs->_get_sync_license_url( $plugin_id ); $can_user_install = ( ( $fs->is_plugin() && current_user_can( 'install_plugins' ) ) || ( $fs->is_theme() && current_user_can( 'install_themes' ) ) ); $query_params = array_merge( $context_params, $_GET, array( // Current plugin version. 'plugin_version' => $fs->get_plugin_version(), 'sdk_version' => WP_FS__SDK_VERSION, 'is_premium' => $is_premium ? 'true' : 'false', 'can_install' => $can_user_install ? 'true' : 'false', 'return_url' => $return_url, ) ); $xdebug_session = fs_request_get( 'XDEBUG_SESSION' ); if ( false !== $xdebug_session ) { $query_params['XDEBUG_SESSION'] = $xdebug_session; } $view_params = array( 'id' => $VARS['id'], 'page' => strtolower( $fs->get_text_inline( 'Checkout', 'checkout' ) ) . ' ' . $fs->get_text_inline( 'PCI compliant', 'pci-compliant' ), ); fs_require_once_template('secure-https-header.php', $view_params); ?>
    PK!ZM*freemius/templates/secure-https-header.phpnu[
    get_text_inline( 'Secure HTTPS %s page, running from an external domain', 'secure-x-page-header' ), $VARS['page'] ) ) . ' - ' . sprintf( '%s', 'https://www.mcafeesecure.com/verify?host=' . WP_FS__ROOT_DOMAIN_PRODUCTION, 'Freemius Inc. [US]' ); } ?>
    PK![0Gfreemius/templates/contact.phpnu[get_slug(); $context_params = array( 'plugin_id' => $fs->get_id(), 'plugin_public_key' => $fs->get_public_key(), 'plugin_version' => $fs->get_plugin_version(), ); // Get site context secure params. if ( $fs->is_registered() ) { $context_params = array_merge( $context_params, FS_Security::instance()->get_context_params( $fs->get_site(), time(), 'contact' ) ); } $query_params = array_merge( $_GET, array_merge( $context_params, array( 'plugin_version' => $fs->get_plugin_version(), 'wp_login_url' => wp_login_url(), 'site_url' => Freemius::get_unfiltered_site_url(), // 'wp_admin_css' => get_bloginfo('wpurl') . "/wp-admin/load-styles.php?c=1&load=buttons,wp-admin,dashicons", ) ) ); $view_params = array( 'id' => $VARS['id'], 'page' => strtolower( $fs->get_text_inline( 'Contact', 'contact' ) ), ); fs_require_once_template('secure-https-header.php', $view_params); $has_tabs = $fs->_add_tabs_before_content(); if ( $has_tabs ) { $query_params['tabs'] = 'true'; } ?>
    _add_tabs_after_content(); } $params = array( 'page' => 'contact', 'module_id' => $fs->get_id(), 'module_type' => $fs->get_module_type(), 'module_slug' => $slug, 'module_version' => $fs->get_plugin_version(), ); fs_require_template( 'powered-by.php', $params );PK!Gao&freemius/templates/debug/api-calls.phpnu[ 0, 'POST' => 0, 'PUT' => 0, 'DELETE' => 0 ); $show_body = false; foreach ( $logger as $log ) { $counters[ $log['method'] ] ++; if ( ! is_null( $log['body'] ) ) { $show_body = true; } } $pretty_print = $show_body && defined( 'JSON_PRETTY_PRINT' ) && version_compare( phpversion(), '5.3', '>=' ); /** * This template is used for debugging, therefore, when possible * we'd like to prettify the output of a JSON encoded variable. * This will only be executed when $pretty_print is `true`, and * the var is `true` only for PHP 5.3 and higher. Due to the * limitations of the current Theme Check, it throws an error * that using the "options" parameter (the 2nd param) is not * supported in PHP 5.2 and lower. Thus, we added this alias * variable to work around that false-positive. * * @author Vova Feldman (@svovaf) * @since 1.2.2.7 */ $encode = 'json_encode'; $root_path_len = strlen( ABSPATH ); $ms_text = fs_text_x_inline( 'ms', 'milliseconds' ); ?>

    Total Time:

    Total Requests:

    $count ) : ?>

    :

    #
    . %s', $log['path'] ); ?> %s', substr( $body, 0, 32 ) . ( 32 < strlen( $body ) ? '...' : '' ) ); if ( $pretty_print ) { $body = $encode( json_decode( $log['body'] ), JSON_PRETTY_PRINT ); } ?>
    %s', substr( $result, 0, 32 ) . ( 32 < strlen( $result ) ? '...' : '' ) ); } if ( $is_not_empty_result && $pretty_print ) { $decoded = json_decode( $result ); if ( ! is_null( $decoded ) ) { $result = $encode( $decoded, JSON_PRETTY_PRINT ); } } else { $result = is_string( $result ) ? $result : json_encode( $result ); } ?> style="display: none">
    PK!@> 0freemius/templates/debug/plugins-themes-sync.phpnu[get_option( 'all_plugins' ); $all_themes = $fs_options->get_option( 'all_themes' ); /* translators: %s: time period (e.g. In "2 hours") */ $in_x_text = fs_text_inline( 'In %s', 'in-x' ); /* translators: %s: time period (e.g. "2 hours" ago) */ $x_ago_text = fs_text_inline( '%s ago', 'x-ago' ); $sec_text = fs_text_x_inline( 'sec', 'seconds' ); ?>

    plugins ) ?> timestamp ) && is_numeric( $all_plugins->timestamp ) ) { $diff = abs( WP_FS__SCRIPT_START_TIME - $all_plugins->timestamp ); $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? $diff . ' ' . $sec_text : human_time_diff( WP_FS__SCRIPT_START_TIME, $all_plugins->timestamp ); echo esc_html( sprintf( ( ( WP_FS__SCRIPT_START_TIME < $all_plugins->timestamp ) ? $in_x_text : $x_ago_text ), $human_diff ) ); } ?>
    themes ) ?> timestamp ) && is_numeric( $all_themes->timestamp ) ) { $diff = abs( WP_FS__SCRIPT_START_TIME - $all_themes->timestamp ); $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? $diff . ' ' . $sec_text : human_time_diff( WP_FS__SCRIPT_START_TIME, $all_themes->timestamp ); echo esc_html( sprintf( ( ( WP_FS__SCRIPT_START_TIME < $all_themes->timestamp ) ? $in_x_text : $x_ago_text ), $human_diff ) ); } ?>
    PK!ru},freemius/templates/debug/scheduled-crons.phpnu[get_option( $module_type . 's' ), FS_Plugin::get_class_name() ); if ( is_array( $modules ) && count( $modules ) > 0 ) { foreach ( $modules as $slug => $data ) { if ( WP_FS__MODULE_TYPE_THEME === $module_type ) { $current_theme = wp_get_theme(); $is_active = ( $current_theme->stylesheet === $data->file ); } else { $is_active = is_plugin_active( $data->file ); } /** * @author Vova Feldman * * @since 1.2.1 Don't load data from inactive modules. */ if ( $is_active ) { $fs = freemius( $data->id ); $next_execution = $fs->next_sync_cron(); $last_execution = $fs->last_sync_cron(); if ( false !== $next_execution ) { $scheduled_crons[ $slug ][] = array( 'name' => $fs->get_plugin_name(), 'slug' => $slug, 'module_type' => $fs->get_module_type(), 'type' => 'sync_cron', 'last' => $last_execution, 'next' => $next_execution, ); } $next_install_execution = $fs->next_install_sync(); $last_install_execution = $fs->last_install_sync(); if (false !== $next_install_execution || false !== $last_install_execution ) { $scheduled_crons[ $slug ][] = array( 'name' => $fs->get_plugin_name(), 'slug' => $slug, 'module_type' => $fs->get_module_type(), 'type' => 'install_sync', 'last' => $last_install_execution, 'next' => $next_install_execution, ); } } } } } $sec_text = fs_text_x_inline( 'sec', 'seconds' ); ?>

    $crons ) : ?>
    PK!p2WW"freemius/templates/debug/index.phpnu[

    >
    #
    . get_id() ?> %s', esc_html( substr( $log['msg'], 0, 32 ) ) . ( 32 < strlen( $log['msg'] ) ? '...' : '' ) ); ?>
    get_file() ) . ':' . $log['line']; } ?>
    PK!t8+freemius/templates/add-trial-to-pricing.phpnu[ PK!24o&freemius/templates/tabs-capture-js.phpnu[get_slug(); ?> PK!LҐAAfreemius/templates/tabs.phpnu[get_slug(); $menu_items = $fs->get_menu_items(); $show_settings_with_tabs = $fs->show_settings_with_tabs(); $tabs = array(); foreach ( $menu_items as $priority => $items ) { foreach ( $items as $item ) { if ( ! $item['show_submenu'] ) { $submenu_name = ('wp-support-forum' === $item['menu_slug']) ? 'support' : $item['menu_slug']; if ( 'pricing' === $submenu_name && ! $fs->is_pricing_page_visible() ) { continue; } if ( ! $show_settings_with_tabs || ! $fs->is_submenu_item_visible( $submenu_name, true ) ) { continue; } } $url = $fs->_get_admin_page_url( $item['menu_slug'] ); $title = $item['menu_title']; $tab = array( 'label' => $title, 'href' => $url, 'slug' => $item['menu_slug'], ); if ( 'pricing' === $item['menu_slug'] && $fs->is_in_trial_promotion() ) { $tab['href'] .= '&trial=true'; } $tabs[] = $tab; } } ?> PK![[freemius/templates/add-ons.phpnu[get_slug(); $open_addon_slug = fs_request_get( 'slug' ); $open_addon = false; $is_data_debug_mode = $fs->is_data_debug_mode(); $is_whitelabeled = $fs->is_whitelabeled(); /** * @var FS_Plugin[] */ $addons = $fs->get_addons(); $has_addons = ( is_array( $addons ) && 0 < count( $addons ) ); $account_addon_ids = $fs->get_updated_account_addons(); $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); $view_details_text = fs_text_inline( 'View details', 'view-details', $slug ); $has_tabs = $fs->_add_tabs_before_content(); $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? get_current_blog_id() : 0; ?>

    get_plugin_name() ) ) ?>

    do_action( 'addons/after_title' ) ?>

      _get_addons_plans_and_pricing_map_by_id(); $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); ?> is_whitelabeled_by_flag() ) { $hide_all_addons_data = true; $addon_ids = $fs->get_updated_account_addons(); $installed_addons = $fs->get_installed_addons(); foreach ( $installed_addons as $fs_addon ) { $addon_ids[] = $fs_addon->get_id(); } if ( ! empty( $addon_ids ) ) { $addon_ids = array_unique( $addon_ids ); } foreach ( $addon_ids as $addon_id ) { $addon = $fs->get_addon( $addon_id ); if ( ! is_object( $addon ) ) { continue; } $addon_storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $addon->slug ); if ( ! $addon_storage->is_whitelabeled ) { $hide_all_addons_data = false; break; } if ( $is_data_debug_mode ) { $is_whitelabeled = false; } } } ?> get_addon_basename( $addon->id ); $is_addon_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $basename ) ); if ( ! $is_addon_installed && $hide_all_addons_data ) { continue; } $is_addon_activated = $is_addon_installed ? $fs->is_addon_activated( $addon->id ) : false; $is_plugin_active = ( $is_addon_activated || isset( $active_plugins_directories_map[ dirname( $basename ) ] ) ); $open_addon = ( $open_addon || ( $open_addon_slug === $addon->slug ) ); $price = 0; $has_trial = false; $has_free_plan = false; $has_paid_plan = false; if ( isset( $plans_and_pricing_by_addon_id[$addon->id] ) ) { $plans = $plans_and_pricing_by_addon_id[$addon->id]; if ( is_array( $plans ) && 0 < count( $plans ) ) { foreach ( $plans as $plan ) { if ( ! isset( $plan->pricing ) || ! is_array( $plan->pricing ) || 0 == count( $plan->pricing ) ) { // No pricing means a free plan. $has_free_plan = true; continue; } $has_paid_plan = true; $has_trial = $has_trial || ( is_numeric( $plan->trial_period ) && ( $plan->trial_period > 0 ) ); $min_price = 999999; foreach ( $plan->pricing as $pricing ) { $pricing = new FS_Pricing( $pricing ); if ( ! $pricing->is_usd() ) { /** * Skip non-USD pricing. * * @author Leo Fajardo (@leorw) * @since 2.3.1 */ continue; } if ( $pricing->has_annual() ) { $min_price = min( $min_price, $pricing->annual_price ); } else if ( $pricing->has_monthly() ) { $min_price = min( $min_price, 12 * $pricing->monthly_price ); } } if ( $min_price < 999999 ) { $price = $min_price; } } } if ( ! $has_paid_plan && ! $has_free_plan ) { continue; } } ?>
    • get_id() . '&plugin=' . $addon->slug . '&TB_iframe=true&width=600&height=550' ) ), esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon->title ) ), esc_attr( $addon->title ) ) . ' class="thickbox%s">%s'; echo sprintf( $view_details_link, /** * Additional class. * * @author Leo Fajardo (@leorw) * @since 2.2.4 */ ' fs-overlay', /** * Set the view details link text to an empty string since it is an overlay that * doesn't really need a text and whose purpose is to open the details dialog when * the card is clicked. * * @author Leo Fajardo (@leorw) * @since 2.2.4 */ '' ); ?> info ) ) { $addon->info = new stdClass(); } if ( ! isset( $addon->info->card_banner_url ) ) { $addon->info->card_banner_url = '//dashboard.freemius.com/assets/img/marketing/blueprint-300x100.jpg'; } if ( ! isset( $addon->info->short_description ) ) { $addon->info->short_description = 'What\'s the one thing your add-on does really, really well?'; } ?>
      • %s', esc_html( $is_plugin_active ? fs_text_x_inline( 'Active', 'active add-on', 'active-addon', $slug ) : fs_text_x_inline( 'Installed', 'installed add-on', 'installed-addon', $slug ) ) ); } ?>
      • title ?>
      • 0) $descriptors[] = '$' . number_format( $price, 2 ); if ($has_trial) $descriptors[] = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); echo implode(' - ', $descriptors); } ?>
      • info->short_description ) ? $addon->info->short_description : 'SHORT DESCRIPTION' ?>
      • is_wp_org_compliant ); $is_allowed_to_install = ( $fs->is_allowed_to_install() || $is_free_only_wp_org_compliant ); $show_premium_activation_or_installation_action = true; if ( ! in_array( $addon->id, $account_addon_ids ) ) { $show_premium_activation_or_installation_action = false; } else if ( $is_addon_installed ) { /** * If any add-on's version (free or premium) is installed, check if the * premium version can be activated and show the relevant action. Otherwise, * show the relevant action for the free version. * * @author Leo Fajardo (@leorw) * @since 2.4.5 */ $fs_addon = $is_addon_activated ? $fs->get_addon_instance( $addon->id ) : null; $premium_plugin_basename = is_object( $fs_addon ) ? $fs_addon->premium_plugin_basename() : "{$addon->premium_slug}/{$addon->slug}.php"; if ( ( $is_addon_activated && $fs_addon->is_premium() ) || file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $premium_plugin_basename ) ) ) { $basename = $premium_plugin_basename; } $show_premium_activation_or_installation_action = ( ( ! $is_addon_activated || ! $fs_addon->is_premium() ) && /** * This check is needed for cases when an active add-on doesn't have an * associated Freemius instance. * * @author Leo Fajardo (@leorw) * @since 2.4.5 */ ( ! $is_plugin_active ) ); } ?>
      • _get_latest_download_local_url( $addon->id ); ?>
      • %s', wp_nonce_url( self_admin_url( 'update.php?' . ( ( $has_paid_plan || ! $addon->is_wp_org_compliant ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon->slug ), 'install-plugin_' . $addon->slug ), fs_esc_html_inline( 'Install Now', 'install-now', $slug ) ); } else { echo sprintf( '%s', wp_nonce_url( 'plugins.php?action=activate&plugin=' . $basename, 'activate-plugin_' . $basename ), fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $addon->slug ), fs_text_inline( 'Activate', 'activate', $addon->slug ) ); } ?>
    do_action( 'addons/after_addons' ) ?>
    _add_tabs_after_content(); } $params = array( 'page' => 'addons', 'module_id' => $fs->get_id(), 'module_type' => $fs->get_module_type(), 'module_slug' => $slug, 'module_version' => $fs->get_plugin_version(), ); fs_require_template( 'powered-by.php', $params );PK!Y--6freemius/templates/forms/subscription-cancellation.phpnu[get_slug(); /** * @var FS_Plugin_License $license */ $license = $VARS['license']; $has_trial = $VARS['has_trial']; $subscription_cancellation_context = $has_trial ? fs_text_inline( 'trial', 'trial', $slug ) : fs_text_inline( 'subscription', 'subscription', $slug ); $plan = $fs->get_plan(); $module_label = $fs->get_module_label( true ); if ( $VARS['is_license_deactivation'] ) { $subscription_cancellation_text = ''; } else { $subscription_cancellation_text = sprintf( fs_text_inline( "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.", 'deactivation-or-uninstall-message', $slug ), $module_label ) . ' '; } $subscription_cancellation_text .= sprintf( fs_text_inline( 'In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?', 'cancel-subscription-message', $slug ), ( $VARS['is_license_deactivation'] ? fs_text_inline( 'license', 'license', $slug ) : $module_label ), $subscription_cancellation_context ); $cancel_subscription_action_label = sprintf( fs_esc_html_inline( "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.", 'cancel-x', $slug ), esc_html( $subscription_cancellation_context ), sprintf( '%s', esc_html( $fs->get_plugin_title() ) ), esc_html( $module_label ) ); $keep_subscription_active_action_label = esc_html( sprintf( fs_text_inline( "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.", 'dont-cancel-x', $slug ), $subscription_cancellation_context ) ); $subscription_cancellation_text = esc_html( $subscription_cancellation_text ); $subscription_cancellation_html = <<< HTML

    {$subscription_cancellation_text}

    HTML; $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); $after_downgrade_blocking_text_premium_only = fs_text_inline( 'Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.', 'after-downgrade-blocking-premium-only', $slug ); $subscription_cancellation_confirmation_message = $has_trial ? fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ) : sprintf( '%s %s %s %s', sprintf( $downgrade_x_confirm_text, ($fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), $plan->title, human_time_diff( time(), strtotime( $license->expiration ) ) ), ( $license->is_block_features ? ( $fs->is_only_premium() ? sprintf( $after_downgrade_blocking_text_premium_only, $module_label ) : sprintf( $after_downgrade_blocking_text, $plan->title ) ) : sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) ), $prices_increase_text, fs_esc_attr_inline( 'Are you sure you want to proceed?', 'proceed-confirmation', $slug ) ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!Z<܋ =freemius/templates/forms/premium-versions-upgrade-handler.phpnu[get_slug(); $plugin_data = $fs->get_plugin_data(); $plugin_name = $plugin_data['Name']; $plugin_basename = $fs->get_plugin_basename(); $license = $fs->_get_license(); if ( ! is_object( $license ) ) { $purchase_url = $fs->pricing_url(); } else { $subscription = $fs->_get_subscription( $license->id ); $purchase_url = $fs->checkout_url( is_object( $subscription ) ? ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : WP_FS__PERIOD_LIFETIME, false, array( 'licenses' => $license->quota ) ); } $message = sprintf( fs_text_inline( 'There is a new version of %s available.', 'new-version-available-message', $slug ) . fs_text_inline( ' %s to access version %s security & feature updates, and support.', 'x-for-updates-and-support', $slug ), '', sprintf( '%s', is_object( $license ) ? fs_text_inline( 'Renew your license now', 'renew-license-now', $slug ) : fs_text_inline( 'Buy a license now', 'buy-license-now', $slug ) ), '' ); $modal_content_html = "

    {$message}

    "; $header_title = fs_text_inline( 'New Version Available', 'new-version-available', $slug ); $renew_license_button_text = is_object( $license ) ? fs_text_inline( 'Renew license', 'renew-license', $slug ) : fs_text_inline( 'Buy license', 'buy-license', $slug ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!U881freemius/templates/forms/email-address-update.phpnu[get_slug(); $user = $fs->get_user(); $current_email_address = $user->email; fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!tt(freemius/templates/forms/affiliation.phpnu[get_slug(); $user = $fs->get_user(); $affiliate = $fs->get_affiliate(); $affiliate_terms = $fs->get_affiliate_terms(); $module_type = $fs->is_plugin() ? WP_FS__MODULE_TYPE_PLUGIN : WP_FS__MODULE_TYPE_THEME; $commission = $affiliate_terms->get_formatted_commission(); $readonly = false; $is_affiliate = is_object( $affiliate ); $is_pending_affiliate = false; $email_address = ( is_object( $user ) ? $user->email : '' ); $full_name = ( is_object( $user ) ? $user->get_name() : '' ); $paypal_email_address = ''; $domain = ''; $extra_domains = array(); $promotion_method_social_media = false; $promotion_method_mobile_apps = false; $statistics_information = false; $promotion_method_description = false; $members_dashboard_login_url = 'https://users.freemius.com/login'; $affiliate_application_data = $fs->get_affiliate_application_data(); if ( $is_affiliate && $affiliate->is_pending() ) { $readonly = 'readonly'; $is_pending_affiliate = true; $paypal_email_address = $affiliate->paypal_email; $domain = $affiliate->domain; $statistics_information = $affiliate_application_data['stats_description']; $promotion_method_description = $affiliate_application_data['promotion_method_description']; if ( ! empty( $affiliate_application_data['additional_domains'] ) ) { $extra_domains = $affiliate_application_data['additional_domains']; } if ( ! empty( $affiliate_application_data['promotion_methods'] ) ) { $promotion_methods = explode( ',', $affiliate_application_data['promotion_methods'] ); $promotion_method_social_media = in_array( 'social_media', $promotion_methods ); $promotion_method_mobile_apps = in_array( 'mobile_apps', $promotion_methods ); } } else { $current_user = Freemius::_get_current_wp_user(); $full_name = trim( $current_user->user_firstname . ' ' . $current_user->user_lastname ); $email_address = $current_user->user_email; $domain = Freemius::get_unfiltered_site_url( null, true ); } $affiliate_tracking = 30; if ( is_object( $affiliate_terms ) ) { $affiliate_tracking = ( ! is_null( $affiliate_terms->cookie_days ) ? ( $affiliate_terms->cookie_days . '-day' ) : fs_text_inline( 'Non-expiring', 'non-expiring', $slug ) ); } $apply_to_become_affiliate_text = fs_text_inline( 'Apply to become an affiliate', 'apply-to-become-an-affiliate', $slug ); $module_id = $fs->get_id(); $affiliate_program_terms_url = "https://freemius.com/plugin/{$module_id}/{$slug}/legal/affiliate-program/"; $has_tabs = $fs->_add_tabs_before_content(); ?>
    is_active() ) : ?>

    %s', $members_dashboard_login_url, $members_dashboard_login_url ) ); ?>

    is_suspended() ) { $message_text = fs_text_inline( 'Your affiliation account was temporarily suspended.', 'affiliate-account-suspended', $slug ); $message_container_class = 'notice notice-warning'; } else if ( $affiliate->is_rejected() ) { $message_text = fs_text_inline( "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.", 'affiliate-application-rejected', $slug ); $message_container_class = 'error'; } else if ( $affiliate->is_blocked() ) { $message_text = fs_text_inline( 'Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.', 'affiliate-account-blocked', $slug ); $message_container_class = 'error'; } ?>

    • has_renewals_commission() ) : ?>
    • is_session_cookie() ) ) : ?>
    • has_lifetime_commission() ) : ?>
    >

    >
    >
    >
    >

    + ...
    >

    >
    />
    />

    _add_tabs_after_content(); } $params = array( 'page' => 'affiliation', 'module_id' => $module_id, 'module_slug' => $slug, 'module_version' => $fs->get_plugin_version(), ); fs_require_template( 'powered-by.php', $params ); PK!>freemius/templates/forms/premium-versions-upgrade-metadata.phpnu[_get_license(); if ( ! is_object( $license ) ) { $purchase_url = $fs->pricing_url(); } else { $subscription = $fs->_get_subscription( $license->id ); $purchase_url = $fs->checkout_url( is_object( $subscription ) ? ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) : WP_FS__PERIOD_LIFETIME, false, array( 'licenses' => $license->quota ) ); } $plugin_data = $fs->get_plugin_data(); ?> PK!|(freemius/templates/forms/trial-start.phpnu[get_slug(); $message_header = sprintf( /* translators: %1$s: Number of trial days; %2$s: Plan name; */ fs_text_inline( 'You are 1-click away from starting your %1$s-day free trial of the %2$s plan.', 'start-trial-prompt-header', $slug ), '', '' ); $message_content = sprintf( /* translators: %s: Link to freemius.com */ fs_text_inline( 'For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.', 'start-trial-prompt-message', $slug ), $fs->get_module_type(), sprintf( '%s', 'https://freemius.com', 'freemius.com' ) ); $modal_content_html = <<< HTML

    {$message_header}

    {$message_content}

    HTML; fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!p2WW"freemius/templates/forms/index.phpnu[get_slug(); $unique_affix = $fs->get_unique_affix(); $last_license_user_id = $fs->get_last_license_user_id(); $has_last_license_user_id = FS_User::is_valid_id( $last_license_user_id ); $message_above_input_field = ( ! $has_last_license_user_id ) ? fs_text_inline( 'Please enter the license key to enable the debug mode:', 'submit-developer-license-key-message', $slug ) : sprintf( fs_text_inline( 'To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:', 'submit-addon-developer-key-message', $slug ), $last_license_user_id ); $processing_text = ( fs_esc_js_inline( 'Processing', 'processing', $slug ) . '...' ); $submit_button_text = fs_text_inline( 'Submit', 'submit', $slug ); $debug_license_link_text = fs_esc_html_inline( 'Start Debug', 'start-debug-license', $slug ); $license_or_user_key_text = ( ! $has_last_license_user_id ) ? fs_text_inline( 'License key', 'license-key' , $slug ) : fs_text_inline( 'User key', 'user-key' , $slug ); $input_html = ""; $modal_content_html = <<< HTML

    {$message_above_input_field}

    {$input_html} HTML; fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!l$'freemius/templates/forms/resend-key.phpnu[get_slug(); $send_button_text = fs_text_inline( 'Send License Key', 'send-license-key', $slug ); $cancel_button_text = fs_text_inline( 'Cancel', 'cancel', $slug ); $email_address_placeholder = fs_esc_attr_inline( 'Email address', 'email-address', $slug ); $other_text = fs_text_inline( 'Other', 'other', $slug ); $is_freemium = $fs->is_freemium(); $send_button_text_html = esc_html($send_button_text); $button_html = <<< HTML HTML; if ( $is_freemium ) { $current_user = Freemius::_get_current_wp_user(); $email = $current_user->user_email; $esc_email = esc_attr( $email ); $form_html = <<< HTML {$button_html} HTML; } else { $email = ''; $form_html = <<< HTML {$button_html} HTML; } $message_above_input_field = $fs->is_only_premium() ? fs_esc_html_inline( "Enter the email address you've used during the purchase and we will resend you the license key.", 'ask-for-upgrade-email-address-premium-only', $slug ) : fs_esc_html_inline( "Enter the email address you've used for the upgrade below and we will resend you the license key.", 'ask-for-upgrade-email-address', $slug ); $modal_content_html = <<< HTML

    {$message_above_input_field}

    {$form_html}
    HTML; fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!+R=&&#freemius/templates/forms/optout.phpnu[get_slug(); $reconnect_url = $fs->get_activation_url( array( 'nonce' => wp_create_nonce( $fs->get_unique_affix() . '_reconnect' ), 'fs_action' => ( $fs->get_unique_affix() . '_reconnect' ), ) ); $plugin_title = "" . esc_html( $fs->get_plugin()->title ) . ""; $opt_out_text = fs_text_x_inline( 'Opt Out', 'verb', 'opt-out', $slug ); $permission_manager = FS_Permission_Manager::instance( $fs ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); fs_enqueue_local_style( 'fs_optout', '/admin/optout.css' ); fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); if ( ! $permission_manager->is_premium_context() ) { $optional_permissions = array( $permission_manager->get_extensions_permission( false, false, true ) ); $permission_groups = array( array( 'id' => 'communication', 'type' => 'required', 'title' => $fs->get_text_inline( 'Communication', 'communication' ), 'desc' => '', 'permissions' => $permission_manager->get_opt_in_required_permissions( true ), 'is_enabled' => $fs->is_registered(), 'prompt' => array( $fs->esc_html_inline( "Sharing your name and email allows us to keep you in the loop about new features and important updates, warn you about security issues before they become public knowledge, and send you special offers.", 'opt-out-message_user' ), sprintf( $fs->esc_html_inline( 'By clicking "Opt Out", %s will no longer be able to view your name and email.', 'opt-out-message-clicking-opt-out' ), $plugin_title ), ), 'prompt_cancel_label' => $fs->get_text_inline( 'Stay Connected', 'stay-connected' ) ), array( 'id' => 'diagnostic', 'type' => 'required', 'title' => $fs->get_text_inline( 'Diagnostic Info', 'diagnostic-info' ), 'desc' => '', 'permissions' => $permission_manager->get_opt_in_diagnostic_permissions( true ), 'is_enabled' => $fs->is_tracking_allowed(), 'prompt' => array( sprintf( $fs->esc_html_inline( 'Sharing diagnostic data helps to provide additional functionality that\'s relevant to your website, avoid WordPress or PHP version incompatibilities that can break the website, and recognize which languages & regions the %s should be translated and tailored to.', 'opt-out-message-clicking-opt-out' ), $fs->get_module_type() ), sprintf( $fs->esc_html_inline( 'By clicking "Opt Out", diagnostic data will no longer be sent to %s.', 'opt-out-message-clicking-opt-out' ), $plugin_title ), ), 'prompt_cancel_label' => $fs->get_text_inline( 'Keep Sharing', 'keep-sharing' ) ), array( 'id' => 'extensions', 'type' => 'optional', 'title' => $fs->get_text_inline( 'Extensions', 'extensions' ), 'desc' => '', 'permissions' => $optional_permissions, ), ); } else { $optional_permissions = $permission_manager->get_license_optional_permissions( false, true ); $permission_groups = array( array( 'id' => 'essentials', 'type' => 'required', 'title' => $fs->esc_html_inline( 'Required', 'required' ), 'desc' => sprintf( $fs->esc_html_inline( 'For automatic delivery of security & feature updates, and license management & protection, %s needs to:', 'license-sync-disclaimer' ), '' . esc_html( $fs->get_plugin_title() ) . '' ), 'permissions' => $permission_manager->get_license_required_permissions( true ), 'is_enabled' => $permission_manager->is_essentials_tracking_allowed(), 'prompt' => array( sprintf( $fs->esc_html_inline( 'To ensure that security & feature updates are automatically delivered directly to your WordPress Admin Dashboard while protecting your license from unauthorized abuse, %2$s needs to view the website’s homepage URL, %1$s version, SDK version, and whether the %1$s is active.', 'premium-opt-out-message-usage-tracking' ), $fs->get_module_type(), $plugin_title ), sprintf( $fs->esc_html_inline( 'By opting out from sharing this information with the updates server, you’ll have to check for new %1$s releases and manually download & install them. Not just a hassle, but missing an update can put your site at risk and cause undue compatibility issues, so we highly recommend keeping these essential permissions on.', 'opt-out-message-clicking-opt-out' ), $fs->get_module_type(), $plugin_title ), ), 'prompt_cancel_label' => $fs->get_text_inline( 'Keep automatic updates', 'premium-opt-out-cancel' ) ), array( 'id' => 'optional', 'type' => 'optional', 'title' => $fs->esc_html_inline( 'Optional', 'optional' ), 'desc' => sprintf( $fs->esc_html_inline( 'For ongoing compatibility with your website, you can optionally allow %s to:', 'optional-permissions-disclaimer' ), $plugin_title ), 'permissions' => $optional_permissions, ), ); } $ajax_action = 'toggle_permission_tracking'; $form_id = "fs_opt_out_{$fs->get_id()}"; ?> require_permissions_js( false ) ?> PK!_ٚXX/freemius/templates/forms/license-activation.phpnu[get_slug(); $unique_affix = $fs->get_unique_affix(); $cant_find_license_key_text = fs_text_inline( "Can't find your license key?", 'cant-find-license-key', $slug ); $message_above_input_field = fs_text_inline( 'Please enter the license key that you received in the email right after the purchase:', 'activate-license-message', $slug ); $message_below_input_field = ''; $header_title = $fs->is_free_plan() ? fs_text_inline( 'Activate License', 'activate-license', $slug ) : fs_text_inline( 'Update License', 'update-license', $slug ); if ( $fs->is_registered() ) { $activate_button_text = $header_title; } else { $message_below_input_field = sprintf( fs_text_inline( 'The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.', 'license-sync-disclaimer', $slug ), $fs->get_module_label( true ), "{$fs->get_plugin_title()}" ); $activate_button_text = fs_text_inline( 'Agree & Activate License', 'agree-activate-license', $slug ); } $license_key_text = fs_text_inline( 'License key', 'license-key' , $slug ); $is_network_activation = ( $fs->is_network_active() && fs_is_network_admin() && ! $fs->is_delegated_connection() ); $network_activation_html = ''; $sites_details = array(); if ( $is_network_activation ) { $all_sites = Freemius::get_sites(); $subsite_data_by_install_id = array(); $install_url_by_install_id = array(); foreach ( $all_sites as $site ) { $site_details = $fs->get_site_info( $site ); if ( FS_Clone_Manager::instance()->is_temporary_duplicate_by_blog_id( $site_details['blog_id'] ) ) { continue; } $blog_id = Freemius::get_site_blog_id( $site ); $install = $fs->get_install_by_blog_id($blog_id); if ( is_object( $install ) ) { if ( isset( $subsite_data_by_install_id[ $install->id ] ) ) { $clone_subsite_data = $subsite_data_by_install_id[ $install->id ]; $clone_install_url = $install_url_by_install_id[ $install->id ]; 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( $site_details['url'] ) ) ) { continue; } } if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) { $site_details['license_id'] = $install->license_id; } $subsite_data_by_install_id[ $install->id ] = $site_details; $install_url_by_install_id[ $install->id ] = $install->url; } } if ( $is_network_activation ) { $vars = array( 'id' => $fs->get_id(), 'sites' => array_values( $subsite_data_by_install_id ), 'require_license_key' => true ); $network_activation_html = fs_get_template( 'partials/network-activation.php', $vars ); } } $premium_licenses = $fs->get_available_premium_licenses(); $available_licenses = array(); foreach ( $premium_licenses as $premium_license ) { $activations_left = $premium_license->left(); if ( ! ( $activations_left > 0 ) ) { continue; } $available_licenses[ $activations_left . '_' . $premium_license->id ] = $premium_license; } $total_available_licenses = count( $available_licenses ); if ( $total_available_licenses > 0 ) { $license_input_html = <<< HTML
    HTML; if ( $total_available_licenses > 1 ) { // Sort the licenses by number of activations left in descending order. krsort( $available_licenses ); $license_input_html .= ''; } else { $available_licenses = array_values( $available_licenses ); /** * @var FS_Plugin_License $available_license */ $available_license = $available_licenses[0]; $value = sprintf( "%s-Site %s License - %s", ( 1 == $available_license->quota ? 'Single' : ( $available_license->is_unlimited() ? 'Unlimited' : $available_license->quota ) ), $fs->_get_plan_by_id( $available_license->plan_id )->title, $available_license->get_html_escaped_masked_secret_key() ); $license_input_html .= <<< HTML HTML; } $license_input_html .= <<< HTML
    HTML; } else { $license_input_html = ""; } $ownership_change_option_text = fs_text_inline( "Associate with the license owner's account.", 'associate-account-with-license-owner', $slug ); $ownership_change_option_html = ""; /** * IMPORTANT: * DO NOT ADD MAXLENGTH OR LIMIT THE LICENSE KEY LENGTH SINCE * WE DO WANT TO ALLOW INPUT OF LONGER KEYS (E.G. WooCommerce Keys) * FOR MIGRATED MODULES. */ $modal_content_html = <<< HTML

    {$message_above_input_field}

    {$license_input_html} {$cant_find_license_key_text} {$network_activation_html}

    {$message_below_input_field}

    {$ownership_change_option_html} HTML; /** * Handle the ownership change option if not an add-on or if no license yet is activated for the * parent product in case of an add-on. * * @author Leo Fajardo (@leorw) * @since 2.3.2 */ $is_user_change_supported = ( ! $fs->is_addon() || ! $fs->get_parent_instance()->has_active_valid_license() ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> get_slug(); echo fs_text_inline( 'Sorry for the inconvenience and we are here to help if you give us a chance.', 'contact-support-before-deactivation', $slug ) . sprintf(" %s", $fs->contact_url( 'technical_support' ), fs_text_inline( 'Contact Support', 'contact-support', $slug ) ); PK!˙4freemius/templates/forms/deactivation/retry-skip.phpnu[get_slug(); $skip_url = fs_nonce_url( $fs->_get_admin_page_url( '', array( 'fs_action' => $fs->get_unique_affix() . '_skip_activation' ) ), $fs->get_unique_affix() . '_skip_activation' ); $skip_text = strtolower( fs_text_x_inline( 'Skip', 'verb', 'skip', $slug ) ); $use_plugin_anonymously_text = fs_text_inline( 'Click here to use the plugin anonymously', 'click-here-to-use-plugin-anonymously', $slug ); echo sprintf( fs_text_inline( "You might have missed it, but you don't have to share any data and can just %s the opt-in.", 'dont-have-to-share-any-data', $slug ), "{$skip_text}" ) . " {$use_plugin_anonymously_text}";PK!)Yee.freemius/templates/forms/deactivation/form.phpnu[get_slug(); $subscription_cancellation_dialog_box_template_params = $VARS['subscription_cancellation_dialog_box_template_params']; $show_deactivation_feedback_form = $VARS['show_deactivation_feedback_form']; $confirmation_message = $VARS['uninstall_confirmation_message']; $is_anonymous = ( ! $fs->is_registered() ); $anonymous_feedback_checkbox_html = ''; $reasons_list_items_html = ''; $snooze_select_html = ''; if ( $show_deactivation_feedback_form ) { $reasons = $VARS['reasons']; foreach ( $reasons as $reason ) { $list_item_classes = 'reason' . ( ! empty( $reason['input_type'] ) ? ' has-input' : '' ); if ( isset( $reason['internal_message'] ) && ! empty( $reason['internal_message'] ) ) { $list_item_classes .= ' has-internal-message'; $reason_internal_message = $reason['internal_message']; } else { $reason_internal_message = ''; } $reason_input_type = ( ! empty( $reason['input_type'] ) ? $reason['input_type'] : '' ); $reason_input_placeholder = ( ! empty( $reason['input_placeholder'] ) ? $reason['input_placeholder'] : '' ); $reason_list_item_html = <<< HTML
  • {$reason_internal_message}
  • HTML; $reasons_list_items_html .= $reason_list_item_html; } if ( $is_anonymous ) { $anonymous_feedback_checkbox_html = sprintf( '', fs_esc_html_inline( 'Anonymous feedback', 'anonymous-feedback', $slug ) ); } $snooze_periods = array( array( 'increment' => fs_text_inline( 'hour', $slug ), 'quantity' => number_format_i18n(1), 'value' => 6 * WP_FS__TIME_10_MIN_IN_SEC, ), array( 'increment' => fs_text_inline( 'hours', $slug ), 'quantity' => number_format_i18n(24), 'value' => WP_FS__TIME_24_HOURS_IN_SEC, ), array( 'increment' => fs_text_inline( 'days', $slug ), 'quantity' => number_format_i18n(7), 'value' => WP_FS__TIME_WEEK_IN_SEC, ), array( 'increment' => fs_text_inline( 'days', $slug ), 'quantity' => number_format_i18n(30), 'value' => 30 * WP_FS__TIME_24_HOURS_IN_SEC, ), ); $snooze_select_html = ''; } // Aliases. $deactivate_text = fs_text_inline( 'Deactivate', 'deactivate', $slug ); $theme_text = fs_text_inline( 'Theme', 'theme', $slug ); $activate_x_text = fs_text_inline( 'Activate %s', 'activate-x', $slug ); $submit_deactivate_text = sprintf( fs_text_inline( 'Submit & %s', 'deactivation-modal-button-submit', $slug ), $fs->is_plugin() ? $deactivate_text : sprintf( $activate_x_text, $theme_text ) ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); } ?> PK!p2WW/freemius/templates/forms/deactivation/index.phpnu[get_slug(); /** * @var object[] $license_owners */ $license_owners = $VARS['license_owners']; $change_user_message = fs_text_inline( 'By changing the user, you agree to transfer the account ownership to:', 'change-user--message', $slug ); $header_title = fs_text_inline( 'Change User', 'change-user', $slug ); $user_change_button_text = fs_text_inline( 'I Agree - Change User', 'agree-change-user', $slug ); $other_text = fs_text_inline( 'Other', 'other', $slug ); $enter_email_address_placeholder_text = fs_text_inline( 'Enter email address', 'enter-email-address', $slug ); $user_change_options_html = <<< HTML
    HTML; foreach ( $license_owners as $license_owner ) { $user_change_options_html .= <<< HTML HTML; } $user_change_options_html .= <<< HTML
    HTML; $modal_content_html = <<< HTML

    {$change_user_message}

    {$user_change_options_html} HTML; fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); ?> PK!Rf.freemius/templates/plugin-info/screenshots.phpnu[
      $url ) : ?>
    PK!p2WW(freemius/templates/plugin-info/index.phpnu[features) && is_array($plan->features)) { foreach ( $plan->features as $feature ) { if ( ! isset( $features_plan_map[ $feature->id ] ) ) { $features_plan_map[ $feature->id ] = array( 'feature' => $feature, 'plans' => array() ); } $features_plan_map[ $feature->id ]['plans'][ $plan->id ] = $feature; } } // Add support as a feature. if ( ! empty( $plan->support_email ) || ! empty( $plan->support_skype ) || ! empty( $plan->support_phone ) || true === $plan->is_success_manager ) { if ( ! isset( $features_plan_map['support'] ) ) { $support_feature = new stdClass(); $support_feature->id = 'support'; $support_feature->title = fs_text_inline( 'Support', $plugin->slug ); $features_plan_map[ $support_feature->id ] = array( 'feature' => $support_feature, 'plans' => array() ); } else { $support_feature = $features_plan_map['support']['feature']; } $features_plan_map[ $support_feature->id ]['plans'][ $plan->id ] = $support_feature; } } // Add updates as a feature for all plans. $updates_feature = new stdClass(); $updates_feature->id = 'updates'; $updates_feature->title = fs_text_inline( 'Unlimited Updates', 'unlimited-updates', $plugin->slug ); $features_plan_map[ $updates_feature->id ] = array( 'feature' => $updates_feature, 'plans' => array() ); foreach ( $plans as $plan ) { $features_plan_map[ $updates_feature->id ]['plans'][ $plan->id ] = $updates_feature; } ?>
    $data ) : ?>
    title ?> pricing ) ) { fs_esc_html_echo_inline( 'Free', 'free', $plugin->slug ); } else { foreach ( $plan->pricing as $pricing ) { /** * @var FS_Pricing $pricing */ if ( 1 == $pricing->licenses ) { if ( $pricing->has_annual() ) { echo "\${$pricing->annual_price} / " . fs_esc_html_x_inline( 'year', 'as annual period', 'year', $plugin->slug ); } else if ( $pricing->has_monthly() ) { echo "\${$pricing->monthly_price} / " . fs_esc_html_x_inline( 'mo', 'as monthly period', 'mo', $plugin->slug ); } else { echo "\${$pricing->lifetime_price}"; } } } } ?>
    title ) ) ?> id ] ) ) : ?> id ]->value ) ) : ?> id ]->value ) ?>
    PK!.freemius/templates/plugin-info/description.phpnu[info->selling_point_0 ) || ! empty( $plugin->info->selling_point_1 ) || ! empty( $plugin->info->selling_point_2 ) ) : ?>
      info->{'selling_point_' . $i} ) ) : ?>
    • info->{'selling_point_' . $i} ) ?>

    info->description, array( 'a' => array( 'href' => array(), 'title' => array(), 'target' => array() ), 'b' => array(), 'i' => array(), 'p' => array(), 'blockquote' => array(), 'h2' => array(), 'h3' => array(), 'ul' => array(), 'ol' => array(), 'li' => array() ) ); ?>
    info->screenshots ) ) : ?> info->screenshots ?>

    slug ) ?>

      $url ) : ?>
    PK!i/"freemius/templates/ajax-loader.phpnu[ PK!p2WWfreemius/templates/index.phpnu[
    PK!Y##freemius/templates/connect.phpnu[get_slug(); $is_pending_activation = $fs->is_pending_activation(); $is_premium_only = $fs->is_only_premium(); $has_paid_plans = $fs->has_paid_plan(); $is_premium_code = $fs->is_premium(); $is_freemium = $fs->is_freemium(); $fs->_enqueue_connect_essentials(); /** * Enqueueing the styles in `_enqueue_connect_essentials()` is too late, as we need them in the HEADER. Therefore, inject the styles inline to avoid FOUC. * * @author Vova Feldman (@svovaf) */ echo "\n"; $current_user = Freemius::_get_current_wp_user(); $first_name = $current_user->user_firstname; if ( empty( $first_name ) ) { $first_name = $current_user->nickname; } $site_url = Freemius::get_unfiltered_site_url(); $protocol_pos = strpos( $site_url, '://' ); if ( false !== $protocol_pos ) { $site_url = substr( $site_url, $protocol_pos + 3 ); } $freemius_usage_tracking_url = $fs->get_usage_tracking_terms_url(); $freemius_plugin_terms_url = $fs->get_eula_url(); $error = fs_request_get( 'error' ); $has_release_on_freemius = $fs->has_release_on_freemius(); $require_license_key = $is_premium_only || ( $is_freemium && ( $is_premium_code || ! $has_release_on_freemius ) && fs_request_get_bool( 'require_license', ( $is_premium_code || $has_release_on_freemius ) ) ); $freemius_activation_terms_url = ($fs->is_premium() && $require_license_key) ? $fs->get_license_activation_terms_url() : $freemius_usage_tracking_url; $freemius_activation_terms_html = 'freemius.com'; if ( $is_pending_activation ) { $require_license_key = false; } if ( $require_license_key ) { $fs->_add_license_activation_dialog_box(); } $is_optin_dialog = ( $fs->is_theme() && $fs->is_themes_page() && $fs->show_opt_in_on_themes_page() ); if ( $is_optin_dialog ) { $show_close_button = false; $previous_theme_activation_url = ''; if ( ! $is_premium_code ) { $show_close_button = true; } else if ( $is_premium_only ) { $previous_theme_activation_url = $fs->get_previous_theme_activation_url(); $show_close_button = ( ! empty( $previous_theme_activation_url ) ); } } $is_network_level_activation = ( fs_is_network_admin() && $fs->is_network_active() && ! $fs->is_network_delegated_connection() ); $fs_user = Freemius::_get_user_by_email( $current_user->user_email ); $activate_with_current_user = ( is_object( $fs_user ) && ! $is_pending_activation && // If requires a license for activation, use the user associated with the license for the opt-in. ! $require_license_key && ! $is_network_level_activation ); $optin_params = $fs->get_opt_in_params( array(), $is_network_level_activation ); $sites = isset( $optin_params['sites'] ) ? $optin_params['sites'] : array(); $is_network_upgrade_mode = ( fs_is_network_admin() && $fs->is_network_upgrade_mode() ); /* translators: %s: name (e.g. Hey John,) */ $hey_x_text = esc_html( sprintf( fs_text_x_inline( 'Hey %s,', 'greeting', 'hey-x', $slug ), $first_name ) ); $activation_state = array( 'is_license_activation' => $require_license_key, 'is_pending_activation' => $is_pending_activation, 'is_gdpr_required' => true, 'is_network_level_activation' => $is_network_level_activation, 'is_dialog' => $is_optin_dialog, ); ?>
    do_action( 'connect/before', $activation_state ); ?>
    $fs->get_id(), 'size' => $size, ); fs_require_once_template( 'plugin-icon.php', $vars ); ?>
    do_action( 'connect/before_message', $activation_state ) ?>
    apply_filters( 'connect_error_esc_html', esc_html( $error ) ) ?>
    is_plugin_update() ) { echo $fs->apply_filters( 'connect-header', sprintf( '

    %s

    ', esc_html( fs_text_inline( 'Never miss an important update', 'connect-header' ) ) ) ); } else { echo $fs->apply_filters( 'connect-header_on-update', sprintf( '

    %s

    ', sprintf( esc_html( /* translators: %1$s: plugin name (e.g., "Awesome Plugin"); %2$s: version (e.g., "1.2.3") */ fs_text_inline('Thank you for updating to %1$s v%2$s!', 'connect-header_on-update' ) ), esc_html( $fs->get_plugin_name() ), $fs->get_plugin_version() ) ) ); } } ?>

    apply_filters( 'pending_activation_message', sprintf( /* translators: %s: name (e.g. Thanks John!) */ fs_text_inline( 'Thanks %s!', 'thanks-x', $slug ) . '
    ' . fs_text_inline( 'You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.', 'pending-activation-message', $slug ), $first_name, '' . $fs->get_plugin_name() . '', '' . $current_user->user_email . '', fs_text_inline( 'complete the opt-in', 'complete-the-opt-in', $slug ) ) ); } else if ( $require_license_key ) { $button_label = fs_text_inline( 'Activate License', 'activate-license', $slug ); $message = $fs->apply_filters( 'connect-message_on-premium', sprintf( fs_text_inline( 'Welcome to %s! To get started, please enter your license key:', 'thanks-for-purchasing', $slug ), '' . $fs->get_plugin_name() . '' ), $first_name, $fs->get_plugin_name() ); } else { $filter = 'connect_message'; if ( ! $fs->is_plugin_update() ) { $default_optin_message = esc_html( sprintf( /* translators: %s: module type (plugin, theme, or add-on) */ fs_text_inline( 'Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message', $slug ), $fs->get_module_label( true ) ) ); } else { // If Freemius was added on a plugin update, set different // opt-in message. /* translators: %s: module type (plugin, theme, or add-on) */ $default_optin_message = esc_html( sprintf( fs_text_inline( 'We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.', 'connect-message_on-update_why' ), $fs->get_module_label( true ) ) ); $default_optin_message .= '

    ' . esc_html( fs_text_inline( 'Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.', 'connect-message_on-update', $slug ) ); if ( $fs->is_enable_anonymous() ) { $default_optin_message .= ' ' . esc_html( fs_text_inline( 'If you skip this, that\'s okay! %1$s will still work just fine.', 'connect-message_on-update_skip', $slug ) ); } // If user customized the opt-in message on update, use // that message. Otherwise, fallback to regular opt-in // custom message if exists. if ( $fs->has_filter( 'connect_message_on_update' ) ) { $filter = 'connect_message_on_update'; } } $message = $fs->apply_filters( $filter, sprintf( $default_optin_message, '' . esc_html( $fs->get_plugin_name() ) . '', '' . $current_user->user_login . '', '' . $site_url . '', $freemius_activation_terms_html ), $first_name, $fs->get_plugin_name(), $current_user->user_login, '' . $site_url . '', $freemius_activation_terms_html, true ); } if ( $is_network_upgrade_mode ) { $network_integration_text = esc_html( fs_text_inline( 'We\'re excited to introduce the Freemius network-level integration.', 'connect_message_network_upgrade', $slug ) ); if ($is_premium_code){ $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %d site(s) that are still pending license activation.', 'connect_message_network_upgrade-premium', $slug ), count( $sites ) ); $message .= '

    ' . sprintf( fs_text_inline( 'If you\'d like to use the %s on those sites, please enter your license key below and click the activation button.', 'connect_message_network_upgrade-premium-activate-license', $slug ), $is_premium_only ? $fs->get_module_label( true ) : sprintf( /* translators: %s: module type (plugin, theme, or add-on) */ fs_text_inline( "%s's paid features", 'x-paid-features', $slug ), $fs->get_module_label( true ) ) ); /* translators: %s: module type (plugin, theme, or add-on) */ $message .= ' ' . sprintf( fs_text_inline( 'Alternatively, you can skip it for now and activate the license later, in your %s\'s network-level Account page.', 'connect_message_network_upgrade-premium-skip-license', $slug ), $fs->get_module_label( true ) ); }else { $message = $network_integration_text . ' ' . sprintf( fs_text_inline( 'During the update process we detected %s site(s) in the network that are still pending your attention.', 'connect_message_network_upgrade-free', $slug ), count( $sites ) ) . '

    ' . ( fs_starts_with( $message, $hey_x_text . '
    ' ) ? substr( $message, strlen( $hey_x_text . '
    ' ) ) : $message ); } } echo $message; ?>

    do_action( 'connect/after_license_input', $activation_state ); ?> - %s', $fs->get_text_inline( 'Yes', 'yes' ), $fs->get_text_inline( 'send me security & feature updates, educational content and offers.', 'send-updates' ) ); $do_not_send_updates_text = sprintf( '%s - %s', $fs->get_text_inline( 'No', 'no' ), sprintf( $fs->get_text_inline( 'do %sNOT%s send me security & feature updates, educational content and offers.', 'do-not-send-updates' ), '', '' ) ); ?>
    $fs->get_id(), 'sites' => $sites, 'require_license_key' => $require_license_key ); echo fs_get_template( 'partials/network-activation.php', $vars ); ?> do_action( 'connect/after_message', $activation_state ) ?>
    do_action( 'connect/before_actions', $activation_state ) ?> is_enable_anonymous() && ! $is_pending_activation && ( ! $require_license_key || $is_network_upgrade_mode ) ) : ?> apply_filters( 'show_delegation_option', true ) ) : ?>
    get_unique_affix() . '_activate_existing' ) ?>
    $value ) : ?>
    do_action( 'connect/after_actions', $activation_state ) ?>
    is_permission_requested( 'newsletter' ) ) { $permissions[] = $permission_manager->get_newsletter_permission(); } $permissions = $permission_manager->get_permissions( $require_license_key, $permissions ); if ( ! empty( $permissions ) ) : ?>

    do_action( 'connect/after', $activation_state ); if ( $is_optin_dialog ) { ?>

    newest->version ?>

    get_option( 'ms_migration_complete', false, true ) ) : ?>
    Resolve Clone(s)
    'WP_FS__REMOTE_ADDR', 'val' => WP_FS__REMOTE_ADDR, ), array( 'key' => 'WP_FS__ADDRESS_PRODUCTION', 'val' => WP_FS__ADDRESS_PRODUCTION, ), array( 'key' => 'FS_API__ADDRESS', 'val' => FS_API__ADDRESS, ), array( 'key' => 'FS_API__SANDBOX_ADDRESS', 'val' => FS_API__SANDBOX_ADDRESS, ), array( 'key' => 'WP_FS__DIR', 'val' => WP_FS__DIR, ), array( 'key' => 'wp_using_ext_object_cache()', 'val' => wp_using_ext_object_cache() ? 'true' : 'false', ), ) ?>
    >

    plugins as $sdk_path => $data ) : ?> version ) ?> >
    version ?> plugin_path ?>
    get_option( $module_type . 's' ), FS_Plugin::get_class_name() ) ?> 0 ) : ?>

    $data ) : ?> file ); } else { $current_theme = wp_get_theme(); $is_active = ( $current_theme->stylesheet === $data->file ); if ( ! $is_active && is_child_theme() ) { $parent_theme = $current_theme->parent(); $is_active = ( ( $parent_theme instanceof WP_Theme ) && $parent_theme->stylesheet === $data->file ); } } ?> id ); $active_modules_by_id[ $data->id ] = true; } ?> has_api_connectivity(); if ( true === $has_api_connectivity && $fs->is_on() ) { echo ' style="background: #E6FFE6; font-weight: bold"'; } else { echo ' style="background: #ffd0d0; font-weight: bold"'; } } ?>> > is_on() ) { echo ' style="color: red; text-transform: uppercase;"'; } ?>>is_on() ? $on_text : $off_text ); } ?> get_network_install_blog_id(); $network_user = $fs->get_network_user(); } ?>
    id ?> version ?> title ?> file ?> public_key ?> email; } ?> has_trial_plan() ) : ?>
    is_registered() ) : ?> is_network_upgrade_mode() ) : ?>
    0 ) : ?>

    /

    $sites ) : ?> blog_id : null; if ( is_null( $site_url ) || $is_multisite ) { $site_url = Freemius::get_unfiltered_site_url( $blog_id, true, true ); } $is_active_clone = ( $site->is_clone( $site_url ) && isset( $active_modules_by_id[ $site->plugin_id ] ) ); if ( $is_active_clone ) { $has_any_active_clone = true; } ?>
    id ?> url ) ?> user_id ?> license_id) ? $site->license_id : '' ?> plan_id ) ) { if ( false === $all_plans ) { $option_name = 'plans'; if ( WP_FS__MODULE_TYPE_PLUGIN !== $module_type ) { $option_name = $module_type . '_' . $option_name; } $all_plans = fs_get_entities( $fs_options->get_option( $option_name, array() ), FS_Plugin_Plan::get_class_name() ); } foreach ( $all_plans[ $slug ] as $plan ) { $plan_id = Freemius::_decrypt( $plan->id ); if ( $site->plan_id == $plan_id ) { $plan_name = Freemius::_decrypt( $plan->name ); break; } } } echo $plan_name; ?> public_key ?> is_whitelabeled ? FS_Plugin_License::mask_secret_key_for_html( $site->secret_key ) : esc_html( $site->secret_key ); ?>
    $plugin_addons ) : ?>

    id ?> title ?> slug ?> version ?> public_key ?> secret_key ) ?>
    id ] = true; } } foreach ( $module_types as $module_type ) { /** * @var FS_Plugin_License[] $licenses */ $licenses = $VARS[ $module_type . '_licenses' ]; foreach ( $licenses as $license ) { if ( $license->is_whitelabeled ) { $users_with_developer_license_by_id[ $license->user_id ] = true; } } } ?>

    $user ) : ?>
    id ?> get_name() ?> email ?> is_verified ) ?> public_key ?> secret_key) : esc_html( $user->secret_key ) ?>
    0 ) : ?>

    id ?> plugin_id ?> user_id ?> plan_id ?> is_unlimited() ? 'Unlimited' : ( $license->is_single_site() ? 'Single Site' : $license->quota ) ?> activated ?> is_block_features ? 'Blocking' : 'Flexible' ?> is_whitelabeled ? 'Whitelabeled' : 'Normal' ?> is_whitelabeled || ! isset( $user_ids_map[ $license->user_id ] ) ) ? $license->get_html_escaped_masked_secret_key() : esc_html( $license->secret_key ); ?> expiration ?>

    #
    {$log.log_order}. {$log.type} {$log.logger} {$log.function} {$log.message_short}
    {$log.message}
    {$log.file}:{$log.line} {$log.created}
    PK!f7__freemius/templates/account.phpnu[get_slug(); /** * @var FS_Plugin_Tag $update */ $update = $fs->has_release_on_freemius() ? $fs->get_update( false, false, WP_FS__TIME_24_HOURS_IN_SEC / 24 ) : null; if ( is_object($update) ) { /** * This logic is particularly required for multisite environment. * If a module is site activated (not network) and not on the main site, * the module will NOT be executed on the network level, therefore, the * custom updates logic will not be executed as well, so unless we force * the injection of the update into the updates transient, premium updates * will not work. * * @author Vova Feldman (@svovaf) * @since 2.0.0 */ $updater = FS_Plugin_Updater::instance( $fs ); $updater->set_update_data( $update ); } $is_paying = $fs->is_paying(); $user = $fs->get_user(); $site = $fs->get_site(); $name = $user->get_name(); $license = $fs->_get_license(); $is_license_foreign = ( is_object( $license ) && $user->id != $license->user_id ); $is_data_debug_mode = $fs->is_data_debug_mode(); $is_whitelabeled = $fs->is_whitelabeled(); $subscription = ( is_object( $license ) ? $fs->_get_subscription( $license->id ) : null ); $plan = $fs->get_plan(); $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); $is_paid_trial = $fs->is_paid_trial(); $has_paid_plan = $fs->apply_filters( 'has_paid_plan_account', $fs->has_paid_plan() ); $show_upgrade = ( ! $is_whitelabeled && $has_paid_plan && ! $is_paying && ! $is_paid_trial ); $trial_plan = $fs->get_trial_plan(); $is_plan_change_supported = ( ! $fs->is_single_plan() && ! $fs->apply_filters( 'hide_plan_change', false ) ); if ( $has_paid_plan ) { $fs->_add_license_activation_dialog_box(); } if ( $fs->should_handle_user_change() ) { $fs->_add_email_address_update_dialog_box(); } $ids_of_installs_activated_with_foreign_licenses = $fs->should_handle_user_change() ? $fs->get_installs_ids_with_foreign_licenses() : array(); if ( ! empty( $ids_of_installs_activated_with_foreign_licenses ) ) { $fs->_add_user_change_dialog_box( $ids_of_installs_activated_with_foreign_licenses ); } if ( $fs->is_whitelabeled( true ) || $fs->is_data_debug_mode() ) { $fs->_add_data_debug_mode_dialog_box(); } if ( fs_request_get_bool( 'auto_install' ) ) { $fs->_add_auto_installation_dialog_box(); } if ( fs_request_get_bool( 'activate_license' ) ) { // Open the license activation dialog box on the account page. add_action( 'admin_footer', array( &$fs, '_open_license_activation_dialog_box' ) ); } $show_billing = ( ! $is_whitelabeled && ! $fs->apply_filters( 'hide_billing_and_payments_info', false ) ); if ( $show_billing ) { $payments = $fs->_fetch_payments(); $show_billing = ( is_array( $payments ) && 0 < count( $payments ) ); } $has_tabs = $fs->_add_tabs_before_content(); // Aliases. $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.', 'downgrade-x-confirm', $slug ); $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); /* translators: %s: Plan title (e.g. "Professional") */ $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); /* translators: %s: Time period (e.g. Auto renews in "2 months") */ $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); /* translators: %s: Time period (e.g. Expires in "2 months") */ $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); $sync_license_text = fs_text_x_inline( 'Sync License', 'as synchronize license', 'sync-license', $slug ); $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); $free_text = fs_text_inline( 'Free', 'free', $slug ); $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); $bundle_plan_text = fs_text_inline( 'Bundle Plan', 'bundle-plan', $slug ); $show_plan_row = true; $show_license_row = is_object( $license ); $site_view_params = array(); if ( fs_is_network_admin() ) { $sites = Freemius::get_sites(); $all_installs_plan_id = null; $all_installs_license_id = ( $show_license_row ? $license->id : null ); foreach ( $sites as $s ) { $site_info = $fs->get_site_info( $s ); $install = $fs->get_install_by_blog_id( $site_info['blog_id'] ); $view_params = array( 'freemius' => $fs, 'user' => $fs->get_user(), 'license' => $license, 'site' => $site_info, 'install' => $install, ); $site_view_params[] = $view_params; if ( empty( $install ) ) { continue; } if ( $show_plan_row ) { if ( is_null( $all_installs_plan_id ) ) { $all_installs_plan_id = $install->plan_id; } else if ( $all_installs_plan_id != $install->plan_id ) { $show_plan_row = false; } } if ( $show_license_row && $all_installs_license_id != $install->license_id ) { $show_license_row = false; } } } $has_bundle_license = false; if ( is_object( $license ) && FS_Plugin_License::is_valid_id( $license->parent_license_id ) ) { // Context license has a parent license, therefore, the account has a bundle license. $has_bundle_license = true; } $bundle_subscription = null; $is_bundle_first_payment_pending = false; if ( $show_plan_row && is_object( $license ) && $has_bundle_license ) { $bundle_plan_title = strtoupper( $license->parent_plan_title ); $bundle_subscription = $fs->_get_subscription( $license->parent_license_id ); $is_bundle_first_payment_pending = $license->is_first_payment_pending(); } $fs_blog_id = ( is_multisite() && ! is_network_admin() ) ? get_current_blog_id() : 0; $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $fs_blog_id ); $is_premium = $fs->is_premium(); $account_addons = $fs->get_updated_account_addons(); $installed_addons = $fs->get_installed_addons(); $installed_addons_ids = array(); /** * Store the installed add-ons' IDs into a collection which will be used in determining the add-ons to show on the "Account" page, and at the same time try to find an add-on that is activated with a bundle license if the core product is not. * * @author Leo Fajardo * * @since 2.4.0 */ foreach ( $installed_addons as $fs_addon ) { $installed_addons_ids[] = $fs_addon->get_id(); if ( $has_bundle_license ) { // We already have the context bundle license details, skip. continue; } if ( $show_plan_row && $fs_addon->has_active_valid_license() ) { $addon_license = $fs_addon->_get_license(); if ( FS_Plugin_License::is_valid_id( $addon_license->parent_license_id ) ) { // Add-on's license is associated with a parent/bundle license. $has_bundle_license = true; $bundle_plan_title = strtoupper( $addon_license->parent_plan_title ); $bundle_subscription = $fs_addon->_get_subscription( $addon_license->parent_license_id ); $is_bundle_first_payment_pending = $addon_license->is_first_payment_pending(); } } } $addons_to_show = array_unique( array_merge( $installed_addons_ids, $account_addons ) ); $is_active_bundle_subscription = ( is_object( $bundle_subscription ) && $bundle_subscription->is_active() ); $available_license = ( $fs->is_free_plan() && ! fs_is_network_admin() ) ? $fs->_get_available_premium_license( $site->is_localhost() ) : null; $available_license_paid_plan = is_object( $available_license ) ? $fs->_get_plan_by_id( $available_license->plan_id ) : null; ?>
    apply_filters( 'hide_account_tabs', false ) ) : ?>

    apply_filters( 'hide_license_key', false ) ); $profile = array(); if ( ! $is_whitelabeled ) { $profile[] = array( 'id' => 'user_name', 'title' => fs_text_inline( 'Name', 'name', $slug ), 'value' => $name ); // if (isset($user->email) && false !== strpos($user->email, '@')) $profile[] = array( 'id' => 'email', 'title' => fs_text_inline( 'Email', 'email', $slug ), 'value' => $user->email ); if ( is_numeric( $user->id ) ) { $profile[] = array( 'id' => 'user_id', 'title' => fs_text_inline( 'User ID', 'user-id', $slug ), 'value' => $user->id ); } } $profile[] = array( 'id' => 'product', 'title' => ( $fs->is_plugin() ? fs_text_inline( 'Plugin', 'plugin', $slug ) : fs_text_inline( 'Theme', 'theme', $slug ) ), 'value' => $fs->get_plugin_title() ); $profile[] = array( 'id' => 'product_id', 'title' => ( $fs->is_plugin() ? fs_text_inline( 'Plugin', 'plugin', $slug ) : fs_text_inline( 'Theme', 'theme', $slug ) ) . ' ' . fs_text_inline( 'ID', 'id', $slug ), 'value' => $fs->get_id() ); if ( ! fs_is_network_admin()) { $profile[] = array( 'id' => 'site_id', 'title' => fs_text_inline( 'Site ID', 'site-id', $slug ), 'value' => is_string( $site->id ) ? $site->id : fs_text_inline( 'No ID', 'no-id', $slug ) ); $profile[] = array( 'id' => 'site_public_key', 'title' => fs_text_inline( 'Public Key', 'public-key', $slug ), 'value' => $site->public_key ); $profile[] = array( 'id' => 'site_secret_key', 'title' => fs_text_inline( 'Secret Key', 'secret-key', $slug ), 'value' => ( ( is_string( $site->secret_key ) ) ? $site->secret_key : fs_text_x_inline( 'No Secret', 'as secret encryption key missing', 'no-secret', $slug ) ) ); } $profile[] = array( 'id' => 'version', 'title' => $version_text, 'value' => $fs->get_plugin_version() ); if ( ! fs_is_network_admin() && $is_premium ) { $profile[] = array( 'id' => 'beta_program', 'title' => '', 'value' => $site->is_beta ); } if ( $has_paid_plan || $has_bundle_license ) { if ( $fs->is_trial() ) { if ( $show_plan_row ) { $profile[] = array( 'id' => 'plan', 'title' => $plan_text, 'value' => ( is_string( $trial_plan->name ) ? strtoupper( $trial_plan->title ) : fs_text_inline( 'Trial', 'trial', $slug ) ) ); } } else { if ( $show_plan_row ) { $profile[] = array( 'id' => 'plan', 'title' => ( $has_bundle_license ? ucfirst( $fs->get_module_type() ) . ' ' : '' ) . $plan_text, 'value' => strtoupper( is_string( $plan->name ) ? $plan->title : strtoupper( $free_text ) ) ); if ( $has_bundle_license ) { $profile[] = array( 'id' => 'bundle_plan', 'title' => $bundle_plan_text, 'value' => $bundle_plan_title ); } } if ( is_object( $license ) ) { if ( ! $hide_license_key ) { $profile[] = array( 'id' => 'license_key', 'title' => fs_text_inline( 'License Key', $slug ), 'value' => $license->secret_key, ); } } } } ?> > is_verified() ) : ?> is_trial() ) : ?> is_lifetime() ) : ?> is_first_payment_pending() ) : ?> is_expired() ?> is_first_payment_pending() ) : ?> is_trial() ) : ?>
    $fs, 'slug' => $slug, 'license' => $available_license, 'plan' => $available_license_paid_plan, 'is_localhost' => $site->is_localhost(), 'install_id' => $site->id, 'class' => 'button-primary', ); fs_require_template( 'account/partials/activate-license-button.php', $view_params ); ?>
    get_unique_affix() . '_sync_license' ) ?>
    has_premium_version() ) : ?> can_use_premium_code() ) : ?>
    is_verified() ) : ?>
    has_release_on_freemius() ) : ?> secret_key ) && in_array( $p['id'], array( 'email', 'user_name' ) ) ) ) : ?>

    get_site(); if ( is_object( $site ) && ( ! is_object( $current_install ) || $current_install->id != $site->id ) ) { $fs->switch_to_blog( $current_blog_id, $site, true ); } ?>
    is_whitelabeled_by_flag() ) { $hide_all_addons_data = true; foreach ( $addons_to_show as $addon_id ) { $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] ); $addon_info = $fs->_get_addon_info( $addon_id, $is_addon_installed ); $is_addon_connected = $addon_info['is_connected']; $fs_addon = ( $is_addon_connected && $is_addon_installed ) ? freemius( $addon_id ) : null; $is_whitelabeled = is_object( $fs_addon ) ? $fs_addon->is_whitelabeled( true ) : $addon_info['is_whitelabeled']; if ( ! $is_whitelabeled ) { $hide_all_addons_data = false; } if ( $is_data_debug_mode ) { $is_whitelabeled = false; } $addon_info_by_id[ $addon_id ] = $addon_info; } } foreach ( $addons_to_show as $addon_id ) { $is_addon_installed = isset( $installed_addons_ids_map[ $addon_id ] ); if ( $hide_all_addons_data && ! $is_addon_installed && ! file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $fs->get_addon_basename( $addon_id ) ) ) ) { continue; } $addon_view_params = array( 'parent_fs' => $fs, 'addon_id' => $addon_id, 'odd' => $odd, 'fs_blog_id' => $fs_blog_id, 'active_plugins_directories_map' => &$active_plugins_directories_map, 'is_addon_installed' => $is_addon_installed, 'addon_info' => isset( $addon_info_by_id[ $addon_id ] ) ? $addon_info_by_id[ $addon_id ] : $fs->_get_addon_info( $addon_id, $is_addon_installed ), 'is_whitelabeled' => ( $is_whitelabeled && ! $is_data_debug_mode ) ); fs_require_template( 'account/partials/addon.php', $addon_view_params ); $odd = ! $odd; } ?>

    do_action( 'after_account_details' ) ?> $VARS['id'], 'payments' => $payments ); fs_require_once_template( 'account/billing.php', $view_params ); fs_require_once_template( 'account/payments.php', $view_params ); } ?>
    _get_subscription_cancellation_dialog_box_template_params( true ); if ( ! empty( $subscription_cancellation_dialog_box_template_params ) ) { fs_require_template( 'forms/subscription-cancellation.php', $subscription_cancellation_dialog_box_template_params ); } ?> _add_tabs_after_content(); } $params = array( 'page' => 'account', 'module_id' => $fs->get_id(), 'module_type' => $fs->get_module_type(), 'module_slug' => $slug, 'module_version' => $fs->get_plugin_version(), ); fs_require_template( 'powered-by.php', $params ); PK!ō<(freemius/templates/auto-installation.phpnu[is_tracking_allowed() ? 'stop_tracking' : 'allow_tracking'; $title = $fs->get_plugin_title(); if ( $plugin_id != $fs->get_id() ) { $addon = $fs->get_addon( $plugin_id ); if ( is_object( $addon ) ) { $title = $addon->title . ' ' . fs_text_inline( 'Add-On', 'addon', $slug ); } } $plugin_title = sprintf( '%s', esc_html( $title ) ); $sec_countdown = 30; $countdown_html = sprintf( esc_js( /* translators: %s: Number of seconds */ fs_text_inline( '%s sec', 'x-sec', $slug ) ), sprintf( '%s', $sec_countdown ) ); fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' ); fs_enqueue_local_style( 'fs_common', '/admin/common.css' ); $params = array(); $loader_html = fs_get_template( 'ajax-loader.php', $params ); // Pass unique auto installation URL if WP_Filesystem is needed. $install_url = $fs->_get_sync_license_url( $plugin_id, true, array( 'auto_install' => 'true' ) ); ob_start(); $method = ''; // Leave blank so WP_Filesystem can populate it as necessary. $credentials = request_filesystem_credentials( esc_url_raw( $install_url ), $method, false, WP_PLUGIN_DIR, array() ); $credentials_form = ob_get_clean(); $require_credentials = ! empty( $credentials_form ); ?>

    %s', 'https://freemius.com', 'freemius.com' ), $countdown_html ) ?>

    ' PK!%YU-freemius/templates/sticky-admin-notice-js.phpnu[ PK!%ⴕOO,freemius/templates/account/partials/site.phpnu[get_slug(); $site = $VARS['site']; $main_license = $VARS['license']; $is_data_debug_mode = $fs->is_data_debug_mode(); $is_whitelabeled = $fs->is_whitelabeled(); $has_paid_plan = $fs->has_paid_plan(); $is_premium = $fs->is_premium(); $main_user = $VARS['user']; $blog_id = $site['blog_id']; $install = $VARS['install']; $is_registered = ! empty( $install ); $license = null; $trial_plan = $fs->get_trial_plan(); $free_text = fs_text_inline( 'Free', 'free', $slug ); if ( $is_whitelabeled && is_object( $install ) && $fs->is_delegated_connection( $blog_id ) ) { $is_whitelabeled = $fs->is_whitelabeled( true, $blog_id ); } ?> data-install-id="id ?>"> id ?>
    $fs, 'slug' => $slug, 'blog_id' => $blog_id, 'class' => 'button-small', ); $license = null; if ( $is_registered ) { $view_params['install_id'] = $install->id; $view_params['is_localhost'] = $install->is_localhost(); $has_license = FS_Plugin_License::is_valid_id( $install->license_id ); $license = $has_license ? $fs->_get_license_by_id( $install->license_id ) : null; } else { $view_params['is_localhost'] = FS_Site::is_localhost_by_address( $site['url'] ); } if ( ! $is_whitelabeled ) { if ( is_object( $license ) ) { $view_params['license'] = $license; // Show license deactivation button. fs_require_template( 'account/partials/deactivate-license-button.php', $view_params ); } else { if ( is_object( $main_license ) && $main_license->can_activate( $view_params['is_localhost'] ) ) { // Main license is available for activation. $available_license = $main_license; } else { // Try to find any available license for activation. $available_license = $fs->_get_available_premium_license( $view_params['is_localhost'] ); } if ( is_object( $available_license ) ) { $premium_plan = $fs->_get_plan_by_id( $available_license->plan_id ); $view_params['license'] = $available_license; $view_params['class'] .= ' button-primary'; $view_params['plan'] = $premium_plan; fs_require_template( 'account/partials/activate-license-button.php', $view_params ); } } } } ?> is_trial() ) { if ( is_object( $trial_plan ) && $trial_plan->id == $install->trial_plan_id ) { $plan_title = is_string( $trial_plan->name ) ? strtoupper( $trial_plan->title ) : fs_text_inline( 'Trial', 'trial', $slug ); } else { $plan_title = fs_text_inline( 'Trial', 'trial', $slug ); } } else { $plan = $fs->_get_plan_by_id( $install->plan_id ); $plan_title = strtoupper( is_string( $plan->title ) ? $plan->title : strtoupper( $free_text ) ); } } ?> > user_id != $main_user->id ) : ?> user_id ) ?> > > > > > > id != $license->id ) : ?> _get_subscription( $license->id ) ?> is_lifetime() && is_object( $subscription ) ) : ?> > is_active(); $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); /* translators: %s: Time period (e.g. Expires in "2 months") */ $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); ?>
    : license_id ) ) : ?> is_homepage_url_tracking_allowed( $blog_id ) ?>
    id}", ':' ) ) ?>
    : get_name() ) ?>
    : email ) ?>
    : id ?>
    : public_key ) ?>
    : secret_key ) ?>
    : get_html_escaped_masked_secret_key() ?>
    : id ?> - billing_cycle ? _fs_text_inline( 'Annual', 'annual', $slug ) : _fs_text_inline( 'Monthly', 'monthly', $slug ) ); ?> is_first_payment_pending() ) : ?> is_first_payment_pending() ) : ?> expiration ) ); $downgrade_confirmation_message = sprintf( $downgrade_x_confirm_text, ( $fs->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), $plan->title, $human_readable_license_expiration ); $after_downgrade_message = ! $license->is_block_features ? sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs->get_module_label( true ) ) : sprintf( $after_downgrade_blocking_text, $plan->title ); ?>
    PK!p2WW-freemius/templates/account/partials/index.phpnu[_get_subscription( $license->id ) : null; $has_active_subscription = ( is_object( $license_subscription ) && $license_subscription->is_active() ); $button_id = "fs_disconnect_button_{$fs->get_id()}"; $website_link = sprintf( '%s', fs_strip_url_protocol( untrailingslashit( Freemius::get_unfiltered_site_url() ) ) ); ?>
    esc_html_inline( 'Disconnect', 'disconnect' ) ?>
    PK!GGAfreemius/templates/account/partials/deactivate-license-button.phpnu[
    PK!MZ!W!W-freemius/templates/account/partials/addon.phpnu[get_slug(); $fs_blog_id = $VARS['fs_blog_id']; $active_plugins_directories_map = $VARS['active_plugins_directories_map']; $addon_info = $VARS['addon_info']; $is_addon_activated = $fs->is_addon_activated( $addon_id ); $is_addon_connected = $addon_info['is_connected']; $is_addon_installed = $VARS['is_addon_installed']; $fs_addon = ( $is_addon_connected && $is_addon_installed ) ? freemius( $addon_id ) : false; // Aliases. $download_latest_text = fs_text_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $slug ); $downgrading_plan_text = fs_text_inline( 'Downgrading your plan', 'downgrading-plan', $slug ); $cancelling_subscription_text = fs_text_inline( 'Cancelling the subscription', 'cancelling-subscription', $slug ); /* translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' */ $downgrade_x_confirm_text = fs_text_inline( '%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.', 'downgrade-x-confirm', $slug ); $prices_increase_text = fs_text_inline( 'Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.', 'pricing-increase-warning', $slug ); $cancel_trial_confirm_text = fs_text_inline( 'Cancelling the trial will immediately block access to all premium features. Are you sure?', 'cancel-trial-confirm', $slug ); $after_downgrade_non_blocking_text = fs_text_inline( 'You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.', 'after-downgrade-non-blocking', $slug ); $after_downgrade_blocking_text = fs_text_inline( 'Once your license expires you can still use the Free version but you will NOT have access to the %s features.', 'after-downgrade-blocking', $slug ); /* translators: %s: Plan title (e.g. "Professional") */ $activate_plan_text = fs_text_inline( 'Activate %s Plan', 'activate-x-plan', $slug ); $version_text = fs_text_x_inline( 'Version', 'product version', 'version', $slug ); /* translators: %s: Time period (e.g. Auto renews in "2 months") */ $renews_in_text = fs_text_inline( 'Auto renews in %s', 'renews-in', $slug ); /* translators: %s: Time period (e.g. Expires in "2 months") */ $expires_in_text = fs_text_inline( 'Expires in %s', 'expires-in', $slug ); $cancel_trial_text = fs_text_inline( 'Cancel Trial', 'cancel-trial', $slug ); $change_plan_text = fs_text_inline( 'Change Plan', 'change-plan', $slug ); $upgrade_text = fs_text_x_inline( 'Upgrade', 'verb', 'upgrade', $slug ); $addons_text = fs_text_inline( 'Add-Ons', 'add-ons', $slug ); $downgrade_text = fs_text_x_inline( 'Downgrade', 'verb', 'downgrade', $slug ); $trial_text = fs_text_x_inline( 'Trial', 'trial period', 'trial', $slug ); $free_text = fs_text_inline( 'Free', 'free', $slug ); $activate_text = fs_text_inline( 'Activate', 'activate', $slug ); $plan_text = fs_text_x_inline( 'Plan', 'as product pricing plan', 'plan', $slug ); // Defaults. $plan = null; $is_paid_trial = false; /** * @var FS_Plugin_License $license */ $license = null; $site = null; $is_active_subscription = false; $subscription = null; $is_paying = false; $show_upgrade = false; $is_whitelabeled = $VARS['is_whitelabeled']; if ( is_object( $fs_addon ) ) { $is_paying = $fs_addon->is_paying(); $user = $fs_addon->get_user(); $site = $fs_addon->get_site(); $license = $fs_addon->_get_license(); $subscription = ( is_object( $license ) ? $fs_addon->_get_subscription( $license->id ) : null ); $plan = $fs_addon->get_plan(); $plan_name = $plan->name; $plan_title = $plan->title; $is_paid_trial = $fs_addon->is_paid_trial(); $version = $fs_addon->get_plugin_version(); $is_whitelabeled = ( $fs_addon->is_whitelabeled( true ) && ! $fs_addon->get_parent_instance()->is_data_debug_mode() ); $show_upgrade = ( ! $is_whitelabeled && $fs_addon->has_paid_plan() && ! $is_paying && ! $is_paid_trial && ! $fs_addon->_has_premium_license() ); } else if ( $is_addon_connected ) { if ( empty( $addon_info ) || ! isset( $addon_info['site'] ) ) { $is_addon_connected = false; } else { /** * @var FS_Site $site */ $site = $addon_info['site']; $version = $addon_info['version']; $plan_name = isset( $addon_info['plan_name'] ) ? $addon_info['plan_name'] : ''; $plan_title = isset( $addon_info['plan_title'] ) ? $addon_info['plan_title'] : ''; if ( isset( $addon_info['license'] ) ) { $license = $addon_info['license']; } if ( isset( $addon_info['subscription'] ) ) { $subscription = $addon_info['subscription']; } $has_valid_and_active_license = ( is_object( $license ) && $license->is_active() && $license->is_valid() ); $is_paid_trial = ( $site->is_trial() && $has_valid_and_active_license && ( $site->trial_plan_id == $license->plan_id ) ); $is_whitelabeled = $addon_info['is_whitelabeled']; } } $has_feature_enabled_license = ( is_object( $license ) && $license->is_features_enabled() ); $is_active_subscription = ( is_object( $subscription ) && $subscription->is_active() ); $show_delete_install_button = ( ! $is_paying && WP_FS__DEV_MODE && ! $is_whitelabeled ); ?> > id ?> is_trial() || is_object( $license ) ) : ?> is_trial() ) { $tags[] = array( 'label' => $trial_text, 'type' => 'success' ); $tags[] = array( 'label' => sprintf( ( $is_paid_trial ? $renews_in_text : $expires_in_text ), human_time_diff( time(), strtotime( $site->trial_ends ) ) ), 'type' => ( $is_paid_trial ? 'success' : 'warn' ) ); } else { if ( is_object( $license ) ) { if ( $license->is_cancelled ) { $tags[] = array( 'label' => fs_text_inline( 'Cancelled', 'cancelled', $slug ), 'type' => 'error' ); } else if ( $license->is_expired() ) { $tags[] = array( 'label' => fs_text_inline( 'Expired', 'expired', $slug ), 'type' => 'error' ); } else if ( $license->is_lifetime() ) { $tags[] = array( 'label' => fs_text_inline( 'No expiration', 'no-expiration', $slug ), 'type' => 'success' ); } else if ( ! $is_active_subscription && ! $license->is_first_payment_pending() ) { $tags[] = array( 'label' => sprintf( $expires_in_text, human_time_diff( time(), strtotime( $license->expiration ) ) ), 'type' => 'warn' ); } else if ( $is_active_subscription && ! $subscription->is_first_payment_pending() ) { $tags[] = array( 'label' => sprintf( $renews_in_text, human_time_diff( time(), strtotime( $subscription->next_payment ) ) ), 'type' => 'success' ); } } } foreach ( $tags as $t ) { printf( '' . "\n", $t['type'], $t['label'] ); } ?> get_id(), 'account', 'deactivate_license', fs_text_inline( 'Deactivate License', 'deactivate-license', $slug ), '', array( 'plugin_id' => $addon_id ), false, true ); $human_readable_license_expiration = human_time_diff( time(), strtotime( $license->expiration ) ); $downgrade_confirmation_message = sprintf( $downgrade_x_confirm_text, ( $fs_addon->is_only_premium() ? $cancelling_subscription_text : $downgrading_plan_text ), $plan->title, $human_readable_license_expiration ); $after_downgrade_message = ! $license->is_block_features ? sprintf( $after_downgrade_non_blocking_text, $plan->title, $fs_addon->get_module_label( true ) ) : sprintf( $after_downgrade_blocking_text, $plan->title ); if ( ! $license->is_lifetime() && $is_active_subscription ) { $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', 'downgrade_account', esc_html( $fs_addon->is_only_premium() ? fs_text_inline( 'Cancel Subscription', 'cancel-subscription', $slug ) : $downgrade_text ), '', array( 'plugin_id' => $addon_id ), false, false, false, ( $downgrade_confirmation_message . ' ' . $after_downgrade_message . ' ' . $prices_increase_text ), 'POST' ); } } else if ( $is_paid_trial ) { $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', 'cancel_trial', esc_html( $cancel_trial_text ), '', array( 'plugin_id' => $addon_id ), false, false, 'dashicons dashicons-download', $cancel_trial_confirm_text, 'POST' ); } else if ( ! $has_feature_enabled_license ) { $premium_licenses = $fs_addon->get_available_premium_licenses(); if ( ! empty( $premium_licenses ) ) { $premium_license = $premium_licenses[0]; $has_multiple_premium_licenses = ( 1 < count( $premium_licenses ) ); if ( ! $has_multiple_premium_licenses ) { $premium_plan = $fs_addon->_get_plan_by_id( $premium_license->plan_id ); $site = $fs_addon->get_site(); $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', 'activate_license', esc_html( sprintf( $activate_plan_text, $premium_plan->title, ( $site->is_localhost() && $premium_license->is_free_localhost ) ? '[localhost]' : ( 1 < $premium_license->left() ? $premium_license->left() . ' left' : '' ) ) ), ($has_multiple_premium_licenses ? 'activate-license-trigger ' . $fs_addon->get_unique_affix() : ''), array( 'plugin_id' => $addon_id, 'license_id' => $premium_license->id, ), true, true ); $is_license_activation_added = true; } } } } // if ( 0 == count( $buttons ) ) { if ( $fs_addon->is_premium() && ! $is_license_activation_added ) { $fs_addon->_add_license_activation_dialog_box(); $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', 'activate_license', ( ! $has_feature_enabled_license ) ? fs_esc_html_inline( 'Activate License', 'activate-license', $slug ) : fs_esc_html_inline( 'Change License', 'change-license', $slug ), 'activate-license-trigger ' . $fs_addon->get_unique_affix(), array( 'plugin_id' => $addon_id, ), (! $has_feature_enabled_license), true ); $is_license_activation_added = true; } if ( $fs_addon->has_paid_plan() ) { // Add sync license only if non of the other CTAs are visible. $buttons[] = fs_ui_get_action_button( $fs->get_id(), 'account', $fs->get_unique_affix() . '_sync_license', fs_esc_html_x_inline( 'Sync', 'as synchronize', 'sync', $slug ), '', array( 'plugin_id' => $addon_id ), false, true ); } // } } else if ( ! $show_upgrade ) { if ( $fs->is_addon_installed( $addon_id ) ) { $addon_file = $fs->get_addon_basename( $addon_id ); if ( ! isset( $active_plugins_directories_map[ dirname( $addon_file ) ] ) ) { $buttons[] = sprintf( '%s', wp_nonce_url( 'plugins.php?action=activate&plugin=' . $addon_file, 'activate-plugin_' . $addon_file ), fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $slug ), $activate_text ); } } else { if ( $fs->is_allowed_to_install() ) { $buttons[] = sprintf( '%s', wp_nonce_url( self_admin_url( 'update.php?' . ( ( isset( $addon_info['has_paid_plan'] ) && $addon_info['has_paid_plan'] ) ? 'fs_allow_updater_and_dialog=true&' : '' ) . 'action=install-plugin&plugin=' . $addon_info['slug'] ), 'install-plugin_' . $addon_info['slug'] ), fs_text_inline( 'Install Now', 'install-now', $slug ) ); } else { $buttons[] = sprintf( '%s', $fs->_get_latest_download_local_url( $addon_id ), esc_html( $download_latest_text ) ); } } } if ( $show_upgrade ) { $buttons[] = sprintf( ' %s', esc_url( network_admin_url( 'plugin-install.php?fs_allow_updater_and_dialog=true' . ( ! empty( $fs_blog_id ) ? '&fs_blog_id=' . $fs_blog_id : '' ) . '&tab=plugin-information&parent_plugin_id=' . $fs->get_id() . '&plugin=' . $addon_info['slug'] . '&TB_iframe=true&width=600&height=550' ) ), esc_attr( sprintf( fs_text_inline( 'More information about %s', 'more-information-about-x', $slug ), $addon_info['title'] ) ), esc_attr( $addon_info['title'] ), ( $fs_addon->has_free_plan() ? $upgrade_text : fs_text_x_inline( 'Purchase', 'verb', 'purchase', $slug ) ) ); } $buttons_count = count( $buttons ); ?> 1 ) : ?>
    1 ) : ?>
    is_addon_installed( $addon_id ); ?> get_addon_basename( $addon_id ) ?> is_allowed_to_install() ) : ?> get_id(), 'account', 'delete_account', fs_text_x_inline( 'Delete', 'verb', 'delete', $slug ), '', array( 'plugin_id' => $addon_id ), false, $show_upgrade ); } ?> PK!g]6 6 ?freemius/templates/account/partials/activate-license-button.phpnu[
    PK!p2WW$freemius/templates/account/index.phpnu[get_slug(); $edit_text = fs_text_x_inline( 'Edit', 'verb', 'edit', $slug ); $update_text = fs_text_x_inline( 'Update', 'verb', 'update', $slug ); $billing = $fs->_fetch_billing(); $has_billing = ( $billing instanceof FS_Billing ); if ( ! $has_billing ) { $billing = new FS_Billing(); } ?>

    > 'Afghanistan', 'AX' => 'Aland Islands', 'AL' => 'Albania', 'DZ' => 'Algeria', 'AS' => 'American Samoa', 'AD' => 'Andorra', 'AO' => 'Angola', 'AI' => 'Anguilla', 'AQ' => 'Antarctica', 'AG' => 'Antigua and Barbuda', 'AR' => 'Argentina', 'AM' => 'Armenia', 'AW' => 'Aruba', 'AU' => 'Australia', 'AT' => 'Austria', 'AZ' => 'Azerbaijan', 'BS' => 'Bahamas', 'BH' => 'Bahrain', 'BD' => 'Bangladesh', 'BB' => 'Barbados', 'BY' => 'Belarus', 'BE' => 'Belgium', 'BZ' => 'Belize', 'BJ' => 'Benin', 'BM' => 'Bermuda', 'BT' => 'Bhutan', 'BO' => 'Bolivia', 'BQ' => 'Bonaire, Saint Eustatius and Saba', 'BA' => 'Bosnia and Herzegovina', 'BW' => 'Botswana', 'BV' => 'Bouvet Island', 'BR' => 'Brazil', 'IO' => 'British Indian Ocean Territory', 'VG' => 'British Virgin Islands', 'BN' => 'Brunei', 'BG' => 'Bulgaria', 'BF' => 'Burkina Faso', 'BI' => 'Burundi', 'KH' => 'Cambodia', 'CM' => 'Cameroon', 'CA' => 'Canada', 'CV' => 'Cape Verde', 'KY' => 'Cayman Islands', 'CF' => 'Central African Republic', 'TD' => 'Chad', 'CL' => 'Chile', 'CN' => 'China', 'CX' => 'Christmas Island', 'CC' => 'Cocos Islands', 'CO' => 'Colombia', 'KM' => 'Comoros', 'CK' => 'Cook Islands', 'CR' => 'Costa Rica', 'HR' => 'Croatia', 'CU' => 'Cuba', 'CW' => 'Curacao', 'CY' => 'Cyprus', 'CZ' => 'Czech Republic', 'CD' => 'Democratic Republic of the Congo', 'DK' => 'Denmark', 'DJ' => 'Djibouti', 'DM' => 'Dominica', 'DO' => 'Dominican Republic', 'TL' => 'East Timor', 'EC' => 'Ecuador', 'EG' => 'Egypt', 'SV' => 'El Salvador', 'GQ' => 'Equatorial Guinea', 'ER' => 'Eritrea', 'EE' => 'Estonia', 'ET' => 'Ethiopia', 'FK' => 'Falkland Islands', 'FO' => 'Faroe Islands', 'FJ' => 'Fiji', 'FI' => 'Finland', 'FR' => 'France', 'GF' => 'French Guiana', 'PF' => 'French Polynesia', 'TF' => 'French Southern Territories', 'GA' => 'Gabon', 'GM' => 'Gambia', 'GE' => 'Georgia', 'DE' => 'Germany', 'GH' => 'Ghana', 'GI' => 'Gibraltar', 'GR' => 'Greece', 'GL' => 'Greenland', 'GD' => 'Grenada', 'GP' => 'Guadeloupe', 'GU' => 'Guam', 'GT' => 'Guatemala', 'GG' => 'Guernsey', 'GN' => 'Guinea', 'GW' => 'Guinea-Bissau', 'GY' => 'Guyana', 'HT' => 'Haiti', 'HM' => 'Heard Island and McDonald Islands', 'HN' => 'Honduras', 'HK' => 'Hong Kong', 'HU' => 'Hungary', 'IS' => 'Iceland', 'IN' => 'India', 'ID' => 'Indonesia', 'IR' => 'Iran', 'IQ' => 'Iraq', 'IE' => 'Ireland', 'IM' => 'Isle of Man', 'IL' => 'Israel', 'IT' => 'Italy', 'CI' => 'Ivory Coast', 'JM' => 'Jamaica', 'JP' => 'Japan', 'JE' => 'Jersey', 'JO' => 'Jordan', 'KZ' => 'Kazakhstan', 'KE' => 'Kenya', 'KI' => 'Kiribati', 'XK' => 'Kosovo', 'KW' => 'Kuwait', 'KG' => 'Kyrgyzstan', 'LA' => 'Laos', 'LV' => 'Latvia', 'LB' => 'Lebanon', 'LS' => 'Lesotho', 'LR' => 'Liberia', 'LY' => 'Libya', 'LI' => 'Liechtenstein', 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'MO' => 'Macao', 'MK' => 'Macedonia', 'MG' => 'Madagascar', 'MW' => 'Malawi', 'MY' => 'Malaysia', 'MV' => 'Maldives', 'ML' => 'Mali', 'MT' => 'Malta', 'MH' => 'Marshall Islands', 'MQ' => 'Martinique', 'MR' => 'Mauritania', 'MU' => 'Mauritius', 'YT' => 'Mayotte', 'MX' => 'Mexico', 'FM' => 'Micronesia', 'MD' => 'Moldova', 'MC' => 'Monaco', 'MN' => 'Mongolia', 'ME' => 'Montenegro', 'MS' => 'Montserrat', 'MA' => 'Morocco', 'MZ' => 'Mozambique', 'MM' => 'Myanmar', 'NA' => 'Namibia', 'NR' => 'Nauru', 'NP' => 'Nepal', 'NL' => 'Netherlands', 'NC' => 'New Caledonia', 'NZ' => 'New Zealand', 'NI' => 'Nicaragua', 'NE' => 'Niger', 'NG' => 'Nigeria', 'NU' => 'Niue', 'NF' => 'Norfolk Island', 'KP' => 'North Korea', 'MP' => 'Northern Mariana Islands', 'NO' => 'Norway', 'OM' => 'Oman', 'PK' => 'Pakistan', 'PW' => 'Palau', 'PS' => 'Palestinian Territory', 'PA' => 'Panama', 'PG' => 'Papua New Guinea', 'PY' => 'Paraguay', 'PE' => 'Peru', 'PH' => 'Philippines', 'PN' => 'Pitcairn', 'PL' => 'Poland', 'PT' => 'Portugal', 'PR' => 'Puerto Rico', 'QA' => 'Qatar', 'CG' => 'Republic of the Congo', 'RE' => 'Reunion', 'RO' => 'Romania', 'RU' => 'Russia', 'RW' => 'Rwanda', 'BL' => 'Saint Barthelemy', 'SH' => 'Saint Helena', 'KN' => 'Saint Kitts and Nevis', 'LC' => 'Saint Lucia', 'MF' => 'Saint Martin', 'PM' => 'Saint Pierre and Miquelon', 'VC' => 'Saint Vincent and the Grenadines', 'WS' => 'Samoa', 'SM' => 'San Marino', 'ST' => 'Sao Tome and Principe', 'SA' => 'Saudi Arabia', 'SN' => 'Senegal', 'RS' => 'Serbia', 'SC' => 'Seychelles', 'SL' => 'Sierra Leone', 'SG' => 'Singapore', 'SX' => 'Sint Maarten', 'SK' => 'Slovakia', 'SI' => 'Slovenia', 'SB' => 'Solomon Islands', 'SO' => 'Somalia', 'ZA' => 'South Africa', 'GS' => 'South Georgia and the South Sandwich Islands', 'KR' => 'South Korea', 'SS' => 'South Sudan', 'ES' => 'Spain', 'LK' => 'Sri Lanka', 'SD' => 'Sudan', 'SR' => 'Suriname', 'SJ' => 'Svalbard and Jan Mayen', 'SZ' => 'Swaziland', 'SE' => 'Sweden', 'CH' => 'Switzerland', 'SY' => 'Syria', 'TW' => 'Taiwan', 'TJ' => 'Tajikistan', 'TZ' => 'Tanzania', 'TH' => 'Thailand', 'TG' => 'Togo', 'TK' => 'Tokelau', 'TO' => 'Tonga', 'TT' => 'Trinidad and Tobago', 'TN' => 'Tunisia', 'TR' => 'Turkey', 'TM' => 'Turkmenistan', 'TC' => 'Turks and Caicos Islands', 'TV' => 'Tuvalu', 'VI' => 'U.S. Virgin Islands', 'UG' => 'Uganda', 'UA' => 'Ukraine', 'AE' => 'United Arab Emirates', 'GB' => 'United Kingdom', 'US' => 'United States', 'UM' => 'United States Minor Outlying Islands', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan', 'VU' => 'Vanuatu', 'VA' => 'Vatican', 'VE' => 'Venezuela', 'VN' => 'Vietnam', 'WF' => 'Wallis and Futuna', 'EH' => 'Western Sahara', 'YE' => 'Yemen', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe', ) ?>
    PK!B['freemius/templates/account/payments.phpnu[get_slug(); ?>

    >
    id ?> created ) ) ?> formatted_gross() ?> is_migrated() ) : ?>
    get_text_x_inline( 'Opt Out', 'verb', 'opt-out' ); $opt_in_text = $fs->get_text_x_inline( 'Opt In', 'verb', 'opt-in' ); if ( empty( $permission_group[ 'prompt' ] ) ) { $is_enabled = false; foreach ( $permission_group[ 'permissions' ] as $permission ) { if ( true === $permission[ 'default' ] ) { // Even if one of the permissions is on, treat as if the entire group is on. $is_enabled = true; break; } } } else { $is_enabled = ( isset( $permission_group['is_enabled'] ) && true === $permission_group['is_enabled'] ); } ?>

      render_permission( $permission ); } ?>
    PK!p2WW$freemius/templates/connect/index.phpnu[
  • class="fs-tooltip-trigger">

  • PK!Hݧ2freemius/templates/api-connectivity-message-js.phpnu[ PK!K3((/freemius/templates/js/jquery.content-change.phpnu[ PK!R9~~1freemius/templates/js/open-license-activation.phpnu[ PK!p2WWfreemius/templates/js/index.phpnu[ PK!3coo-freemius/templates/js/style-premium-theme.phpnu[get_slug(); ?> PK!c c #freemius/templates/admin-notice.phpnu[
    >
    PK!i $freemius/templates/gdpr-optin-js.phpnu[ PK!2&M-M-freemius/README.mdnu[Freemius WordPress SDK ====================== Welcome to the official repository for the Freemius SDK! Adding the SDK to your WordPress plugin, theme, or add-ons, enables all the benefits that come with using the [Freemius platform](https://freemius.com) such as: * [Software Licensing](https://freemius.com/wordpress/software-licensing/) * [Secure Checkout](https://freemius.com/wordpress/checkout/) * [Subscriptions](https://freemius.com/wordpress/recurring-payments-subscriptions/) * [Automatic Updates](https://freemius.com/wordpress/automatic-software-updates/) * [Seamless EU VAT](https://freemius.com/wordpress/collecting-eu-vat-europe/) * [Cart Abandonment Recovery](https://freemius.com/wordpress/cart-abandonment-recovery/) * [Affiliate Platform](https://freemius.com/wordpress/affiliate-platform/) * [Analytics & Usage Tracking](https://freemius.com/wordpress/insights/) * [User Dashboard](https://freemius.com/wordpress/user-dashboard/) * [Monetization](https://freemius.com/wordpress/) * [Analytics](https://freemius.com/wordpress/insights/) * [More...](https://freemius.com/wordpress/features-comparison/) Freemius truly empowers developers to create prosperous subscription-based businesses. If you're new to Freemius then we recommend taking a look at our [Getting Started](https://freemius.com/help/documentation/getting-started/) guide first. If you're a WordPress plugin or theme developer and are interested in monetizing with Freemius then you can [sign-up for a FREE account](https://dashboard.freemius.com/register/): https://dashboard.freemius.com/register/ Once you have your account setup and are familiar with how it all works you're ready to begin [integrating Freemius](https://freemius.com/help/documentation/wordpress-sdk/integrating-freemius-sdk/) into your WordPress product You can see some of the existing WordPress.org plugins & themes that are already utilizing the power of Freemius here: * https://profiles.wordpress.org/freemius/#content-plugins * https://includewp.com/freemius/#focus ## Code Documentation You can find the SDK's documentation here: https://freemius.com/help/documentation/wordpress-sdk/ ## Integrating & Initializing the SDK As part of the integration process, you'll need to [add the latest version](https://freemius.com/help/documentation/getting-started/#add_the_latest_wordpress_sdk_into_your_product) of the Freemius SDK into your WordPress project. Then, when you've completed the [SDK integration form](https://freemius.com/help/documentation/getting-started/#fill_out_the_sdk_integration_form) a snippet of code is generated which you'll need to copy and paste into the top of your main plugin's PHP file, right after the plugin's header comment. Note: For themes, this will be in the root `functions.php` file instead. A typical SDK snippet will look similar to the following (your particular snippet may differ slightly depending on your integration): ```php if ( ! function_exists( 'my_prefix_fs' ) ) { // Create a helper function for easy SDK access. function my_prefix_fs() { global $my_prefix_fs; if ( ! isset( $my_prefix_fs ) ) { // Include Freemius SDK. require_once dirname(__FILE__) . '/freemius/start.php'; $my_prefix_fs = fs_dynamic_init( array( 'id' => '1234', 'slug' => 'my-new-plugin', 'premium_slug' => 'my-new-plugin-premium', 'type' => 'plugin', 'public_key' => 'pk_bAEfta69seKymZzmf2xtqq8QXHz9y', 'is_premium' => true, // If your plugin is a serviceware, set this option to false. 'has_premium_version' => true, 'has_paid_plans' => true, 'is_org_compliant' => true, 'menu' => array( 'slug' => 'my-new-plugin', 'parent' => array( 'slug' => 'options-general.php', ), ), // Set the SDK to work in a sandbox mode (for development & testing). // IMPORTANT: MAKE SURE TO REMOVE SECRET KEY BEFORE DEPLOYMENT. 'secret_key' => 'sk_ubb4yN3mzqGR2x8#P7r5&@*xC$utE', ) ); } return $my_prefix_fs; } // Init Freemius. my_prefix_fs(); // Signal that SDK was initiated. do_action( 'my_prefix_fs_loaded' ); } ``` ## Usage example You can call anySDK methods by prefixing them with the shortcode function for your particular plugin/theme (specified when completing the SDK integration form in the Developer Dashboard): ```php get_upgrade_url(); ?> ``` Or when calling Freemius multiple times in a scope, it's recommended to use it with the global variable: ```php get_account_url(); ?> ``` There are many other SDK methods available that you can use to enhance the functionality of your WordPress product. Some of the more common use-cases are covered in the [Freemius SDK Gists](https://freemius.com/help/documentation/wordpress-sdk/gists/) documentation. ## Adding license based logic examples Add marketing content to encourage your users to upgrade for your paid version: ```php is_not_paying() ) { echo '

    ' . esc_html__('Awesome Premium Features', 'my-plugin-slug') . '

    '; echo '' . esc_html__('Upgrade Now!', 'my-plugin-slug') . ''; echo '
    '; } ?> ``` Add logic which will only be available in your premium plugin version: ```php is__premium_only() ) { // ... premium only logic ... } ?> ``` To add a function which will only be available in your premium plugin version, simply add __premium_only as the suffix of the function name. Just make sure that all lines that call that method directly or by hooks, are also wrapped in premium only logic: ```php is__premium_only() ) { // Init premium version. $this->admin_init__premium_only(); add_action( 'admin_init', array( &$this, 'admin_init_hook__premium_only' ); } ... } // This method will be only included in the premium version. function admin_init__premium_only() { ... } // This method will be only included in the premium version. function admin_init_hook__premium_only() { ... } } ?> ``` Add logic which will only be executed for customers in your 'professional' plan: ```php is_plan('professional', true) ) { // .. logic related to Professional plan only ... } ?> ``` Add logic which will only be executed for customers in your 'professional' plan or higher plans: ```php is_plan('professional') ) { // ... logic related to Professional plan and higher plans ... } ?> ``` Add logic which will only be available in your premium plugin version AND will only be executed for customers in your 'professional' plan (and higher plans): ```php is_plan__premium_only('professional') ) { // ... logic related to Professional plan and higher plans ... } ?> ``` Add logic only for users in trial: ```php is_trial() ) { // ... logic for users in trial ... } ?> ``` Add logic for specified paid plan: ```php is__premium_only() ) { if ( my_prefix_fs()->is_plan( 'professional', true ) ) { // ... logic related to Professional plan only ... } else if ( my_prefix_fs()->is_plan( 'business' ) ) { // ... logic related to Business plan and higher plans ... } } ?> ``` ## Excluding files and folders from the free plugin version There are [two ways](https://freemius.com/help/documentation/wordpress-sdk/software-licensing/#excluding_files_and_folders_from_the_free_plugin_version) to exclude files from your free version. 1. Add `__premium_only` just before the file extension. For example, functions__premium_only.php will be only included in the premium plugin version. This works for all types of files, not only PHP. 2. Add `@fs_premium_only` a special meta tag to the plugin's main PHP file header. Example: ```php ``` In the example plugin header above, the file `/lib/functions.php` and the directory `/premium-files/` will be removed from the free plugin version. # WordPress.org Compliance Based on [WordPress.org Guidelines](https://wordpress.org/plugins/about/guidelines/) you are not allowed to submit a plugin that has premium code in it: > All code hosted by WordPress.org servers must be free and fully-functional. If you want to sell advanced features for a plugin (such as a "pro" version), then you must sell and serve that code from your own site, we will not host it on our servers. Therefore, if you want to deploy your free plugin's version to WordPress.org, make sure you wrap all your premium code with `if ( my_prefix_fs()->{{ method }}__premium_only() )` or use [some of the other methods](https://freemius.com/help/documentation/wordpress-sdk/software-licensing/) provided by the SDK to exclude premium features & files from the free version. ## Deployment Zip your Freemius product’s root folder and [upload it in the Deployment section](https://freemius.com/help/documentation/selling-with-freemius/deployment/) in the *Freemius Developer's Dashboard*. The plugin/theme will automatically be scanned and processed by a custom-developed *PHP Processor* which will auto-generate two versions of your plugin: 1. **Premium version**: Identical to your uploaded version, including all code (except your `secret_key`). Will be enabled for download ONLY for your paying or in trial customers. 2. **Free version**: The code stripped from all your paid features (based on the logic added wrapped in `{ method }__premium_only()`). The free version is the one that you should give your users to download. Therefore, download the free generated version and upload to your site. Or, if your plugin was WordPress.org compliant and you made sure to exclude all your premium code with the different provided techniques, you can deploy the downloaded free version to the .org repo. ## License Copyright (c) Freemius®, Inc. Licensed under the GNU general public license (version 3). PK!pJJfreemius/start.phpnu[plugins ) ) { $fs_active_plugins->plugins = array(); } } if ( empty( $fs_active_plugins->abspath ) ) { /** * Store the WP install absolute path reference to identify environment change * while replicating the storage. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 */ $fs_active_plugins->abspath = ABSPATH; } else { if ( ABSPATH !== $fs_active_plugins->abspath ) { /** * WordPress path has changed, cleanup the SDK references cache. * This resolves issues triggered when spinning a staging environments * while replicating the database. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 */ $fs_active_plugins->abspath = ABSPATH; $fs_active_plugins->plugins = array(); unset( $fs_active_plugins->newest ); } else { /** * Make sure SDK references are still valid. This resolves * issues when users hard delete modules via FTP. * * @author Vova Feldman (@svovaf) * @since 1.2.1.7 */ $has_changes = false; foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) { if ( ! file_exists( ( isset( $data->type ) && 'theme' === $data->type ? $themes_directory : WP_PLUGIN_DIR ) . '/' . $sdk_path ) ) { unset( $fs_active_plugins->plugins[ $sdk_path ] ); if ( ! empty( $fs_active_plugins->newest ) && $sdk_path === $fs_active_plugins->newest->sdk_path ) { unset( $fs_active_plugins->newest ); } $has_changes = true; } } if ( $has_changes ) { if ( empty( $fs_active_plugins->plugins ) ) { unset( $fs_active_plugins->newest ); } update_option( 'fs_active_plugins', $fs_active_plugins ); } } } if ( ! function_exists( 'fs_find_direct_caller_plugin_file' ) ) { require_once dirname( __FILE__ ) . '/includes/supplements/fs-essential-functions-1.1.7.1.php'; } if ( ! function_exists( 'fs_get_plugins' ) ) { require_once dirname( __FILE__ ) . '/includes/supplements/fs-essential-functions-2.2.1.php'; } // Update current SDK info based on the SDK path. if ( ! isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) || $this_sdk_version != $fs_active_plugins->plugins[ $this_sdk_relative_path ]->version ) { if ( $is_theme ) { $plugin_path = basename( dirname( $this_sdk_relative_path ) ); } else { $plugin_path = plugin_basename( fs_find_direct_caller_plugin_file( $file_path ) ); } $fs_active_plugins->plugins[ $this_sdk_relative_path ] = (object) array( 'version' => $this_sdk_version, 'type' => ( $is_theme ? 'theme' : 'plugin' ), 'timestamp' => time(), 'plugin_path' => $plugin_path, ); } $is_current_sdk_newest = isset( $fs_active_plugins->newest ) && ( $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path ); if ( ! isset( $fs_active_plugins->newest ) ) { /** * This will be executed only once, for the first time a Freemius powered plugin is activated. */ fs_update_sdk_newest_version( $this_sdk_relative_path, $fs_active_plugins->plugins[ $this_sdk_relative_path ]->plugin_path ); $is_current_sdk_newest = true; } else if ( version_compare( $fs_active_plugins->newest->version, $this_sdk_version, '<' ) ) { /** * Current SDK is newer than the newest stored SDK. */ fs_update_sdk_newest_version( $this_sdk_relative_path, $fs_active_plugins->plugins[ $this_sdk_relative_path ]->plugin_path ); if ( class_exists( 'Freemius' ) ) { // Older SDK version was already loaded. if ( ! $fs_active_plugins->newest->in_activation ) { // Re-order plugins to load this plugin first. fs_newest_sdk_plugin_first(); } // Refresh page. fs_redirect( $_SERVER['REQUEST_URI'] ); } } else { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $fs_newest_sdk = $fs_active_plugins->newest; $fs_newest_sdk = $fs_active_plugins->plugins[ $fs_newest_sdk->sdk_path ]; $is_newest_sdk_type_theme = ( isset( $fs_newest_sdk->type ) && 'theme' === $fs_newest_sdk->type ); if ( ! $is_newest_sdk_type_theme ) { $is_newest_sdk_plugin_active = is_plugin_active( $fs_newest_sdk->plugin_path ); } else { $current_theme = wp_get_theme(); $is_newest_sdk_plugin_active = ( $current_theme->stylesheet === $fs_newest_sdk->plugin_path ); $current_theme_parent = $current_theme->parent(); /** * If the current theme is a child of the theme that has the newest SDK, this prevents a redirects loop * from happening by keeping the SDK info stored in the `fs_active_plugins` option. */ if ( ! $is_newest_sdk_plugin_active && $current_theme_parent instanceof WP_Theme ) { $is_newest_sdk_plugin_active = ( $fs_newest_sdk->plugin_path === $current_theme_parent->stylesheet ); } } if ( $is_current_sdk_newest && ! $is_newest_sdk_plugin_active && ! $fs_active_plugins->newest->in_activation ) { // If current SDK is the newest and the plugin is NOT active, it means // that the current plugin in activation mode. $fs_active_plugins->newest->in_activation = true; update_option( 'fs_active_plugins', $fs_active_plugins ); } if ( ! $is_theme ) { $sdk_starter_path = fs_normalize_path( WP_PLUGIN_DIR . '/' . $this_sdk_relative_path . '/start.php' ); } else { $sdk_starter_path = fs_normalize_path( $themes_directory . '/' . str_replace( "../{$themes_directory_name}/", '', $this_sdk_relative_path ) . '/start.php' ); } $is_newest_sdk_path_valid = ( $is_newest_sdk_plugin_active || $fs_active_plugins->newest->in_activation ) && file_exists( $sdk_starter_path ); if ( ! $is_newest_sdk_path_valid && ! $is_current_sdk_newest ) { // Plugin with newest SDK is no longer active, or SDK was moved to a different location. unset( $fs_active_plugins->plugins[ $fs_active_plugins->newest->sdk_path ] ); } if ( ! ( $is_newest_sdk_plugin_active || $fs_active_plugins->newest->in_activation ) || ! $is_newest_sdk_path_valid || // Is newest SDK downgraded. ( $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path && version_compare( $fs_active_plugins->newest->version, $this_sdk_version, '>' ) ) ) { /** * Plugin with newest SDK is no longer active. * OR * The newest SDK was in the current plugin. BUT, seems like the version of * the SDK was downgraded to a lower SDK. */ // Find the active plugin with the newest SDK version and update the newest reference. fs_fallback_to_newest_active_sdk(); } else { if ( $is_newest_sdk_plugin_active && $this_sdk_relative_path == $fs_active_plugins->newest->sdk_path && ( $fs_active_plugins->newest->in_activation || ( class_exists( 'Freemius' ) && ( ! defined( 'WP_FS__SDK_VERSION' ) || version_compare( WP_FS__SDK_VERSION, $this_sdk_version, '<' ) ) ) ) ) { if ( $fs_active_plugins->newest->in_activation && ! $is_newest_sdk_type_theme ) { // Plugin no more in activation. $fs_active_plugins->newest->in_activation = false; update_option( 'fs_active_plugins', $fs_active_plugins ); } // Reorder plugins to load plugin with newest SDK first. if ( fs_newest_sdk_plugin_first() ) { // Refresh page after re-order to make sure activated plugin loads newest SDK. if ( class_exists( 'Freemius' ) ) { fs_redirect( $_SERVER['REQUEST_URI'] ); } } } } } if ( class_exists( 'Freemius' ) ) { // SDK was already loaded. return; } if ( version_compare( $this_sdk_version, $fs_active_plugins->newest->version, '<' ) ) { $newest_sdk = $fs_active_plugins->plugins[ $fs_active_plugins->newest->sdk_path ]; $plugins_or_theme_dir_path = ( ! isset( $newest_sdk->type ) || 'theme' !== $newest_sdk->type ) ? WP_PLUGIN_DIR : $themes_directory; $newest_sdk_starter = fs_normalize_path( $plugins_or_theme_dir_path . '/' . str_replace( "../{$themes_directory_name}/", '', $fs_active_plugins->newest->sdk_path ) . '/start.php' ); if ( file_exists( $newest_sdk_starter ) ) { // Reorder plugins to load plugin with newest SDK first. fs_newest_sdk_plugin_first(); // There's a newer SDK version, load it instead of the current one! require_once $newest_sdk_starter; return; } } #endregion SDK Selection Logic -------------------------------------------------------------------- #region Hooks & Filters Collection -------------------------------------------------------------------- /** * Freemius hooks (actions & filters) tags structure: * * fs_{filter/action_name}_{plugin_slug} * * -------------------------------------------------------- * * Usage with WordPress' add_action() / add_filter(): * * add_action('fs_{filter/action_name}_{plugin_slug}', $callable); * * -------------------------------------------------------- * * Usage with Freemius' instance add_action() / add_filter(): * * // No need to add 'fs_' prefix nor '_{plugin_slug}' suffix. * my_freemius()->add_action('{action_name}', $callable); * * -------------------------------------------------------- * * Freemius filters collection: * * fs_connect_url_{plugin_slug} * fs_trial_promotion_message_{plugin_slug} * fs_is_long_term_user_{plugin_slug} * fs_uninstall_reasons_{plugin_slug} * fs_is_plugin_update_{plugin_slug} * fs_api_domains_{plugin_slug} * fs_email_template_sections_{plugin_slug} * fs_support_forum_submenu_{plugin_slug} * fs_support_forum_url_{plugin_slug} * fs_connect_message_{plugin_slug} * fs_connect_message_on_update_{plugin_slug} * fs_uninstall_confirmation_message_{plugin_slug} * fs_pending_activation_message_{plugin_slug} * fs_is_submenu_visible_{plugin_slug} * fs_plugin_icon_{plugin_slug} * fs_show_trial_{plugin_slug} * * -------------------------------------------------------- * * Freemius actions collection: * * fs_after_license_loaded_{plugin_slug} * fs_after_license_change_{plugin_slug} * fs_after_plans_sync_{plugin_slug} * * fs_after_account_details_{plugin_slug} * fs_after_account_user_sync_{plugin_slug} * fs_after_account_plan_sync_{plugin_slug} * fs_before_account_load_{plugin_slug} * fs_after_account_connection_{plugin_slug} * fs_account_property_edit_{plugin_slug} * fs_account_email_verified_{plugin_slug} * fs_account_page_load_before_departure_{plugin_slug} * fs_before_account_delete_{plugin_slug} * fs_after_account_delete_{plugin_slug} * * fs_sdk_version_update_{plugin_slug} * fs_plugin_version_update_{plugin_slug} * * fs_initiated_{plugin_slug} * fs_after_init_plugin_registered_{plugin_slug} * fs_after_init_plugin_anonymous_{plugin_slug} * fs_after_init_plugin_pending_activations_{plugin_slug} * fs_after_init_addon_registered_{plugin_slug} * fs_after_init_addon_anonymous_{plugin_slug} * fs_after_init_addon_pending_activations_{plugin_slug} * * fs_after_premium_version_activation_{plugin_slug} * fs_after_free_version_reactivation_{plugin_slug} * * fs_after_uninstall_{plugin_slug} * fs_before_admin_menu_init_{plugin_slug} */ #endregion Hooks & Filters Collection -------------------------------------------------------------------- if ( ! class_exists( 'Freemius' ) ) { if ( ! defined( 'WP_FS__SDK_VERSION' ) ) { define( 'WP_FS__SDK_VERSION', $this_sdk_version ); } $plugins_or_theme_dir_path = fs_normalize_path( trailingslashit( $is_theme ? $themes_directory : WP_PLUGIN_DIR ) ); if ( 0 === strpos( $file_path, $plugins_or_theme_dir_path ) ) { // No symlinks } else { /** * This logic finds the SDK symlink and set WP_FS__DIR to use it. * * @author Vova Feldman (@svovaf) * @since 1.2.2.5 */ $sdk_symlink = null; // Try to load SDK's symlink from cache. if ( isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && is_object( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && ! empty( $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink ) ) { $sdk_symlink = $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink; if ( 0 === strpos( $sdk_symlink, $plugins_or_theme_dir_path ) ) { /** * Make the symlink path relative. * * @author Leo Fajardo (@leorw) */ $sdk_symlink = substr( $sdk_symlink, strlen( $plugins_or_theme_dir_path ) ); $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink = $sdk_symlink; update_option( 'fs_active_plugins', $fs_active_plugins ); } $realpath = realpath( $plugins_or_theme_dir_path . $sdk_symlink ); if ( ! is_string( $realpath ) || ! file_exists( $realpath ) ) { $sdk_symlink = null; } } if ( empty( $sdk_symlink ) ) // Has symlinks, therefore, we need to configure WP_FS__DIR based on the symlink. { $partial_path_right = basename( $file_path ); $partial_path_left = dirname( $file_path ); $realpath = realpath( $plugins_or_theme_dir_path . $partial_path_right ); while ( '/' !== $partial_path_left && ( false === $realpath || $file_path !== fs_normalize_path( $realpath ) ) ) { $partial_path_right = trailingslashit( basename( $partial_path_left ) ) . $partial_path_right; $partial_path_left_prev = $partial_path_left; $partial_path_left = dirname( $partial_path_left_prev ); /** * Avoid infinite loop if for example `$partial_path_left_prev` is `C:/`, in this case, * `dirname( 'C:/' )` will return `C:/`. * * @author Leo Fajardo (@leorw) */ if ( $partial_path_left === $partial_path_left_prev ) { $partial_path_left = ''; break; } $realpath = realpath( $plugins_or_theme_dir_path . $partial_path_right ); } if ( ! empty( $partial_path_left ) && '/' !== $partial_path_left ) { $sdk_symlink = fs_normalize_path( dirname( $partial_path_right ) ); // Cache value. if ( isset( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) && is_object( $fs_active_plugins->plugins[ $this_sdk_relative_path ] ) ) { $fs_active_plugins->plugins[ $this_sdk_relative_path ]->sdk_symlink = $sdk_symlink; update_option( 'fs_active_plugins', $fs_active_plugins ); } } } if ( ! empty( $sdk_symlink ) ) { // Set SDK dir to the symlink path. define( 'WP_FS__DIR', $plugins_or_theme_dir_path . $sdk_symlink ); } } // Load SDK files. require_once dirname( __FILE__ ) . '/require.php'; /** * Quick shortcut to get Freemius for specified plugin. * Used by various templates. * * @param number $module_id * * @return Freemius */ function freemius( $module_id ) { return Freemius::instance( $module_id ); } /** * @param string $slug * @param number $plugin_id * @param string $public_key * @param bool $is_live Is live or test plugin. * @param bool $is_premium Hints freemius if running the premium plugin or not. * * @return Freemius * * @deprecated Please use fs_dynamic_init(). */ function fs_init( $slug, $plugin_id, $public_key, $is_live = true, $is_premium = true ) { $fs = Freemius::instance( $plugin_id, $slug, true ); $fs->init( $plugin_id, $public_key, $is_live, $is_premium ); return $fs; } /** * @param array $module Plugin or Theme details. * * @return Freemius * @throws Freemius_Exception */ function fs_dynamic_init( $module ) { $fs = Freemius::instance( $module['id'], $module['slug'], true ); $fs->dynamic_init( $module ); return $fs; } function fs_dump_log() { FS_Logger::dump(); } } PK! Ohh$freemius/languages/freemius-hu_HU.monu[{ nS0 _ ! !!!'!+!J!j!r!!! !!!!!&!-"A" V"`"s"z""" "" " """" # #2=#!p######### ## $$:$A$U$ i$ v$$$$$ $ $$$$% %('%:P%%%%% % %%%% %%x&& ' -'7'K'S'm'''''' '' 'a(i(o(( ( (((( ( ( ((j(5c) )))))))-!*O*c*'{**7*** ++0+F+ N+X+1+. ,O,A,-1-G-BK-,-- --- .... 0. ;.G. W.c.y. ... ..... .....///$/@/ F/R/ [/ f/t/////-/U/B0a0h0 x00(0*0"011+:1*f1&1111.1 22=2E2 T2 _2j2s2 |22 2222 22233$3(313 93E3 W3b3q3333 33 33 3 33 33 44 (4 34>4S4f4C444-4-5 35@5'G5 o5y5555555555*5 6*6^=666666 6 666"66 7=W77 77-778+8*E8p8t888&8-899<L9U9K9(+:GT:#:%:):$;)5;_;q;>;;;$; <:<V<r<&<*<7<=1=O=3h======*>1;>m>>#>>>>>?6? K?X?w??????1?/@7@K@ \@ h@ u@ @@@C@@Q@IA YA fApAvA AA A A A A A A A A A B~E(GF pFu{FFG G G'G*G-G1G:GCGIG ]G hGtGGGGG;G3$HXHtHH HHHHH$HI I I >IIIZI rI*IIII J J J *J5J=JBJKJeJzJJJJJ JJJK+KDKYK uKKK'K KCK L&L+L PTPCP Q*Q3Q'HQpQ#Q2QQQR2R5R!QR"sRRRRRRR.S2S(T<TUU6UY;+h~W1HKsfcYicue+Z(a:O]gt=A1#_# o& /?[5v@}GF't24L"y!k_Zr[dNCy.nr@EEz X>BPI=k9MBjR$)<D^A0qS9m2U6F^$7-0LXo "d&x5VQl The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Licenses%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s rating%s ratings%s star%s stars%s time%s timesAPIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd another domainAdd-OnAdd-OnsAddressAddress Line %dAffiliationAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAmountAn unknown error has occurred.Anonymous feedbackApply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?Automatic InstallationAverage RatingAwesomeBillingBilling & InvoicesBlockingBlog IDBodyBusiness nameBuy a license nowBuy licenseCan't find your license key?CancelCancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %s...Cancelling the subscriptionChange LicenseChange OwnershipChange PlanChange UserCheckoutCityClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelete All AccountsDetailsDon't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest versionDownloadedEmailEmail addressEnter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsFileFilterFreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius is our licensing and software updates engineFull nameFunctionHave a license key?How to upload and activate?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI no longer need the %sI only needed the %s for a short periodIDIf you have a moment, please let us know why you are %sImportant Upgrade Notice:Install Free Version NowInstall NowInstall Update NowInstalling plugin: %sInvoiceIs ActiveIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It's not what I was looking forJoin the Beta programKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLocalhostMessageMethodModule PathMore information about %sNameNewNew Version AvailableNewsletterNextNoNo IDNo credit card requiredO.KOpt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPayPal account email addressPaymentsPlanPlan IDPlease contact us herePlease contact us with the following message:Please enter the license key that you received in the email right after the purchase:Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium versionPremium version already active.PricingPrivacy PolicyProcess IDProcessingProductsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRenew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sScreenshotsSearch by addressSecret KeySelect CountrySend License KeySingle Site LicenseSite IDSitesSkip & %sSlugStart TrialStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you!Thanks %s!The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a new version of %s available.TimestampTitleTotalTownTrialTypeUnlimited LicensesUnlimited UpdatesUp to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUsersValueVerifiedVerify EmailView detailsView paid featuresWarningWe made a few tweaks to the %s, %sWebsite, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?WordPress.org Plugin PageWould you like to proceed with the update?YesYou are all good!You are just one step away - %sYou have a %s license.You have successfully updated your %s.Your %s license was successfully deactivated.Your account was successfully activated with the %s plan.Your email has been successfully verified - you are AWESOME!Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your trial has been successfully started.ZIP / Postal Codeactive add-onActiveadvance notice of something that will need attention.Heads upas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOncall to actionStart free trialclose a windowDismissclose windowDismissdeactivatinge.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,installed add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingtrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradeProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Peter Ambrus, 2018-2019,2021 Language-Team: Hungarian (Hungary) (http://www.transifex.com/freemius/wordpress-sdk/language/hu_HU/) Language: hu_HU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js A(z) %1$s fizetős verziója sikeresen telepítve. Kérlek aktiváld, hogy a(z) %2$s minden funkciója elérhető legyen. %3$s"%s" aktiválásának a befejezése most%s licenszA(z) %s egy prémium kiegészítő. A bővítmény aktiválásához előbb egy érvényes licenszt kell vásárolnod.%s értékelés%s értékelés%s csillag%s csillag%s%sAPI←➤FiókFiók információkEseményekAktiválás%s aktiválása%s csomag aktiválása%s funkciók aktiválásaIngyenes verzió aktiválásaLicensz aktiválásaLicensz aktiválása a hálózat minden függő aloldalán.Licensz aktiválása a hálózat minden aloldalán.Kiegészítő bekapcsolásaSikeres aktiválásMásik domain hozzáadásaKiegészítőKiegészítőkCímCím %dAjánlórendszerLicensz elfogadása és aktiválásaMinden kérésMinden típusEngedélyezés és folytatásMennyiségIsmeretlen hiba.Névtelen visszajelzésJelentkezés ajánló partnernekBiztosan törölsz minden Freemius adatot?Biztosan ezt szeretnéd?Automatikus telepítésÁtlagos értékelésNagyszerűSzámlázásSzámlázásBlokkolásBlog IDBodyCégnévVásárolj licenszet mostLicensz vásárlásaNem találod a licensz kulcsod?MégsemTelepítés törléseElőfizetés törlésePróbaidő törléseTörölve%s törlése...Előfizetés törléseLicensz módosításaTulajdonos módosításaCsomag módosításaFelhasználó módosításaPénztárVárosAPI gyorsítótár törléseÁtmeneti frissítési adatok törléseKattints ideKattints ide, ha névtelenül szeretnéd használni a bővítménytTermékekKódKompatibilitás:KapcsolatÍrás az ügyfélszolgálatraKapcsolatKözreműködőkA(z) %s aktiválása nem sikerült.OrszágDátumDeaktiválásLicensz deaktiválásaA(z) %s deaktiválása vagy törlése automatikusan törli az oldalhoz tartozó licenszed is, amit így másik weboldalon tudsz újra aktiválni.A licensz deaktiválása után a prémium funkciók használata nem elérhető, de így tudod másik weboldalon aktiválni ugyanezt a licenszt. Folytatod a deaktiválást?DeaktiválásHibakeresési naplóMinden fiók törléseRészletekNincs még licensz kulcsod?Bővítmény támogatásaElőfizetés módosítása kisebbreLetöltés%s verzió letöltéseFizetős verzió letöltéseTöltsd le a legfrissebb verziótLetöltések száma:EmailEmail címAdd meg az email címet, amit a vásárlás során használtál és újraküldjük a licensz kulcsot.HibaHiba érkezett a szervertől.LejártHátralévő idő: %sTovábbi domainekFájlSzűrőIngyenesIngyenes próbaidőIngyenes verzióFreemius APIFreemius hibakeresésA Freemius SDK nem találja a bővítmény fő fájlját. Kérlek írj az sdk@freemius.com email címre a problémával kapcsolatban.A licenszkezelést és szoftver frissítést a Freemius biztosítjaTeljes névFunkcióVan licensz kulcsod?Hogyan kell feltölteni és aktiválni?Nem tudom tovább fizetniNem értettem, hogy kell használniNem szeretném megosztani veletek az információtJobb %st találtamNincs tovább szükségem ráCsak egy kis időre használtamIDKérlek mondd el, miért %sFontos frissítési információ:Ingyenes verzió telepítése mostTelepítés mostFrissítés telepítése mostBővítmény telepítése: %sSzámlaAktívEz egy ügyfélnek készült weboldal? %s ha szeretnéd inkább elrejteni az admin felületről az érzékeny információkat, mint például email cím, licensz kulcs, árak, számlázási adatok és számlák.Úgy tűnik, hogy a licensz nem aktiválható.Úgy tűnik a licensz kikapcsolása nem sikerült.Úgy tűnik, hogy még mindig a %s csomagban vagy. Ha biztosan előfizettél vagy csomagot váltottál, akkor valószínű a hiba a mi oldalunkon van. Elnézést!Úgy tűnik, hogy a weboldalhoz nem tartozik aktív licensz.Nem ezt kerestemJelentkezz a Béta programbaKulcsHa elmondod mi nem működött, ki tudjuk javítani a leendő felhasználók számára...Ha elmondod az okát, tudunk fejlődni.UtolsóUtolsó frissítésLegfrissebb ingyenes verzió telepítveLegfrissebb verzió telepítveBővebbenHosszLicenszLicensz szerződésLicensz IDLicensz kulcsLicensszel kapcsolatos problémák?Licensz kulcsA licensz kulcs üres.ÖrökLocalhostÜzenetMódModul útvonalTovábbi információNévÚjÚj verzió érhető elHírlevélKövetkezőNemNincs IDBankkártya megadása nem kötelezőRendbenFeliratkozásLeiratkozásIratkozz fel, hogy a(z) %s még jobb lehessen!EgyébTulajdonos email címeTulajdonos IDTulajdonos nevePCI megfelelésPayPal fiók email címeFizetési módokCsomagCsomag IDÍrj nekünk ittKérlek írj nekünk a következő üzenettel:Kérlek add meg a licensz kulcsot, amit emailben kaptál a vásárlásod után:Kérlek add meg a teljes neved!BővítményBővítmény oldalaBővítmény IDBővítmény telepítéseVáltoztatásokLeírásGYIKFunkciók & ÁrakTelepítésEgyéb megjegyzésekVéleményekBővítményekBővítmények és sablonok szinkronizálásaPrémium%s prémium verziója sikeresen aktiválva.Prémium verzióA prémium verzió már aktív.ÁrakAdatkezelési tájékoztatóMűvelet IDFeldolgozás alattTermékekTartományPublikus kulcsLicensz vásárlásaVásárlás folytatásaGyors visszajelzésRendelkezésre állAktivációs email újraküldéseLicensz megújításaLicensz kulcs megújításaKérésekA következő WordPress verzió szükséges:EredménySDKSDK útvonal%s mentéseKépernyőfotókKeresés cím alapjánTitkos kulcsVálaszz országotLicensz kulcs küldéseEgy weboldalas licenszWeboldal IDWeboldalakKihagyás & %sKözvetlen hivatkozásPróbaidő indításaMegyeKüldés & %sElőfizetésÜgyfélszolgálatTámogató fórumAdatok szinkronizálása a szerverrőlKözösségi adószámSzolgáltatási feltételekKöszönjük!Köszönjük %s!A(z) %s hibát generált az oldalonA(z) %s nem működöttA %s nem az elvárásoknak megfelelően működöttA(z) %s nagyszerű, de nekem olyan funkcióra van szükségem, amit nem tudA(z) %s nem működikA(z) %s egyszer csak nem működöttA(z) %s frissítése sikeres volt.SablonSablon váltásSablonokA(z) %s új verziója érhető el.IdőbélyegCímÖsszesenTelepülésPróbaidőTípusKorlátlan licenszKorlátlan frissítésWeboldalak száma: %sFrissítésLicensz frissítéseFrissítések, közlemények, marketing, de semmi SPAM!ElőfizetésTöltsd fel és aktiváld a letöltött verziótFantasztikusFelhasználó vezérlőpultFelhasználó IDFelhasználókÉrtékEllenőrzöttEmail ellenőrzéseRészletek megtekintéseFizetős funkciók megtekintéseFigyelmeztetésNéhány frissítést kapott a %s. %sWeboldal, email és közösségi média statisztikák (opcionális)Köszönjük, hogy a(z) %s szoftverünket választottad! Kérlek add meg a licensz kulcsod: Mire számítottál?Melyik funkcióra van szükséged?Mi a te %s?Mi lenne az elfogadható ár, amit tudnál fizetni?Pontosan mit kerestél?Mi a %s neve?WordPress.org bővítmény oldalSzeretnéd folytatni a frissítést?IgenMinden rendben!Már csak egy lépés van hátra - %sLicensz típusa: %sA(z) %s sikeresen frissítve.A(z) %s licensz kikapcsolása sikerült.A fiókodat sikeresen aktiváltuk a következő csomaggal: %sAz email címedet sikerült ellenőrizni - ez nagyszerű!A licensz kikapcsolásra került. Ha úgy gondolod, hogy ez tévedés volt, kérlek írj az ügyfélszolgálatra!A licensz lejárt. A(z) %s ingyenes verziója továbbra is használható korlátlanul.A licensz sikeresen aktiválva.A licenszedet sikeresen deaktiváltuk, az aktuális csomagod: %sA neved sikeresen frissítettük.Az előfizetés sikeresen aktiválva.Az előfizetés sikeresen módosítva: %s.Az előfizetés sikeresen frissítve.A próbaidőszakodat sikeresen aktiváltuk.IrányítószámAktívFigyelemAktiválásévAPIMégsemHibakeresésGratulálunkBlokkolvaKapcsolódvaLegfrissebb verzió letöltéseA legfrissebb ingyenes verzió letöltéseHaviLejáratÚtvonalEmail küldésehóÉvesÉvesEgyszeriCsomagNincs titkos kulcsSDK verziókLicenszSzinkronizálásLicensz szinkronizálásaSzerzőKiBeIngyenes próbaidőszak indításaMégsemMégsemdeaktiválod%s csomag%s számlázásLegjobbÜdvHoppáÜdv %s!TelepítveJuhuuulicenszWeboldalakúj Béta verzióúj verziónem ellenőrzöttÁrÁrakválaszthatóVerziókérek biztonsági és funkcionális frissítéseket, használati ismertetőket és ajánlatokat.ugrásHmmpróbaidő indításaelőfizetésváltaszpróbaidőPróbaidőTörlésVáltás kisebb csomagraSzerkesztésElrejtFeliratkozásLeiratkozásVásárlásMutasdUgrásFrissítésVáltás nagyobb csomagraPK!!freemius/languages/freemius-ja.monu[<\x(Sy(%( ( ( ))6%)\)_*#q** * * ******H*C+OV++++++ +++,&,->,l, ,,,,,,5,-- - )-'5-]- v- --o-...."./2#/!V/ax/0/ 0040C0K0_0g0p0x0 }00000 0 0Y0H1W1 h1t1}111(111%2:,2g2l2}22 2 222 22 222 3 33333344%4D4 `4k4[5f_55 55Y5a86666 6 6;6 7%7,7+8 08 ;8 H8U8jd88 8838%9~99U9:*:C:)^:-::S:;'6;^;Ma;7;g;pO<<<< < ==4=G= O=1Y=.=O= >A>y>F?af??B?,@<@ A@ N@[@y@ @@@ @ @@@4@A 'A1A5AL&CLLjLfLM $M0MAM GM SM`M hMvM MMM@N/NO)'O QO \O\gOOOOC POPePPd Q-pQQ QQ'QMQG.R vRRRRRRERRRSS-S4S*CSnS*vS^STTTdTyT TT TTTiTW:U"UBU6U/V DVRV-cVVV&VVW W$WM8WW/WW>W'X&>XZeXTXRY.hY.Y9YZZ3[Z<ZbZP/[U[_[6\K\(]GF]#])]$])^+^=^Z^;o^6^>^!_'_B_b_$x_____& `*4`7_````3`a.aDa\apa*a1aab#b>bZblb |bbbb bbNb9cXcvcccc1cc d d &d 1d>d VdCbddQdd ee2e EeQe `e je te e e e e e e ee3si6iiij jk$jjzk$l6lTlllul~l l lllnlmk)mmmmm mmm$n,nEHnZnnoo +oLo ko xoaoo oopE'p'mpppppqqOr3nrQr$rAs%[sjsTsAtTtmt tt tt ttt1tu*-u3Xu!uuu_vxvvvvvBvEwAawKww w' x1x8xNxUx!qxxxx xxx yy!y!yz:z$Xz}z'z"zzz{}{m|||}}} 8~3B~ v~~~T~ I!kr ǀT+2c'X6$<܃0$Jo%6T9Mm܅J6?$d$("· 99<ovH*W~B E*T$Ռ] !k   $)ELh'KBBX \fK|Bȏ3 ?R>bDT gq U. CCM H< !9.Hh-^ߓ>Z!|:-ʖ$-$@ ery} f'$7\HlW H, uԙ  #0vOƚߚ& $ 2?Ws Bu֛$L q~!B$ '34 hu ;iН: !- IV'r >O/<!8*!-]3'أ3E y ;q٥tK ֦  ]l!Ƨاߧ92KH  ¨ Zg1wةߩ.yHZ«"@ W;x"׬A)7ah0ol ;$ `f*0Deׯr=NB=BKNbrfٳar3wT3449i3׷ ?K;  ȸ ظ6 B FSZmqx й׹ !3: ANU պ   $ 1> AK R\lup  1O_f|  ˼ҼG4@v_(I)j`f258zEBqd Y~Ah"av/  Xa7Uj|XgsWT+y9\@'y3K} _D-7[ pHhq\os/ rkFO)iRYw<2F+UB=H:lG=nM`1;60^P-x' %t?u}6kK5. S1mfWLE<tNw0*!"lN;#.gZ!QAJ[bZRniJ8L&M](>CcPzc>&?3V],eQu,4CS*%m#|d{e:xI{DV9 b^  ~rTOo$p $%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.APIAccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBillingBlockingBlog IDBodyBusiness nameCan't find your license key?CancelCancel InstallationCancel SubscriptionCancel TrialCancelledCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClear Updates TransientsClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't have a license key?Donate to this pluginDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.During the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.In %sInstall Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense KeyLicense keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Opt InOpt OutOtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProcess IDProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicenseQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageYesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlike websitesSitesmillisecondsmsnot verifiednounPricenounPricingproduct versionVersionsecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialswitchingthe latest %s version heretrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Tomohyco Tsunoda, 2018 Language-Team: Japanese (http://www.transifex.com/freemius/wordpress-sdk/language/ja/) Language: ja MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js すぐに "%s" 有効化を完了してください%s のアドオンの支払いが完了しました。%sインストール%sラインセス%s 前%sとそのアドオンカスタマーが新規ライセンスを購入するごとに%sのコミッションが発生します。%s の無料試用が正常にキャンセルされました。 アドオンはプレミアムなので、自動的に無効化されました。 将来使用したい場合は、ライセンスを購入する必要があります。%s はプレミアムのみのアドオンです。そのプラグインを有効化する前にライセンスを購入する必要があります。%s は新しいオーナーです。%sお支払いの最低金額%sまたはそれ以上%s評価%s評価%s秒%sスター%sスター%s回%s回%s初回の訪問後、クッキーをトラッキングして収益の可能性を最大化しましょう。%sの有料機能%sここをクリックして%s ライセンスを有効化したいサイトを選択してください。APIアカウントアカウント詳細アクション有効化%sを有効化する%s プランを有効化フリーバージョンを有効化ライセンスを有効化保留中のサイトすべてでライセンスを有効にする。ネットワーク上にあるすべてのサイトのライセンスを有効にする。このアドオンを有効化有効化済み%s のアドオンモジュールのアドオン%sドメイン名を追加するアドオンアドオンアドオンが WordPress.org か Freemius にデプロイされている必要があります。住所住所欄 %dアフィリエイトアフィリエイト無料の %s の後は、わずか %s だけお支払ください。同意してライセンスを有効化すべてのリクエストすべてのタイプ許可して続けるまたは、今すぐスキップして、%sのネットワークレベルのアカウントページでライセンスを有効にすることもできます。総額%sから %s (有料版) の自動ダウンロードと自動インストールが%sで開始します。手動で行う場合は今すぐにキャンセルボタンをクリックしてください。匿名のフィードバック保留中のサイトすべてに反映させる。ネットワーク上にあるすべてのサイトに対して反映させる。アフィリエイトに応募するほんとうに全ての Freemius データを削除しますか?本当に続行していいですか?30日間の返金期間があるため、コミッションのお支払いは30日以降になります。自動インストールはオプトインしたユーザのみで動作します。%s に自動更新自動インストールレーティングの平均すごい!アフィリエイトになる請求書ブロッキングブログ ID本文商号ライセンスキーは見つかりませんか?キャンセルインストールをキャンセルするサブスクリプションをキャンセルするトライアルをキャンセルキャンセルトライアルをキャンセルするとすぐにすべてのプレミアム機能へのアクセスができなくなります。本当に実行しますか?ライセンスを変更オーナーを変更プラン変更チェックアウト市API キャッシュをクリアアップデートのトランジエントをクリアーにする匿名でプラグインを使用するにはこちらをクリッククリックして%sの評価をしているレビューを観るクリックしてフルサイズのスクリーンショットを見る %dプロダクトコード互換性のある最新バージョン連絡サポートに連絡連絡コントリビューター%s を有効化できません。国Cron タイプ日付無効化ライセンスを無効化ライセンスを無効化するとすべてのプレミアム機能が使えなくなりますが、他のサイトでライセンスを有効にすることができるようになります。本当に実行しますか?無効化デバッグログサイト管理者に委任する全てのアカウントを削除詳細ライセンスキーをお持ちではありませんか?このプラグインに寄付するダウンロード%s バージョンをダウンロード最新の %s をダウンロード最新版をダウンロードダウンロード済みアフィリエイト規約違反により、アフィリエイトアカウントを一時的に凍結させていただきました。ご質問等がありましたら、サポートにお問い合わせください。アップデートの処理中に%dサイトがライセンスの有効化が保留中であることを検知しました。アップデートの処理中に、ネットワーク内の%dサイトが対応待ちになっていることを検知しました。Emailメールアドレス終了%sのプロモーションを行う予定のあなたのサイトや他のサイトのドメイン名を入力してください。アップグレードに使用したメールアドレスを下に入力してください。そうすれば、ライセンスキーをお送りします。エラーサーバーからエラーを受信しました。期限切れ%s で期間終了追加のドメイン名プロダクトフォームのマーケティングを行う追加ドメイン名。ファイルフィルターWordPress.orgのガイドラインに準拠するため、トライアルを開始する前に、ユーザーと重要でないサイト情報のオプトイン、更新の確認やトライアルの状態確認のために%sが%sに対して定期的にデータを送信する許可を得るように設定してください。無料フリートライアルフリーバージョンFreemius APIFreemius デバッグFreemius SDK がプラグインのメインファイルを見つけることができませんでした。現在のエラーを添えて sdk@freemius.com に連絡してください。Freemius ステータスフルネーム機能サブスクリプションの自動更新でコミッションを得ましょう。ライセンスキーはお持ちですか?こんにちは。%sにアフィリエイトプログラムがあるのはご存知でしたか? %sがお好きなら、私たちのアンバサダーになって報酬を得ましょう!%s はどうですか? 私たちの全ての %s のプレミアム機能をお試しください。アップロードと有効化の方法どのように我々をプロモートしますか?もう払うことができませんどうしたら動作するか分かりませんでした。自分の情報を共有したくありませんより良い %sを見つけましたアカウントをアップグレードしましたが、ライセンスを同期しようとするとプランが %s のままです。%sはもう不要です短期間だけ %sが 必要です。ID決定をサイトの管理者に委任するにはクリックしてください。お時間があれば、なぜ%sするのか理由を教えてください。%sの所有権を%sへ譲りたい場合は、所有権の変更ボタンをクリックしてください。これらのサイトで%sを使う場合は、ライセンスキーを入力し、アクティベーションボタンをクリックしてください。%s 内フリーバージョンを今すぐインストールフリーバージョンの更新を今すぐインストール今すぐインストール今すぐ更新をインストールインストール中プラグイン: %sモジュール ID が不正です請求書有効ライセンスの有効化ができませんでした。ライセンスの無効化ができませんでした。すでにトライアルモードではないようなので、キャンセルする必要はありません :)まだ %s プランのようです。もしアップグレードやプランの変更をしたのなら、こちらで何らかの問題が発生しているようです。申し訳ありません。サイトは有効なライセンスを持っていないようです。認証パラメータの1つが間違っているようです。 公開鍵、秘密鍵、ユーザーIDを更新して、もう一度お試しください。探していたものではありません%s のアドオンに関する情報は、外部サーバーから取得されます。キー将来のユーザーのために修正できるよう、何が動作しなかったのかどうか共有してください…改善できるよう、どうか理由を教えてください。最終最終更新最新のライセンス最新のフリーバージョンがインストールされました最新版がイストールされました詳細はこちら長さライセンスライセンスキーライセンスキーライセンスキーが空です。ライフタイム%sは気に入りましたか? アンバサダーになって報酬を得ましょう ;-)DB オプションを読み込むlocalhostグロガーメッセージメソッドモバイルアプリケーションモジュールモジュールのパスモジュールタイプ%s に関する詳細情報名前ネットワークブログネットワークユーザ新規新しいバージョンがあります新しいフリーバージョン (%s) がインストールされました新しいバージョン (%s) がインストールされましたニュースレター次いいえID がありません%s の拘束はありません。いつでもキャンセルできます。%s 日以内であればいつでもキャンセルできます。クレジットカードは必要ありません。有効期限なし期限のない%sのプランにはトライアル期間はありません。O.K一度ライセンスの期限が切れると、フリーバージョンの利用は可能ですが、%sの機能を使うことができなくなります。オプトインオプトアウトその他所有者の Emailオーナー ID所有者名PCI コンプライアント有料アドオンは Freemius にデプロイされている必要があります。PayPal アカウントのメールアドレス支払いお支払いは USD かつ PayPal 経由で毎月行われます。プラン%s プランは存在しないため、試用を開始できません。%s プランにはトライアル期間はありません。プラン IDこちらで私たちに連絡をとってください。以下のメッセージとともに私たちに連絡をください。%s をダウンロードしてください。購入後すぐにメールで受け取ったライセンスキーを入力してください:関係のあるウェブサイトやソーシャルメディアの統計を提供してください。例: サイトの月間訪問者数、Emailの購読者数、フォロワー数等 (機密情報として取り扱います)アップグレードを完了するには以下の手順を完了させてください。セキュリティや機能のアップデート、学習用用コンテンツ、およびオファーについてお問い合わせを希望される場合は、お知らせください。どのように%sをプロモートするつもりなのか、詳細をお知らせください (できるだけ具体的にお願いします)フルネームを入力してください。プラグインプラグインのホームページプラグイン IDプラグインのインストール変更履歴説明FAQ機能 & 料金インストールその他の記述レビュープラグインはプレミアムコードバージョンのない「サービスウェア」です。プラグインプラグインとテーマを同期プレミアムプレミアムバージョンの %sは有効化に成功しました。プレミアムアドオンバージョンはすでにインストール済みです。プレミアムバージョンプレミアムバージョンはすでに有効になっています。料金表プライバシーポリシープロセス IDプロダクトプログラム概要プロモーション方法県・州・省公開鍵ライセンスを購入クォータ有効化メールを再送信新規カスタマーに私たちの%sを紹介して、売り上げごとに%sのコミッションを得ましょうライセンスを更新リクエスト数必要な WordPress のバージョン結果SDKSDK のパス%s を保存スケジュール Cronスクリーンショット住所で検索する秘密鍵外部ドメインで実行中のセキュアな HTTPS %sページトライアルのキャンセルに一時的な問題がありました。数分後に再度お試しください。最新版を取得できました。国を選択ライセンスキーを送信DB オプションを設定するネットワークアップグレードをシミュレートするシングルサイトライセンスサイト IDサイトのオプトインに成功しました。サイト数スキップと%sスラッグソーシャルメディア(Facebook、Twitter、その他)ご迷惑をおかけしてすいません。もし機会をいただけたらお手伝いをします。メールアドレスのアップデートを完了できませんでした。他のユーザーがすでに同じメールアドレスで登録しているようです。開始トライアルを開始無料の %s を開始州送信と%sサブスクリプションサポートサポートフォーラムサーバーからのデータを同期税金 / VAT ID利用規約アフィリエイトアカウントに応募いただきありがとうございます。残念ながら現時点では申請を受理することができませんでした。30日後に改めてお申込みください。アフィリエイトプログラムに応募いただきありがとうございます。14日以内にお申し込み詳細をレビューし、改めてご連絡いたします。%sとアドオンのご利用ありがとうございます!%sのご利用ありがとうございます!プロダクトのご利用ありがとうございます!ありがとうございます!ありがとう $s さん!所有権の変更を確認していただきありがとうございます。 %s に承認メールが送信されました。%s の影響でサイトを崩れました%s が動作しませんでした%sが期待通りに動きませんでした %s は素晴らしいのですが、サポートされていないある機能が必要です%s が動作していません%s の動作が突然停止しましたインストールプロセスが開始され、数分で完了します。完了までしばらくお待ちください。ページのリフレッシュなどは行わないでください。リモートプラグインパッケージには、目的のスラッグを含むフォルダが含まれていないため、リねームが機能しませんでした。%s のアップグレードが完了しました。テーマテーマ変更テーマ%sの入手可能な新しいバージョンがありますこのプラグインはインストールされた WordPress のバージョンに互換性がありません。このプラグインはインストールされた WordPress のバージョンでは検証されていません。タイムスタンプタイトルトータル町トライアルタイプファイルシステムに接続できません。視覚情報を確認してください。無制限ライセンス無制限のアップデート無制限のコミッション。%sサイトまで更新ライセンスを更新更新、発表、マーケティング、スパムなしアップグレードダウンロードしたバージョンをアップロードして有効化やったーユーザー IDユーザー値%s に確認メールを送信しました。もし5分以内にそれが届かない場合、迷惑メールボックスを確認してください。認証済み認証メールバージョン %s をリリースしました。詳細を表示有料の機能を表示する警告メールアドレスに関連付けられた有効なライセンスが見つかりません。メールアドレスが正しいか確認してください。システムではメールアドレスを見つけることができませんでした。メールアドレスが正しいか確認してください。プラグインを微調整します、 %s, %sFreeminus ネットワークレベルのインテグレーションをご紹介できることに興奮しています。ウェブサイト、Email またはソーシャルメディアの統計 (オプション)何を期待していましたか?何の機能ですか?自分の %s はなんですか? 支払ってもよいと思う価格はいくらですか?探していたのは何ですか?%sの名前は何ですか?%sのプロモーションを行うサイトはどこですか?WordPress.org のプラグインページはいはい以前すでに試用版を利用しました。%2$s プランの%1$s日間のフリートライアルを開始するまであとワンクリックです。すべて完璧です!すでに%sをトライアルモードで利用中です。もうあとわずかです - %sプレミアムバージョンにアクセスできる有効なライセンス持っていません。%s ライセンスを持っています。%s のアップデートが成功しました。見逃していたかもしれませんが、どんな情報も共有する必要はなく、オプトインを $s することができます。 %sの改善に役立つ使用状況のトラッキングにすでにオプトインしています。プロダクトの改善に役立つ使用状況のトラッキングにすでにオプトインしています。%s のアドオンのプランのアップグレードが完了しました。%s のフリートライアルはキャンセルされました。アカウントが %s プランで有効化できました。%sのアフィリエイト申請は受理されました! 次のリンクからアフィリエイトエリアにログインしてください:%sアフィリエイトアカウントは一時的に停止されました。あなたのメールアドレスの承認が完了しました。すごい!フリートライアル期間が終了しました。%1$s %3$sに邪魔されずに利用を継続するには,今すぐ %2$s のアップグレードを行ってください。フリートライアル期間が終了しました。無料で使える機能は引き続き利用可能です。ライセンスはキャンセルされました。もしそれが間違いだと思うならサポートに連絡してください。ライセンスの有効期限が切れました。 %1$s %3$sに邪魔されずに利用を継続するには,今すぐ%2$sアップグレードを行ってください。ライセンスは有効期限がきれました。%s の機能を引き続き利用することができます。ただし、アップデートやサポートをうけるにはライセンスをアップデートする必要があります。ライセンスの有効期限が切れました。無料バージョンの%s は引き続き利用できます。ライセンスの有効化が成功しました。ライセンスの無効化が完了しました。%s プランに戻りました。名前のアップデートが成功しました。プランの %s への変更が成功しました。プランのアップグレードが成功しました。トライアル版の利用を開始しました。ZIP / 郵便番号そうだ有効%s は、%s が無いと実行することができません。%s は、プラグインが無いと実行することができません。警告許可あと %s有効化中年API却下デバッグおめでとうブロック接続最新版をダウンロード最新のフリーバージョンをダウンロード月期限切れパスメール送信中月年次毎年一度プラン秘密鍵がありませんSDK バージョンライセンス同期ライセンスを同期作者オフオン%sを基にフリートライアルを開始却下却下無効化中代表セキュリティと機能のアップデート、学習用コンテンツやオファーを%s送らないでください%s。%s プラン%s への請求ベストヘイおっとおおい %s さん、ヤッホーサイト数ms未認証料金料金表バージョン秒セキュリティと機能のアップデート、学習用コンテンツやオファーを送ってください。スキップふむトライアルを開始変更中最新の %s バージョンはこちらです。トライアル削除ダウングレード編集非表示オプトインオプトアウト購入表示スキップ更新アップグレード%s 前PK!냉11$freemius/languages/freemius-he_IL.monu[|ep&Sq&%& & &'6 'A'_'#V(z( ( ( ((((((H((),)4)D)L) U)a)r))&)-)) * **1*D*K*5S*** * *'** * ++ +'+++"+,25,!h,a,,,-$-,-@-H-Q-Y- ^-l---- - -Y-).8. I.U.^.c.(s.1.%.:.//4/E/M/ ]/ h/u// // /// U0 b0l00000000 1 (1311 11Y1a=2222 2 2;2%3*31304 54 @4 M4Z4ji44 4434*5~>5U56/6H6)c6-66S6#7';7c77f7g78 8%8 E8Q8d8w8 818.8O8:9A9y92v::a:+;B/;,r;; ; ;;; ;<< < <'<=<4F<{< <<<<< << < <<< <= == <=G=L=O=%U=+{== = =/= >m>|>>> >> > >)>>?4?D?5I?(???-??U @_@1(A[ZAAAA AA(B*.B"YB1|B+B*B&C,C4CJC.RCCCCC C CCCC D DD.D4DWMD DDDDDDDD EE !E5,ElbE&EEF F$F=FQFYF _FiF&nFLFfFIG OG[GlG rG ~GG GG GGGkHI)"I LI WI\bIIIICJJJ`JJ-K4K :KGK'NKMvKGK LLL"L'L-LE2LxLLLLLL*LM* M^7MMMMdMN N%N >NKN^NifNWN"(O6KOO OO-OOP&P*?PjPnP$wPMPP/P,Q>LQQ&QZQ.$R.SR9RZR3S<KSPSUS/TKT(UG?U#U)U$U)U$V6V;SV6V>VW W&WFW$\WWWWW&W*XCXZXxX3XXXXYY*9Y1dYYY#YYZZ (Z4ZTZkZ ZZNZZ["[=[M[^[1o[[[ [ [ [[ \C\R\QW\\ \\\ \\ ] ] ] ,] 9] G] Q] [] g] t])]#a(aa b b6 bWb_ c5lcc c cc c cd d dH(dqd udd d d dd$ddO e8\ee eeeeef5 f @f Kf Yfcf'vf$ffffg ggBg+}#}} }/} }m}k~r~z~~~~~)~~4+ ``k> &B:}mFρ[#r т  )4N ]j.*у  % 0 ;HXj}6W>V_z ƅ߅5'4܆ <W g r~&kf% &ڈ   $ELb<Å ,΋ߋCA%Tz-) Wb x%GB 6 @KQ XeEl ӎ 'S?8Տڏd hs'iWY"6ԑ 47F~&5̒(M9/˓XD$_5IE8Zȕ3#<W{_pK 'W>/ޘ'.6e &A  *1 5 @ KY bm  ˚Ԛ ݚ  -<V]fo ƛyϛ IW u  Ŝ Μۜu^e l $ ɝНٝ   %Ib=Qo@?^N5|9m,M41;</$]l&}n.*[qfpB+ e  Dmp43S{]*-=t T'j:'hPEP5b>"L!,K)"&Q~WXwGzk_>a+YXMSvl-[J6s $@iCcxcLZO.%1FtjWo\zA07I9J8rV<K}RdyY Cu(n# RB27H0UZ8FOTa#Vr^(3kG DUgfhsHwE q_ux?ey/i{!2:|d%A`vN;)6~ \`g%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s tracking cookie after the first visit to maximize earnings potential.APIAccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBillingBlockingBlog IDBodyBusiness nameCan't find your license key?CancelCancel InstallationCancel SubscriptionCancel TrialCancelledCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't have a license key?Donate to this pluginDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.In %sInstall Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInvalid module ID.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense KeyLicense keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMobile appsModuleModule PathModule TypeMore information about %sNameNetwork UserNewNew Version AvailableNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Opt InOpt OutOtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium versionPremium version already active.PricingPrivacy PolicyProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicenseQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSingle Site LicenseSite IDSitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWebsite, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?Would you like to proceed with the update?YesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlike websitesSitesmillisecondsmsnot verifiednounPricenounPricingproduct versionVersionsecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialswitchingthe latest %s version heretrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Vova Feldman , 2017-2018,2022 Language-Team: Hebrew (Israel) (http://www.transifex.com/freemius/wordpress-sdk/language/he_IL/) Language: he_IL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js השלם הפעלת "%s" עכשיוההרחבה %s נרכשה בהצלחה.%s התקנות%s Licensesלפני %s%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s הינו הבעלים החד של חשבון זה.%s minimum payout amount.%s ומעלהדרוג %s%s דרוגים%s שניותכוכב %s%s כוכביםפעם %s%s פעמים%s tracking cookie after the first visit to maximize earnings potential.APIחשבוןפרטי חשבוןפעולותהפעלהActivate %sהפעל חבילה %sהפעלת גירסה חינאמיתהפעלת רישיוןהפעלת רישיון על כל האתרים התלויים והעומדים.הפעלת רישיון על כל האתרים ברשת.הפעל את ההרחבהActivatedהרחבות עבור %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.כתובתכתובת %sAffiliateאפיליאציהAfter your free %s, pay as little as %sהסכמה והפעלת רישיוןכל הבקשותכל הסוגיםאפשר\י והמשכ\יסכוםהורדה והתקנה אוטומטית של %s (גרסה בתשלום) מ-%2$s תתחיל בעוד %3$s. אם ברצונך לבצע את ההתקנה ידנית - לחץ על כפתור הביטול עכשיו.פידבק אנונימייישום על כל האתרים התלויים והעומדים.יישום על כל האתרים ברשת.Apply to become an affiliateAre you sure you want to delete all Freemius data?האם את/ה בטוח רוצה להמשיך?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.עדכן אוטומטית בעוד %sהתקנה אוטומטיתדירוג ממוצעאדירBecome an affiliateבילינגBlockingמזהה בלוגBodyשם עסקהאם אינך מוצא את מפתח הרישיון?בטלבטל התקנהבטל מנויביטבוטלביטול הניסיון יחסום מייד את הפיטצ'רים שהינם בתשלום. האם ברצונך בכל זאת להמשיך?שינוי רישיוןעדכון בעלותשינוי חבילהCheckoutעירניקוי מטמון ה-APIClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dמוצריםCodeCompatible up toContactצור קשריצירת קשרתורמיםלא ניתן להפעיל את %s.מדינהCron Typeתאריךכיבוישיחרור רישיוןביטול הרישיון יחסום את כל הפיטצ'רים שבתשלום אך יאפשר להפעיל את הרישיון על אתר אחר. האם תרצו להמשיך בכל זאת?דיאקטיבציהDebug Logהאצלה למנהלי האתריםמחיקת כל החשבונותפרטיםהאם אין ברשותך מפתח רישיון?תרום לתוסףהורדההורד גרסת %sהורד\י את גרסת ה-%s העדכניתהורד את הגרסה האחרונהDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.דוא"לכתובת דוא"לEndEnter the domain of your website or other websites from where you plan to promote the %s.הזן את כתובת הדואל שאיתה שידרגת כדי לקבל את הרישיון שוב.שגיאההוחזרה שגיאה מהשרת:פג תוקףפג תוקף בעוד %sExtra DomainsExtra domains where you will be marketing the product from.קובץפילטרFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.חינםניסיון חינםגירסה חינאמיתFreemius APIניפוי תקלות פרימיוסFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.מצב פרימיוסFull nameפונקציהGet commission for automated subscription renewals.האם ברשותך רישיון?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.איך להעלות ולהפעיל?How will you promote us?אני לא יכול/ה להמשיך לשלם על זהלא הצלחתי להבין איך לגרום לזה לעבודאני לא אוהב את הרעיון של שיתוף מידע איתכםמצאתי %s יותר טובשידרגתי את החשבון שלי אבל כשאני מנסה לבצע סנכרון לרישיון החבילה נשארת %s.I no longer need the %sI only needed the %s for a short periodמזההIf you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.בעוד %sהתקן גרסה חינאמית עכשיוהתקן עדכון גרסה חינאמית עכשיוהתקן עכשיוהתקן עדכון במיידימזהה המודול לא תקני.חשבוניתהאם פעילנראה שלא ניתן להפעיל את הרישיון.נראה שניתוק הרישיון נכשל.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.נראה לאתר עדיין אין רישיון פעיל.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.זה %s זמני - אני מנסה לפתור בעיהחיפשתי משהו אחרJust letting you know that the add-ons information of %s is being pulled from an external server.Keyאנא שתפ\י מה לא עבד כדי שנוכל לתקן זאת עבור משתמשים עתידיים...אנא שתף את הסיבה כדי שנוכל להשתפר.Lastעודכן לאחרונהרישיון אחרוןגרסה חינאמית עדכנית הותקנההגרסה האחרונה הותקנהLearn moreLengthרישיוןLicense Keyמפתח רישיוןמפתח הרישיון ריק.לכל החייםLike the %s? Become our ambassador and earn cash ;-)Load DB Optionשרת לוקאליLogLoggerהודעהMethodMobile appsמודולModule Pathסוג מודולמידע נוסף אודות %sשםמשתמש רשתחדשיש גרסה חדשהגרסה חדשה (%s) הותקנהניוסלטרNextלאאין מזההNo commitment for %s - cancel anytimeללא התחייבות ל-%s ימין - בטלו בכל רגע!לא נדרש כרטיס אשראיללא תפוגהNon-expiringNone of the %s's plans supports a trial period.אוקייOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Opt InOpt Outאחרמייל הבעליםמזהה הבעליםשם הבעליםעומד בתקן PCIPaid add-on must be deployed to Freemius.PayPal account email addressתשלומיםPayouts are in USD and processed monthly via PayPal.חבילההחבילה %s אינה קיימת, לכן, לא ניתן להתחיל תקופת ניסיון.תוכנית %s אינה תומכת בתקופת ניסיון.Plan IDאנא צור איתנו קשר כאןאנא צור איתנו קשר יחד עם ההודעה הבאה:נא להוריד את %s.אנא הזן את הרישיון שקיבלת לתיבת הדואל שלך לאחר השלמת הרכישה.Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).נא לבצע את הצעדים הבאים להשלמת השידרוגPlease provide details on how you intend to promote %s (please be as specific as possible).נא למלא את שמך המלא.תוסףעמוד התוסףPlugin IDהתקנת תוסףלוג שינוייםתיאורשאלות נפוצותפיטצ'רים ומחיריםהתקנההיערות נוספותביקורותתוספיםPlugins & Themes SyncPremiumPremium %s version was successfully activated.גירסת פרימיוםהגרסה בתשלום כבר פעילה.מחירוןמדיניות פרטיותProcess IDProcessingמוצריםProgram SummaryPromotion methodsפרובינציהמפתח פומביקניית רישיוןQuotaשליחה חוזרת של מייל האקטיבציהRefer new customers to our %s and earn %s commission on each successful sale you refer!חידוש רישיוןRequestsRequires WordPress VersionResultSDKמיקום SDKשמירת %sScheduled Cronsצילומי מסךחפש לפי כתובתמפתח סודיSecure HTTPS %s page, running from an external domainנראה שיש תקלה זמנית המונעת את ביטול הניסיון. אנא נסו שוב בעוד כמה דקות.נראה שיש לך את הגרסה האחרונה.בחר מדינהשליחת מפתח רישיוןSet DB Optionסמלוץ עדכון לרשתרשיון לאתר אחדמזהה אתראתריםדלג ו%sמזהה כתובתSocial media (Facebook, Twitter, etc.)מצטערים על חוסר הנעימות, אנחנו כאן כדי לעזור אם תאפשר\י זאת.Sorry, we could not complete the email update. Another user with the same email is already registered.Startהתחל תקופת ניסיוןהתחל את %s הניסיון שלימחוז/מדינהSubmit & %sמנויתמיכהפורום תמיכהסנכרון מידע מהשרתח.פ.תנאי השירותThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.אנו מודים לך על היותך כמשתמש של %s!אנו מודים לך על השימוש במוצרים שלנו!תודה רבה!תודה %s!תודה על אישור ביצוע החלפת הבעלות. הרגע נשלח מייל ל-%s כדי לקבל אישור סופי.ה%s הרס לי את האתרה%s לא עבדה%s לא עבד כמצופהThe %s is great, but I need specific feature that you don't supportה%s לא עובדה%s הפסיק פתאום לעבודתהליך ההתקנה התחיל ויכול לקחת מספר דקות לסיום. אנא המתינו בסבלנות עד לסיום מבלי לרענן את הדפדפן.The upgrade of %s was successfully completed.תבניתהחלפת תֵמָהתבניותיש גרסה חדשה עבור ה%s.התוסף לא סומן כתואם לגרסת הוורדפרס שלך.תוסף זה לא נבדק עם גרסת הוורדפרס שלך.TimestampכותרתTotalכפרניסיוןסוגUnable to connect to the filesystem. Please confirm your credentials.רשיונות ללא הגבלהעדכונים ללא הגבלהUnlimited commissions.עד %s אתריםעדכןעדכון רישיוןעדכונים, הכרזות, הודעות שיווקיות, ללא דואר זבלשדרגהעלה\י והפעיל\י את הגרסה שהורדתישמזהה משתמשמשתמשיםValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.מאומתאמת כתובת דוא"לגרסה %s הושקה.פרטים נוספיםצפה בפיטצ'רים שבתשלוםWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWebsite, email, and social media statistics (optional)למה ציפית?איזה פיטצ'ר?מה ה%s שלך?מה המחיר שכן תרגיש\י בנוח לשלם?מה חיפשת?What's the %s's name?Where are you going to promote the %s?האם ברצונך להמשיך עם העידכון?כןYes - %sהניסיון כבר נוצל בעבר.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.את\ה מסודר!You are already running the %s in a trial mode.You are just one step away - %sאין ברשותך רישיון בר תוקף לשימוש בגרסת הפרימיום.יש לך רישיון %s.עידכנת בהצלחה את ה%s.אולי פספסת את זה אבל אינך חייב\ת לשתף כל מידע איתנו, ביכולתך %s על שיתוף המידע.חבילת ההרחבה %s שודרגה בהצלחה.תקופת הניסיון החינמית של %s בוטלה בהצלחה.חשבונך הופעל בהצלחה עם חבילת %s.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!תקופת הניסיון שלך הסתיימה. הפיטצ'רים החינאמיים עדיין ניתנים לשימוש.רשיונך בוטל. אם לדעתך זו טעות, נא ליצור קשר עם התמיכה.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.הרישיון הופעל בהצלחה.רישיונך נותק בהצלחה, חזרת לחבילת %sשמך עודכן בהצלחה.החבילה עודכנה בהצלחה אל %s.החבילה שודרגה בהצלחה.הניסיון שלך הופעל בהצלחה.מיקוד / תא דוארמעולה%s לא יכול לעבוד ללא %s.ההרחבה %s אינה יכולה לפעול ללא התוסף.לתשמות לבךאפשרנשארו %sמפעילשנהAPIסגירהדיבוגמזל טובחסוםמחוברהורד גרסה אחרונהחודשיתפוגהנתיבשולח דוא"לחודשיםשנתישנתיפעם אחתחבילהאין מפתח סודיגרסאות SDKרישיוןסינכרוןסינכרן רישיוןAuthorכבוידלוקמבוסס על %sהתחלת ניסיון חינםסגירהסגירהdeactivatingהאצל%sאל%2$s תשלחו לי עדכוני אבטחה, פיטצ'רים, תוכן חינוכי, ומידע על מבצעים.חבילה %sמחוייב על בסיס %sהכי טובהייאופסהיי %s,ישששאתריםmsלא מאומתמחירמחירוןגרסהsecתשלחו לי עדכוני אבטחה ופיטצ'רים, תוכן חינוכי, ומידע אודות מבצעים.דלגאממהתחל תקופת ניסיוןswitchingגרסת ה-%s האחרונה כאןניסיוןמחקשנמךערוךהסתרOpt InOpt Outרכישההצגדלגעדכןשדרגלפני %sPK!  !freemius/languages/freemius-en.monu[$,*X8AY88n>929Z9h;:d:S ;%]; ; ;;;;6[<<_G===#=>%> B> O> Y>d>k>s>|>>@>H>?O*?Mz?j?3@@@@AA*AAA AAAAB&!B-HBvB BBBBBB5BCC )C 3C'?CgC C CCoCDDGDTE[EzE$F7F"SFvF(F2F!F>GaPG+G0GH!H8HGHOHcHhHpHHHHH H HH HEHyIIIII cJnJJ J J JJJYJBKQK bK nKzKKKK K(K1K%L:=LxL }LLLL L LLL L7L,M 1M*]mi]g]p?^^^y^J_c_ ___ __%_ `3`;`X`o` `&``1Ta.aOabAbbyb2acccac ,d9dPdBTd,dd d dde e%e,e4e Fe Qe]e meyee4ee eeeef fff,f GfSf Zf ffrff,f f fffgg g!gg g hhh%&hLh%Rh+xhh h h/him i~yiiiuj}jYkkmm %m1m :m Em)Sm}mm4mm5m(n) @&a<DőZ eTR;..-;,9hZ3<1bnPіU"_xؗKs(G#0%T)z$əUT)Ԛ;06l>#$9^x&Μ*7 Xo3ڝ1*N1yǞ#۞- =Ii Nɟ7Up1Ƞ& 7 C P [hq 9C*Q/ ¢ݢ    % 1 > L V ` l yoA8nۧJZShd|% ˩שީ6Ϊ_#>%X ~ @ɬH SOfMjo !)9Ưί ׯ &0-W Ȱ۰5 ( 8 B'Nv o'.GͲTj3F"b(2˴!> a_+00GV^rw ɶ ۶Ey-ķ˷߷ r} ʸ۸YQ` q } (˹1%&LU Zhy  Ǻ7Ѻ x, : GQ5*`X{tԽIcy¾ ¿Z[lf/ 5CXrvY_aD ;SX_O^ jR5a 32AS~g:U!w)8-@nS'gM7>mFgpy'@ `l % 5L j&t11.cOAby2>qa -B1,t    # .: JVl4u  $0 7 COi,n vz ! %)%/+U /m~VuZ6   ")0Zw45(!-8fUz61~[ 0: I S_c v N. );eu   (7=WV  (/3<DJ Zf x5sl-& -AIe ku z=&Lf8    $ :GX%5/[) \H C(lm@xd'- 'MG,^ SXME#6H_nu** dx   % @M`ihWn*">aB6(=_ -&3/Z#*/2QbUD $OMt/o$> & <2DoZTR.8.g-;9ZL3<bP{U_"K(iG#%)$$NsU)T~!  '1: BL\y         ( < A  F S N\                   + 1 9 B J  S a 9e C       - H N T  [ e j o v ~      wk|{ &~2kd(d+.g""K{!9X(C4sg\\Jxoru9vEjzJ1 Ns%n,We^6* >tg@e+/_^i#6=4Bx7iyHZ*f20cRUAVp41IhJz  b:~'8y? 5Yjnm[-}\),m?j3$u!S.;ZN5ClN':Htr$o >tLw@{zDC:!M6<]yDBQKBK2_pPEu 3OYpHQXf=##[S|8Rs0aEIl9}em"SM.W(_~)l P]q/XvFn8]^OkLW$7UP% Ywhoba3<1=@[Zr%c?}`G-bQ;<v'`dFV|qIMA*T),>a&q 0 D ;GRc GT/ F+UT7VxhO-i `& ALf5 %s to access version %s security & feature updates, and support. 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. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"The ", e.g.: "The plugin"The %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s is the new owner of the account.%s minimum payout amount.%s opt-in was successfully completed.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.Click here to learn more about updating PHP.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.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.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsAccount is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again.ActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre both %s and %s your email addresses?Are you sure you want to delete all Freemius data?Are you sure you want to proceed?Are you sure you would like to proceed with the disconnection?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundleBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s.Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutCityClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCommunicationCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeCurrent %s & SDK versions, and if active or uninstalledDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDiagnostic InfoDiagnostic data will no longer be sent from %s to %s.Disabling white-label modeDisconnecting the website will permanently remove %s from your User Dashboard's account.Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue 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 violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Duplicate WebsiteDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEmail address updateEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used during the purchase and we will resend you the license key.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtensionsExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.For delivery of security & feature updates, and license management, %s needs toFreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.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.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!Homepage URL & title, WP & PHP versions, and site languageHow do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't know what is cURL or how to install it, help me!I don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you skip this, that's okay! %1$s will still work just fine.If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there.If you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid clone resolution action.Invalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Is ActiveIs active, deactivated, or uninstalledIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.Keep SharingKeep automatic updatesKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerLong-Term DuplicateMessageMethodMigrateMigrate LicenseMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNames, slugs, versions, and if active or notNetwork BlogNetwork UserNever miss an important updateNever miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.NewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - just deactivateNo - only move this site's data to %sNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to get email notifications for security & feature updates, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires PHP VersionRequires WordPress VersionReset Deactivation SnoozingResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSharing 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.Simulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStay ConnectedStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank 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.Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you for updating to %1$s v%2$s!Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.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:This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.This plugin requires a newer version of PHP.This will allow %s toTimestampTitleTo avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.To ensure compatibility and avoid conflicts with your installed plugins and themes.To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View %s StateView Basic %s InfoView Basic Profile InfoView Basic Website InfoView Diagnostic InfoView License EssentialsView Plugins & Themes ListView detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.We made a few tweaks to the %s, %sWe'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.We're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress & PHP versions, site language & titleWordPress.org Plugin PageWould you like to merge %s into %s?Would you like to proceed with the update?YesYes - %sYes - both addresses are mineYes - do your thingYes - move all my data and assets from %s to %sYes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.You already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesYou might have missed it, but you don't have to share any data and can just %s the opt-in.You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your WordPress user's: first & last name, and email addressYour account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$sYour subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactivate a license hereactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismisscomplete the opt-indatadaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,hourhoursinstalled add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionproductsrevert it nowsecondssecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe above-mentioned sitesthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: freemius Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: Last-Translator: Vova Feldman Language-Team: Freemius Team Language: en MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js %s to access version %s security & feature updates, and support. 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. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$sThe %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.Complete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s is the new owner of the account.%s minimum payout amount.%s opt-in was successfully completed.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.Click here to learn more about updating PHP.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.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.API←➤AccountAccount DetailsAccount is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again.ActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre both %s and %s your email addresses?Are you sure you want to delete all Freemius data?Are you sure you want to proceed?Are you sure you would like to proceed with the disconnection?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundleBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s.Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutCityClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dProductsCodeCommunicationCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeCurrent %s & SDK versions, and if active or uninstalledDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDiagnostic InfoDiagnostic data will no longer be sent from %s to %s.Disabling white-label modeDisconnecting the website will permanently remove %s from your User Dashboard's account.Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue 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 violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Duplicate WebsiteDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEmail address updateEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used during the purchase and we will resend you the license key.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtensionsExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.For delivery of security & feature updates, and license management, %s needs toFreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.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.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!Homepage URL & title, WP & PHP versions, and site languageHow do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't know what is cURL or how to install it, help me!I don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you skip this, that's okay! %1$s will still work just fine.If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there.If you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid clone resolution action.Invalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Is ActiveIs active, deactivated, or uninstalledIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.Keep SharingKeep automatic updatesKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerLong-Term DuplicateMessageMethodMigrateMigrate LicenseMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNames, slugs, versions, and if active or notNetwork BlogNetwork UserNever miss an important updateNever miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.NewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - just deactivateNo - only move this site's data to %sNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to get email notifications for security & feature updates, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallChangelogDescriptionFAQFeatures & PricingInstallationOther NotesReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires PHP VersionRequires WordPress VersionReset Deactivation SnoozingResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSharing 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.Simulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStay ConnectedStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank 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.Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you for updating to %1$s v%2$s!Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.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:This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.This plugin requires a newer version of PHP.This will allow %s toTimestampTitleTo avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.To ensure compatibility and avoid conflicts with your installed plugins and themes.To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionW00tUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View %s StateView Basic %s InfoView Basic Profile InfoView Basic Website InfoView Diagnostic InfoView License EssentialsView Plugins & Themes ListView detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.We made a few tweaks to the %s, %sWe'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.We're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress & PHP versions, site language & titleWordPress.org Plugin PageWould you like to merge %s into %s?Would you like to proceed with the update?YesYes - %sYes - both addresses are mineYes - do your thingYes - move all my data and assets from %s to %sYes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.You already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesYou might have missed it, but you don't have to share any data and can just %s the opt-in.You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your WordPress user's: first & last name, and email addressYour account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$sYour subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal CodeRight onactivate a license hereActive%s cannot run without %s.%s cannot run without the plugin.Heads upallow%s leftActivatingyearAPIDismissDebuggingCongratsBlockedConnectedDownload LatestDownload Latest Free VersionMonthlyExpirationPathSending emailmoAnnualAnnuallyOncePlanNo SecretSDK VersionsLicenseSyncSync LicenseAuthorOffOnbased on %sStart free trialDismissDismisscomplete the opt-indatadaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.%s PlanBilled %sBestHeyOopsHey %s,hourhoursInstalledYee-hawlicenseSitesmsnew Beta versionnew versionnot verifiedPricePricingoptionalVersionproductsrevert it nowsecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipHmmstart the trialsubscriptionswitchingthe above-mentioned sitesthe latest %s version heretrialTrialDeleteDowngradeEditHideOpt InOpt OutPurchaseShowSkipUpdateUpgrade%s agoPK!^+o``$freemius/languages/freemius-es_ES.monu[$,*X8Y8^8Ad88nI929Z9hF:d:S;%h; ; ;;;;6f<<_R===#= >%'> M> Z> d>o>v>~>>>@>H>"?O5?M?j?>@@@@A%A5AAA AAABB&,B-SBB BBBBBB5BC$C 4C >C'JCrC C CCoC#D*DGDTEfEE/FBF"^FF(F2F!F>Ga[G+G0GH,HCHRHZHnHsH{HHHHH H HH HEHy)IIIII nJyJJ J J JJJYJMK\K mK yKKKKK K(K1K%"L:HLL LLLL L LLL L7L7M 5]mt]g]pJ^^^y^U_n_ ___ __%_ `>`F`c`z` `&``1_a.aOabAbbyb2lcccac 7dDd[dB_d,dd d dd e %e0e7e?e Qe \ehe xeee4ee eeeefff'f7f Rf^f ef qf}ff,f f ffggg g!gg hhhh%1hWh%]h+hh h h/himi~ij jujjdklm*m 0muDuW]u uuuuuv/v6v:vCvKvQv avmv v5vsvl4w&www w wyy4yHyPyly ry|y y=y&yLyf?zz z zzzz zz z {{ {+{ A{N{_{||%<}/b}})} } }\}O~~~C/smGxd.- ǁԁہ'"MςG,e S_TΆԆن߆E*=Ofu|** dz  ʈ' BObijWԉn,"@cB6*=a Œ-ӌ&5/\#*ʍ /4QdUD $QMvď/֏o&>Ր & <4DqZTR.:.i-;ؔ9ZN3<ݕbP}UΖ_$K(kG#ܘ%)&$PuU)Vǚ;ܚ6>Oϛ$ $@\&z*7̜93Rɝݝ*1%Ws#Ǟٞ ,AUZ _lNuğ,=NSY1t Ҡ 5 > L9XC֡ۡ 7= P\ k u  Ƣ ӢǦ˦WЦ(Zpid&Ω1' 8EMc9 Ckp#ˬ% *< C O\cVlZíW:MjK ðӰ 0/K{α A >Id m3y˲rxU&`|%ݴ'ѵ& (>>g%>̶h 4tG " 6AR Wd | Ÿ߸Ky<+պ  z5 ջ$<9K;;  .7 I Vd| 7ǽ ͽؽhU !#ٿ5/"eX"o  2=yua! "B FXg_  6FN;UO #z2DFBb:~:u0M"h$#3-6_P,gR]9>m)l##2)V C,Bow &*4O$t=?_2'Do\ `5W# -<Ng"y B   (08@P iw,  ) .%&L U_b%x6>" > L;Z| u+$#)7<+tDM+> jv0hNC=s46ik$ &6 JW[ v \3%.Y  -7F Wdy+t 4 NY!n   1&Xx&Sz  !+M T` eMq&P7#+2AX _ kx#U%4 !U1w n1AT$fm=xk$0 (LC,] SYp Vr . 'G gu} < Zh{ #Q0MuI@ N^v0/#?#c)/QUlD*R2'qBO   $ < D* go  UX R 4 26 Ui 1 ; 6-nd7A mMdi cO'A(R{&!Y0 -8PW&w        &> FPUru {    ' * 4>RW \igq   "+25I Xfmt}K^OV u    +3; B MXyl~|'4mf*f-/i#$M}#:  Y*D5ui^]Lyptw;xFk|K3 !Pt'o.Yg_8,!@vhBf,0``j %7?6Cz9kzJ[,h42eSWBXr62JjL| "c<(:{@ 6Zlpo\/^+-nAl5%w"T0=\O7EnP)<Jvs&q?uNyA}{FE;#O8 =_{FDSLDM3aqRGv 5Q[rISZg>%$]U~9T u2cGKn;~go$UO0X)a +mQ_s1ZxGp:^`QmNY &9W!R& [xjqdb4>3?B]\t'eAbH/dR=>w)a eHX}sKNC+V*.@c(r 1 "E <ITd IU1H-VV8WziP.kb( CMh7HmmW00t %s to access version %s security & feature updates, and support. 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. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"The ", e.g.: "The plugin"The %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s is the new owner of the account.%s minimum payout amount.%s opt-in was successfully completed.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.Click here to learn more about updating PHP.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.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.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsAccount is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again.ActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre both %s and %s your email addresses?Are you sure you want to delete all Freemius data?Are you sure you want to proceed?Are you sure you would like to proceed with the disconnection?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundleBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s.Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutCityClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCommunicationCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeCurrent %s & SDK versions, and if active or uninstalledDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDiagnostic InfoDiagnostic data will no longer be sent from %s to %s.Disabling white-label modeDisconnecting the website will permanently remove %s from your User Dashboard's account.Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue 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 violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Duplicate WebsiteDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEmail address updateEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used during the purchase and we will resend you the license key.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtensionsExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.For delivery of security & feature updates, and license management, %s needs toFreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.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.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!Homepage URL & title, WP & PHP versions, and site languageHow do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't know what is cURL or how to install it, help me!I don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you skip this, that's okay! %1$s will still work just fine.If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there.If you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid clone resolution action.Invalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Is ActiveIs active, deactivated, or uninstalledIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.Keep SharingKeep automatic updatesKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerLong-Term DuplicateMessageMethodMigrateMigrate LicenseMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNames, slugs, versions, and if active or notNetwork BlogNetwork UserNever miss an important updateNever miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.NewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - just deactivateNo - only move this site's data to %sNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to get email notifications for security & feature updates, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires PHP VersionRequires WordPress VersionReset Deactivation SnoozingResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSharing 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.Simulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStay ConnectedStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank 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.Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you for updating to %1$s v%2$s!Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.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:This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.This plugin requires a newer version of PHP.This will allow %s toTimestampTitleTo avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.To ensure compatibility and avoid conflicts with your installed plugins and themes.To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View %s StateView Basic %s InfoView Basic Profile InfoView Basic Website InfoView Diagnostic InfoView License EssentialsView Plugins & Themes ListView detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.We made a few tweaks to the %s, %sWe'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.We're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress & PHP versions, site language & titleWordPress.org Plugin PageWould you like to merge %s into %s?Would you like to proceed with the update?YesYes - %sYes - both addresses are mineYes - do your thingYes - move all my data and assets from %s to %sYes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.You already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesYou might have missed it, but you don't have to share any data and can just %s the opt-in.You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your WordPress user's: first & last name, and email addressYour account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$sYour subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactivate a license hereactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismisscomplete the opt-indatadaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,hourhoursinstalled add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionproductsrevert it nowsecondssecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipstart the trialsubscriptionswitchingthe above-mentioned sitesthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Leo Fajardo , 2022 Language-Team: Spanish (Spain) (http://www.transifex.com/freemius/wordpress-sdk/language/es_ES/) Language: es_ES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js HmmW00t %s para acceder a la versión %s de actualizaciones de funciones, seguridad y soporte. El %s de %s enlace de descarga de %s, la clave de licencia y las instrucciones de instalación se han enviado a %s. Si no encuentras el correo electrónico después de 5 minutos, comprueba tu bandeja de correo no deseado. La versión de pago de %1$s ya está instalada. Por favor, actívala para empezar a beneficiarte de las características de %2$s. %3$sThe %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s detendrá inmediatamente todos los pagos recurrentes futuros y tu licencia del plan %2$s caducará en %3$s.%1$s detendrá inmediatamente todos los pagos recurrentes futuros y tu licencia del plan caducará en %s.Completar la activación de "%s" ahoraEl complemento %s ha sido comprado correctamente.%s Instalaciones%s Licenciashace %s%s y sus complementos%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s comisión cuando un cliente compra una nueva licencia.la prueba gratuita de %s fue cancelada con éxito. Puesto que el complemento es sólo premium se desactivó automáticamente. Si quieres utilizarlo en el futuro, deberás comprar una licencia.%s es un complemento único de premium. Tienes que comprar una licencia primero antes de activar el plugin.%s is my client's email address%s is my email address%s es el nuevo dueño de la cuenta.%s cantidad mínima a pagar.%s opt-in was successfully completed.%s o mayor%s calificación%s calificaciones%s seg%s estrella%s estrellas%s vez%s veces%s para acceder a la versión %s de actualizaciones de funciones, seguridad y soporte.%s tracking cookie después de la primera visita para maximizar las ganancias potenciales.%s características de pago%sClick aquí %s para elegir los sitios sobre los que te gustaría activar la licencia.Click here to learn more about updating PHP.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.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.API←➤CuentaDetalles de la cuentaAccount is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again.AccionesActivarActivar %sActivar plan %sActivar características %sActivar versión gratuitaActivar licenciaAplicar licencia en todos los sitios pendientes.Activar licencia en todos los sitios de la red.Activar este complementoActivadoComplementos para %sComplementos del módulo %sAñadir otro dominioComplementoComplementosEl complemento debe implementarse en WordPress.org o en Freemius.DirecciónLínea de la dirección %dAfiliadoAfiliaciónDespués de su período gratuito %s, pague sólo %sDe acuerdo y activar licenciaTodas las peticionesTodos los TiposPermitir y continuarAlternativamente, puedes saltarlo ahora y activar la licencia después, en tu %s página de cuenta a nivel de red.CantidadUna descarga automatizada y la instalación de %s (versión de pago) de %s comenzará en %s. Si quieres hacerlo manualmente - haz clic en el botón de cancelación.Se ha producido un error desconocido al intentar establecer el modo beta del usuario.Se ha producido un error desconocido al intentar activar el modo de marca blanca de la licencia.Se ha producido un error desconocido.Una actualización a una versión Beta reemplazará tu versión instalada de %s con la última versión Beta - úsalo con precaución, y no en sitios de producción. Te hemos avisado.Comentarios anónimosAplicar en todos los sitios pendientes.Aplicar en todos los sitios de la red.Aceptar para hacerse afiliadoAre both %s and %s your email addresses?¿Está seguro que desea eliminar todos los datos de Freemius?¿Estás seguro que quieres proceder?Are you sure you would like to proceed with the disconnection?Como aplazamos 30 días para posible devoluciones, sólo pagamos comisiones que son de más de 30 días.Asocia con la cuenta del propietario de la licencia.La instalación automática sólo funciona para usuarios que aceptaron.Auto renovaciones en %sInstalación automáticaCalificación mediaIncreíbleHacerse afiliadoBetaFacturaciónFacturación y facturasBloqueandoID del blogCuerpoBundlePlan combinadoNombre de la empresaCompra una licencia ahoraComprar licenciaAl cambiar al usuario, usted acepta transferir la propiedad de la cuenta a:By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s.¿No puedes encontrar tu clave de licencia?CancelarCancelar %s y procederCancelar %s - No necesito más actualizaciones de características y seguridad, ni soporte para %s porque no pretendo utilizar%s en este, u otro sitio.¿Cancelar %s?Cancelar instalaciónCancelar suscripciónCancelar período de pruebaCanceladoCancelando %sCancelando %s...Cancelando la suscripciónLa cancelación del período de prueba bloqueará inmediatamente el acceso a todas las funciones premium. ¿Estás seguro?Cambiar licenciaCambiar propietarioCambiar PlanCambiar usuarioPagarCiudadBorrar caché de la APIBorrar transients de actualizacionesHaz clic aquíHaz click aquí para utilizar el plugin de forma anónimaHaz clic para ver los comentarios con una valoración de %sClick para ver la captura de pantalla a tamaño completo %dProductosCódigoCommunicationCompatible hastaContactoContactar soporteContáctanosColaboradoresNo se puede activar %s.PaísTipo de cronCurrent %s & SDK versions, and if active or uninstalledFechaDesactivarDesactivar licenciaDesactivar o desinstalar %s deshabilitará automáticamente la licencia, que podrás usar en otro sitio.Al desactivar tu licencia todas las características premium se bloquearán, pero posibilitará poder activar tu licencia en otro sitio. ¿Estás seguro que quieres continuar?DesactivaciónLog de DebugEl modo de depuración se ha activado con éxito y se desactivará automáticamente en 60 minutos. También puedes desactivarlo antes haciendo clic en el enlace "Detener depuración".Delegar a administradores del sitioBorrar todas las cuentasDetallesDiagnostic InfoDiagnostic data will no longer be sent from %s to %s.Desactivar el modo de marca blancaDisconnecting the website will permanently remove %s from your User Dashboard's account.No cancelar %s - Todavía estoy interesado en obtener actualizaciones de características y seguridad, así como poder contactar con soporte.¿No tienes una clave de licencia?Donar a este pluginBajando tu planDescargaDescargar versión %sDescargar la versión de pagoDescargar la última versión %sDescargar la última versiónDescargadoDebido al nuevo %sEU Reglamento General de Protección de Datos (RGPD)%s los requisitos de obligado cumplimiento requieren que proporciones tu consentimiento explícito, una vez más, confirmando que estás de acuerdo :-)Debido a la violación de nuestros términos de afiliados, hemos decidido bloquear temporalmente tu cuenta de afiliación. Si tienes alguna pregunta, por favor contacta nuestro soporte.Duplicate WebsiteDurante el proceso de actualización hemos detectado%d sitio(s) que aún están pendientes de la activación de licencia.Durante el proceso de actualización detectamos %s sitio(s) en la red que todavía están pendientes de tu atención.Correo electrónicoDirección de correo electrónicoEmail address updateActivar el modo de marca blancaFinIntroduce el correo electrónicoIntroduce el dominio de tu sitio web o de otros sitios web donde planeas promocionar %s.Enter the email address you've used during the purchase and we will resend you the license key.Escribe abajo la dirección de correo electrónico que has usado para la actualización y te reenviaremos la clave de licencia.Enter the new email addressErrorError recibido del servidor:CaducadoCaduca en %sExtensionsDominios extraDominios extra desde donde promocionarás el producto.ArchivoFiltroPara el cumplimiento de las directrices de WordPress.org, antes de empezar el período de prueba te pedimos que aceptes con tu usuario e información no sensible del sitio web, permitiendo a %s enviar datos periódicamente a %s para comprobar si hay actualizaciones de versión y para validar la versión de prueba.For delivery of security & feature updates, and license management, %s needs toGratisPeríodo de prueba gratuitoVersión gratuitaAPI FreemiusDebug FreemiusFreemius SDK no pudo encontrar el archivo principal del plugin. Por favor contacta a sdk@freemius.com con el error actual.Estado FreemiusFreemius es nuestro motor de licencias y actualizaciones de softwareNombre completoFunciónObtén comisiones por renovaciones automatizadas de las suscripciones.Obten actualizaciones para las versiones Beta de vanguardia de %s.Genial, por favor instala cURL y habilítalo en el archivo php.ini. Además, busca la directiva 'disable_functions' en el archivo php.ini y quita cualquier método que comienza con 'curl_'. Para asegurarte de que se activó con éxito, utiliza 'phpinfo()'. Una vez activado, desactiva el %s y reactívalo de nuevo.¿Tienes una clave de licencia?Hey, ¿sabías que %s tiene un programa de afiliados? ¡Si te gusta %s puedes convertirte en nuestro embajador y ganar dinero!Homepage URL & title, WP & PHP versions, and site language¿Qué te pareció %s hasta ahora? Prueba todas nuestras funciones premium de %s con una prueba gratuita de %d-días.¿Cómo subirlo y activarlo?¿Como nos promocionarás?Estoy de acuerdo - Cambiar usuarioNo puedo pagarlo durante más tiempoNo entiendo cómo hacerlo funcionarNo sé qué es cURL o cómo instalarlo, ¡ayúdame!No me gusta compartir mi información contigoHe encontrado un %s mejorHe actualizado mi cuenta, pero cuando intento sincronizar la licencia, el plan sigue siendo %s.Ya no necesito el %sSólo necesitaba la %s por un corto períodoIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.Si haces click, esta decisión será delegada a los administradores de los sitios.Si tienes un momento, por favor, dinos por qué estás %sIf you skip this, that's okay! %1$s will still work just fine.If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there.Si deseas renunciar a la titularidad de la cuenta de %s a %s haz clic en el botón de cambio de titularidad.Si quieres utilizar %s en estos sitios, introduce por favor tu clave de licencia abajo y haz click en el botón de activación.Aviso importante de actualización:En %sEn caso de que NO estés planeando utilizar este %s en este sitio (o en cualquier otro sitio), ¿te gustaría cancelar también %s?Instalar la versión gratuita ahoraInstalar la actualización gratuita ahoraInstalar ahoraInstalar actualización ahoraInstalando plugin: %sInvalid clone resolution action.Id de módulo no válido.Nuevo ID de usuario o dirección de correo electrónico no válido.Colección de detalles del sitio no válida.FacturaIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Está activoIs active, deactivated, or uninstalled¿Es este el sitio de clientes? %s si deseas ocultar información sensible como tu correo electrónico, clave de licencia, precios, dirección de facturación y facturas de la administración de WP.Parece que la licencia no se pudo activar.Parece que la desactivación de licencia ha fallado.Parece que ya no estás en modo de prueba, así que no hay nada que cancelar :)Parece que todavía estás en el plan %s. Si actualizaste o cambiaste tu plan, probablemente sea un problema de nuestra parte - lo sentimos.Parece que tu sitio actualmente no tiene una licencia activa.It requires license activation.Parece que uno de los parámetros de autenticación es incorrecto. Actualiza tu clave pública, clave secreta e ID de usuario e inténtelo de nuevo.It's a temporary %s - I'm troubleshooting an issueNo es lo que estaba buscandoÚnete al programa BetaSólo déjanos informarte que la información de complementos de %s se está extrayendo de un servidor externo.Keep SharingKeep automatic updatesClavePor favor, comparte lo que no funcionó para que podamos arreglarlo para los futuros usuarios...Por favor, dínos la razón para que podamos mejorar.ÚltimoÚltima actualizaciónÚltima licenciaÚltima versión gratuita instaladaÚltima versión instaladaSaber másLongitudLicenciaAcuerdo de licenciaID de licenciaClave de licencia¿Problemas de licencia?Clave de licenciaLa clave de licencia está vacía.Permanente¿Te gusta %s? Conviértete en nuestro embajador y gana dinero ;-)Cargar opción de BDLocalhostLogLoggerLong-Term DuplicateMensajeMétodoMigrateMigrate LicenseMigrar opciones a la redApps móvilesMóduloRuta del móduloTipo de móduloMás información sobre %sNombreNames, slugs, versions, and if active or notBlog de redUsuario de redNever miss an important updateNever miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.NuevoNueva versión disponibleNew WebsiteVersión gratuita más reciente (%s) instaladaVersión más reciente (%s) instaladaBoletínSiguienteNoNo - sólo desactivarNo - only move this site's data to %sSin IDSin compromiso para %s - cancelar en cualquier momentoSin compromiso por %s días - ¡cancelar en cualquier momento!No se necesita tarjeta de créditoSin caducidadSin caducidadNinguno de los planes de %s soportan un período de prueba.O.KUna vez que caduque tu licencia todavía puedes utilizar la versión gratuita pero NO tendrás acceso a las funciones de %s.Una vez que tu licencia caduque no podrás seguir utilizando %s, a no ser que lo actives de nuevo con una licencia premium válida.InscribirseDarse de bajaOpt in to get email notifications for security & feature updates, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.¡Inscríbite para hacer "%s" Mejor!OtraCorreo electrónico del propietarioID del propietarioNombre del propietarioCompatible con PCIEl complemento de pago se debe implementar en Freemius.Dirección de correo electrónico de PayPalPagosLos pagos son en USD y se procesan mensualmente por medio de PayPal.PlanEl plan %s no existe, por lo tanto, no puedes comenzar un período de prueba.El plan %s no admite un período de prueba.ID del planContacta aquí con nosotrosPor favor contáctanos con el siguiente mensaje:Por favor descarga %s.Por favor, introduce la clave de licencia que recibiste en el correo electrónico al realizar la compra:Por favor, introduce la clave de licencia para activar el modo de depuración:Siéntete libre de proporcionarnos estadísticas de tu sitio web o social media, p.ej. visitas únicas mensuales, número de suscriptores de correo electrónico, seguidores, etc. (mantendremos esta información confidencial)Por favor, sigue estos pasos para completar la actualizaciónIndica si deseas que te contactemos para actualizaciones de seguridad y nuevas funciones, contenido educativo y ofertas ocasionales:Ten en cuenta que no podremos abaratar los precios desactualizados para renovaciones/nuevas suscripciones después de una cancelación. Si eliges renovar la suscripción manualmente en el futuro, después de un aumento de precio, que generalmente ocurre una vez al año, se te cobrará el precio actualizado.Por favor, danos detalles de como pretendes promocionar %s (por favor, se lo más específico que puedas)Por favor, dinos tu nombre completo.PluginPágina web del pluginID del pluginInstalar pluginRegistro de cambiosDescripciónFAQCaracterísticas y preciosInstalaciónOtras notasValoracionesEl plugin es un "Serviceware" lo que significa que no tiene una versión de código premium.PluginsSincronizar plugins y temasPremiumLa versión Premium %s ha sido activada con éxito.Versión del complemento premium ya instalada.Versión premiumVersión premium ya activa.PrecioPolítica de privacidadProcederID del procesoProcesandoProductosSumario del programaMétodos de promociónProvinciaClave públicaComprar licenciaComprar másComentarios rápidosCuotaReenviar correo electrónico de activación¡Envíanos nuevos usuarios a nuestro %s y gana %s de comisión en cada venta satisfactoria que nos hayas referido!Renovar la licenciaRenueva tu licencia ahoraPeticionesRequires PHP VersionNecesita la versión de WordPressReset Deactivation SnoozingResultadoSDKRuta del SDKGuardar %sGuardadoCrons programadosCapturas de pantallaBuscar por direcciónClave secretaPágina segura HTTPS %s, desde un dominio externoParece que estamos teniendo algún problema temporal con tu cancelación de la suscripción. Vuelve a intentarlo en unos minutos.Parece que estamos teniendo algún problema temporal con tu cancelación de prueba. Vuelve a intentarlo en unos minutos.Parece que tienes la última versión.Seleccionar paísEnviar clave de licenciaGuardar opción en BDSharing 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.Simular actualización de redSimular período de pruebaLicencia para un único sitioID del sitioSitio dado de alta correctamente.SitiosSaltar y %sRutaSnooze & %sDe este modo, podrás reutilizar la licencia cuando el %s ya no esté activo.Social media (Facebook, Twitter, etc.)Disculpa las molestias y estamos aquí para ayudarte si nos das una oportunidad.Lo sentimos, no podemos completar la actualización de correo electrónico. Ya hay registrado otro usuario con esa dirección de correo electrónico.InicioIniciar DepuraciónComenzar el período de pruebaComenzar mi período gratuito de %sEstadoStay ConnectedDetener la depuraciónEnviarEnviar y %sSuscripciónSoporteForo de soporteSincronizar datos desde el servidorTax / Núm IVATérminos de servicio¡Gracias por darnos la oportunidad de arreglarlo! Acabamos de enviar un mensaje a nuestro personal técnico. Nos pondremos en contacto contigo tan pronto como tengamos una actualización de %s. Apreciamos tu paciencia.Gracias por aplicar a nuestro programa de asociados, infortunadamente, de momento hemos decidido rechazar tu petición. Por favor, prueba de nuevo en 30 días.Gracias por aplicar a nuestro programa de afiliados, revisaremos tu petición durante los próximos 14 días y te volveremos a contactar con información adicional.Thank you for updating to %1$s v%2$s!¡Muchas gracias por utilizar %s y sus complementos!¡Muchas gracias por utilizar %s!¡Muchas gracias por utilizar nuestros productos!¡Gracias!¡Gracias %s!Gracias por confirmar el cambio de propiedad. Se envió un correo electrónico a %s para su aprobación final.The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.%s ha roto mi sitioEl %s no funcionabaEl %s no funciona como esperaba%s es genial, pero necesito una característica que no soportáisEl %s no funciona%s de repente ha dejado de funcionarThe following products'El proceso de instalación ha comenzado y puede tardar unos minutos en completarse. Por favor, espera hasta que se finalice - no actualices esta página.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sEl paquete de plugin remoto no contiene una carpeta con el Slug deseado y el cambio de nombre no funcionó.La actualización de %s se completó con éxito.TemaCambiar temaTemasHay una %s de %s disponible.Hay una nueva versión de %s disponible.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:Este puglin no ha sido marcado como compatible con tu versión de WordPress.Este plugin no ha sido probado con tu versión actual de WordPress.This plugin requires a newer version of PHP.This will allow %s toTimestampTítuloTo avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.To ensure compatibility and avoid conflicts with your installed plugins and themes.Para entrar en el modo de depuración, introduce la clave secreta del propietario de la licencia (UserID = %d), que puedes encontrar en la sección "Mi perfil" de tu panel de control de usuario:To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.TotalMunicipioPeríodo de prueba gratuitoTipoNo es posible conectarse al sistema de archivos. Por favor, confirma tus credenciales.Licencias ilimitadasActualizaciones IlimitadasComisiones Ilimitadas.Hasta %s sitiosActualizarActivar licenciaActualizaciones, anuncios, marketing, sin spamActualizarCargar y activar la versión descargadaPanel de escritorio del usuarioID de usuarioClave de usuarioUsuariosValorEl correo de verificación se acaba de enviar a %s. Si no puedes encontrarlo después de 5 min, comprueba tu carpeta de spam.VerificadoVerificar correo electrónicoLa versión %s se ha lanzado.View %s StateView Basic %s InfoView Basic Profile InfoView Basic Website InfoView Diagnostic InfoView License EssentialsView Plugins & Themes ListVer detallesVer las funciones de pagoAtencionNo vemos ninguna licencia activa asociada a esa dirección de correo electrónico, ¿estás seguro de que es la dirección de correo electrónico correcta?No podemos encontrar tu dirección de correo electrónico en el sistema, ¿estás seguro de que es la dirección de correo electrónico correcta?No pudimos cargar la lista de complementos. Probablemente sea un problema por nuestra parte, por favor, inténtalo de nuevo en unos minutos.We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.Hemos realizado algunas optimizaciones al %s, %sNos aseguraremos de ponernos en contacto con tu empresa de alojamiento web y resolver el problema. Recibirás un correo electrónico de seguimiento a %s tan pronto tengamos una actualización.Estamos emocionados de introducir la integración de Freemius a nivel de red.Sitio web, correo electrónico y estadísticas de social media (opcional)¡Bienvenido a %s! Para empezar, introduce tu clave de licencia:¿Qué esperas?¿Qué característica?¿Cual es tú %s?¿Con qué precio te sentirías cómodo pagando?¿Que has estado buscando?¿Cuál es el nombre de %s?¿Dónde vas a promocionar %s?WordPress & PHP versions, site language & titlePágina del plugin en WordPress.orgWould you like to merge %s into %s?¿Deseas continuar con la actualización?SiSi - %sYes - both addresses are mineVamos, adelanteYes - move all my data and assets from %s to %sYes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.Ya utilizaste un período de prueba antes.Estás a sólo 1-click de comenzar tu %1$s días de prueba gratuita del plan %2$s.¡Está todo listo!Estás ejecutando %s en modo de prueba.Estás a sólo un paso - %sTodavía puedes disfrutar de todas las funciones de %s pero no tendrás acceso a soporte y actualizaciones de %s.No tienes una licencia válida para acceder a la versión premium.Tienes una licencia %s.Has comprado una licencia %s.Has actualizado correctamente tu %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesEs posible que te lo hayas perdido, pero no tienes que compartir ningún dato y puedes solo aceptar %s.You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.Ya has optado por nuestro seguimiento de uso, lo que nos ayuda a seguir mejorando %s.Ya has optado por nuestro seguimiento de uso, lo que nos ayuda a seguir mejorando.Tu complemento %s del plan se actualizó con éxito.Tu prueba gratuita de %s fue cancelada con éxito.Tu licencia %s ha sido marcada como etiqueta blanca para ocultar información sensible del administrador de WP (por ejemplo, tu correo electrónico, clave de licencia, precios, dirección de facturación y facturas). Si alguna vez deseas revertirlo, puedes hacerlo fácilmente a través de tu %s. Si se trata de un error, también puedes %s.Tu licencia %s ha sido desactivada correctamente.Your WordPress user's: first & last name, and email addressTu cuenta se ha activado correctamente con el plan %s.¡Tu aplicación al programa de afiliación para %s ha sido aceptada! Entra en tu área de afiliado desde: %s.Tu cuenta de afiliado ha sido suspendida temporalmente.Tu email ha sido verificado correctamente - ¡Eres IMPRESIONANTE!Tu período de prueba ha caducado. %1$sActualiza ahora %2$s para continuar usando el %3$s sin interrupciones.Tu período de prueba ha caducado. Todavía puedes seguir usando todas nuestras funciones gratuitas.Tu licencia ha sido cancelada. Si crees que es un error, ponte en contacto con el servicio de asistencia.Tu licencia ha caducado. %1$sActualiza ahora %2$s para continuar usando el %3$s sin interrupciones.Tu licencia ha caducado. Todavía puedes seguir usando todas las funciones de %s, pero tendrás que renovar tu licencia para seguir recibiendo actualizaciones y soporte.Tu licencia ha caducado. Puedes seguir usando el plan gratuito %s para siempre.Tu licencia fue activada correctamente.Tu licencia fue desactivada correctamente, has vuelto al plan %s.Tu nombre fue actualizado correctamente.Tu plan se activó con éxito.Tu plan se cambió correctamente a %s.Tu plan se actualizó con éxito.Tu servidor está bloqueando el acceso al API de Freemius, que es crucial para sincronizar %1$s. Por favor, contacta a tu proveedor de hosting para permitir el acceso de %2$sTu suscripción ha sido cancelada correctamente. Tu %s licencia del plan caducará en %s.Tu versión de prueba se ha iniciado con éxito.Código postalBien hechoactivate a license hereActivo%s no se puede ejecutar sin %s.%s no se puede ejecutar sin el plugin.Atenciónpermitirquedan %sActivandoañoAPIDescartarDepurandoFelicidadesBloqueadoConectadoDescargar la últimaDescargar la última versión gratuitaMensualCaducidadRutaEnviando correo electrónicomeAnualAnualmenteUna vezPlanSin clave secretaVersiones SDKLicenciaSincronizarSincronizar licenciaAutorApagadoEncendidobasado en %sComenzar el período de prueba gratuitoDescartarDescartarcomplete the opt-indatadaysdesactivandodelegar%sNO%s me envíes actualizaciones de seguridad y nuevas funcionalidades, contenido educativo y ofertas.Plan %sFacturado %sEl mejorHeyOopsHey %s,hourhoursInstaladoVayalicenciaSitiosmsnueva versión Betanueva versiónno verificadoPrecioPrecioopcionalVersiónproductsrevertirlo ahorasegparece que la clave que has introducido no coincide con nuestros registros.envíame actualizaciones de seguridad y nuevas funcionalidades, contenido educativo y ofertas.saltarcomenzar el período de pruebasuscripcióncambiandothe above-mentioned sitesla última versión %s aquíperíodo de pruebaPeríodo de Prueba GratuitoBorrarDegradarEditarOcultarInscribirseDarse de bajaComprarMostrarSaltarActualizarActualizarhace %sPK!O$freemius/languages/freemius-zh_CN.monu[gT5&`3Aa33nF424Z4hC5d5S6%e6 6 66666c77_O888#8 9 $9 19 ;9F9M9U9^9f9@o9H99O :j\::c;g;;;;;; ;;;<<&(<-O<}< <<<<<<5<= = 0= :='F=n= = ==o=>&>G>T ?b??+@>@"Z@}@(@2@!@aA+zA0AAABBB+B0B8BKBTB\B aB mB{B BEBBBCC CCC C C CDDY/DDD D DDDDD D(E1,E%^E:EEEEE E EFF #F-F 2F=FxPFF ^G kGuG H"H6H>HtYHHHHII1IGIfI IIGJJ[JfMKK KKKKKYLaiLLLL M M #M;1MmMrMyMxN }N N NNjNO5+O aOkO3tO2OO~OUnPPPPQ)*Q-TQQSQQ'R*Rg-RMR7RgSpSSTyTTT TTT TU%0U VUwUUUU UU1qV.VOV"WAWWyX2~XXXaXIYBMY,YY Y YYY ZZ%Z-Z ?Z JZVZ fZrZZ4ZZ ZZZZZ[ [[%[ @[L[ S[ _[k[[ [ [[[ [![[ \\\%\B\%H\+n\\ \ \/\\m]~o]]]]^ ^+^ 4^ ?^)M^w^^4^^5^( _6_>_-U__U_6_$`1`~aa[bc6c=c McWc(fc*c"c1c+d*;d&fdNdddd.e)1e[ekeeee e eeeee ee ff-f3fWLf fffff gggg%g+g ;gGg Yg5dgsglh&{hhh hhhiii:i @iJi Oi=[i&iLif jtj zj jjj jj j jj jj k kkk/Kl{l)l l l\l8mMm`mCmmmmnmnxod~o-op p$p+p'JpMrpGp qqqqqqqEqr(r:rQr`rgr*vrr*r^r3sBsJsSsYsd_ss ss sttitWtnt"LuBou6u=u'v yz 0z&Qz<xzDzZzTU{R{.{.,|[|-m}9}Z}30~<d~b~PUU_ K(G#c%)$ׁU)R|Â;؂6>K˃$ <X&v*7Ȅ53NŅم*1!So#ÆՆ (=B GTN]ˇ%6;A1\ ˈ ׈  & 49@CzQÉ % 2<Vqw  Ŋ Ҋ :Ύ QScW_6T ]ir,H\$>c ǓГ֓ܓ:G.v7v7%.=L_-o0Ζ  ,39m t  ʗ חYNU<ژ<Tj $$'L.e,Oך''-O}  ƛ ӛ  !7<G! Q ] j w E  ,9@GY ly),ž   ! .8J Q\cj\zlןD KX٠ `¡ɡء 2M?V 07;Mf2 9 E'Ow~ N X e r[/+29)Xpi vĨڨ!\-#Xé92VUkߪKaaj̫ $<[,m!ì  3ݭNuj0k-6ЯGAQH*Ṵ̋߰ *7 > H Uaq6 #*18Hat {  βղ &  7 AK*Oz! ҳ ߳)Yeq ״ # 2? O,Y8/;D']?0Զ!÷u[P \u | ƹֹݹNFM c p! ֺ ݺ  #'. > KX_L~˻ۻ!( >I R\ ly(]]$u ӽ 5 <F KAW$`i  ƿӿ ڿ !# 6zC,Kx' _4L[2s a^9hKMk r99 /9@62 BObt{* W kxlQ:Z%/ 6=;t2P k!4SDX5G7&Jqg0"7T2l7MV%W|#& #*)NOx$4T"WwZQ*|J!N8p# I*t    ,04;B I V`s %.AHOV ZdFk    # -7> EOV ]j3n?   3:AHOV ] jw~kd.z<=" NH<+YoH#C  j'S8M?PN6X< xF8:,I}Z*G0C";(YZ6 Esa-nfPUr/uv4EnhYv &]ehJL'Tp5Iw9BAF`S\|WmVP:2O_Sy'c[ ~Q2 _qj9(.7%GR3!iwR Qg  0o!2m |OE\V34d] Q"g ?zWG.TgD$U$DFUM9{c^@#y=[&`;lC> ?@{8=sTX7`t>a~RxN bb0,\ f/lf#b1MVB^)Z%qe[}e:-5$>J*B/k;t&LX ]p)L,@*Ir!i1uKd%)6^A(W4AKO+1DHa75K_J+c3- %s to access version %s security & feature updates, and support. 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. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"The ", e.g.: "The plugin"The %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.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.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre both %s and %s your email addresses?Are you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutCityClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDisabling white-label modeDon't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue 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 violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Duplicate WebsiteDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEmail address updateEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid clone resolution action.Invalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Is ActiveIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerLong-Term DuplicateMessageMethodMigrateMigrate LicenseMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - only move this site's data to %sNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires WordPress VersionReset Deactivation SnoozingResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTo enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageWould you like to merge %s into %s?Would you like to proceed with the update?YesYes - %sYes - both addresses are mineYes - move all my data and assets from %s to %sYes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.You already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesYou might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactivate a license hereactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdatadaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,hourhoursinstalled add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionproductsrevert it nowsecondssecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe above-mentioned sitesthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Yulinn <977869645@qq.com>, 2022 Language-Team: Chinese (China) (http://www.transifex.com/freemius/wordpress-sdk/language/zh_CN/) Language: zh_CN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js %s以访问版本%s的安全和功能更新以及支持。%s 的 %s 下载链接 %s、许可证密钥和安装说明已发送到 %s。如果您在 5 分钟后找不到该电子邮件,请检查您的垃圾邮件箱。已安装%1$s的付费版本。请激活它以开始使用%2$s功能。%3$s公司%s 的%1$s 已进入安全模式,因为我们注意到 %2$s 是 %3$s 的精确副本。%1$s将立即停止所有未来的定期付款,并且您的%2$s计划许可证将在%3$s过期。%1$s将立即停止所有未来的定期付款,并且您的%s计划许可证将在%s过期。立即完成”%s“激活已成功购买%s加载项。%s安装%s许可证%s以前%s及其附加组件%s 自动安全和功能更新以及付费功能将继续运行而不会中断,直到 %s(或当您的许可证到期时,无论先到者)。%s客户购买新许可证时收取佣金。%s免费试用已成功取消。因为附加组件是高级的,所以它被自动停用。如果你想在将来使用它,你必须购买许可证。%s是一款仅限高级的附加组件。在激活插件之前,必须先购买许可证。%s 是我客户的电子邮件地址%s 是我的电子邮件地址%s是帐户的新主人。%s最低付款金额。%s或更高%s级%s评级%s秒%s星%s星%s时间%年代%s以访问版本%s的安全和功能更新以及支持。%s首次访问后跟踪Cookie,以最大限度地提高收益潜力。%s的付费功能%s单击此处%s可选择要激活许可证的站点。刚刚向 %s 发送了一封确认电子邮件。电子邮件所有者必须在接下来的 4 小时内确认更新。刚刚向 %s 发送了一封确认电子邮件。您必须在接下来的 4 小时内确认更新。如果您找不到该电子邮件,请检查您的垃圾邮件文件夹。API←➤帐户帐户详细信息行动激活激活%s激活%s计划激活%s功能激活免费版本激活许可证在所有挂起的站点上激活许可证。在网络中的所有站点上激活许可证。激活此加载项激活%s的加载项模块%s的加载项添加另一个域附加组件附加组件加载项必须部署到WordPress.org或Freemius。网址地址行%d会员推广联盟免费%s后,只需支付%s同意并激活许可证所有请求所有类型允许并继续或者,您可以暂时跳过它,稍后在%s的网络级帐户页中激活许可证。数量从%s自动下载并安装的%s(付费版本)将在%s中启动。如果要手动执行此操作,请单击“取消”按钮。尝试设置用户的测试模式时发生了未知错误。尝试切换许可证的白标模式时发生未知错误。发生未知错误。测试版的更新将用最新的测试版替换您安装的%s版本-请小心使用,不要在生产站点上使用。你被警告了。匿名反馈在所有挂起的站点上应用。应用于网络中的所有站点。申请成为推广联盟%s 和 %s 都是您的电子邮件地址吗?是否确实要删除所有Freemius数据?确定要继续吗?由于我们保留30天的潜在退款,我们只支付超过30天的佣金。与许可证所有者的帐户关联。自动安装仅适用于已选择的用户。在%s中自动续订自动安装平均评级真棒加入推广联盟测试版本开具账单账单和发票阻止博客ID身体捆绑计划企业名称立即购买许可证购买许可证通过更改用户,您同意将帐户所有权转让给:找不到您的许可证密钥?取消取消%s并继续取消%s-我不再需要任何安全和功能更新,也不需要%s的支持服务,因为我不打算在此或任何其他站点上使用%s。取消%s?取消安装取消订阅取消试用取消正在取消%s正在取消%s...取消订阅取消试用将立即阻止使用所有高级功能。你确定吗?更改许可证变更所有权变更计划更改用户结帐城市清除 API 缓存清除更新瞬态点击这里单击此处匿名使用插件单击此处可查看评分为%s的评论单击此处可查看全尺寸屏幕截图%d产品代码兼容至联系方式联系支持联系我们贡献者无法激活%s。国家Cron类型日期停用停用许可证停用或卸载%s将自动禁用许可证,您可以在其他站点上使用该许可证。停用许可证将阻止所有高级功能,但将在其他站点上激活许可证。确定要继续吗?停用调试日志调试模式已成功启用,将在60分钟以内自动禁用。您也可以通过单击“停止调试”链接提前禁用。委派给站点管理员删除所有帐户细节禁用白标模式不要取消%s-我仍然对获取安全和功能更新感兴趣,也可以联系支持人员。没有许可证密钥?捐赠给这个插件降低您的计划下载下载%s版本下载付费版本下载最新的%s版本下载最新版本下载由于新的%sEU通用数据保护条例(GDPR)%s的合规性要求,要求您再次明确表示同意,确认您已经加入:-)由于违反我们的推广联盟条款,我们决定暂时封锁您的附属帐户。如果您有任何问题,请联系支持人员。复制网站在更新过程中,我们检测到%d个网站仍在等待许可证激活。在更新过程中,我们在网络中检测到%s个网站仍然需要您的注意。电子邮件电子邮件地址电子邮件地址更新启用白标模式终点请输入邮箱地址请输入您计划升级%s的网站或其他网站的域。请在下面输入您用于升级的电子邮件地址,我们将重新向您发送许可证密钥。输入新的电子邮件地址错误从服务器收到错误:期满%s后过期额外域您将营销产品的额外的网域。文件过滤为了遵守WordPress.org指南,在开始试用之前,我们要求您选择使用您的用户和非敏感站点信息,允许%s定期向%s发送数据以检查版本更新并验证您的试用。免费的免费试用免费版本Freemius APIFreemius 调试Freemius SDK找不到插件的主文件。请关于当前错误与sdk@freemius.com联系。Freemius 状态Freemius是我们的许可和软件更新引擎全名函数获得自动续订的佣金。获取%s的前沿测试版本的更新。有许可证密钥吗?嘿,你知道%s有会员计划吗?如果您喜欢%s,您可以成为我们的大使并赚取一些现金!到目前为止,您觉得%s怎么样?使用%d天免费试用来测试我们所有的%s高级功能。如何上传和激活?您将如何推广我们?我同意 - 更改用户我再也付不起了我不明白该怎么做我不想和你分享我的信息我找到了更好的%s我已经升级了我的帐户,但是当我尝试同步许可证时,计划仍然是%s。我不再需要%s我只需要在短时间内使用%sID如果这是长期副本,要在 %s 之后保留自动更新和付费功能,请 %s。如果单击它,此决定将委派给站点管理员。如果您有空,请告诉我们您为什么是%s如果要将%s帐户的所有权放弃给%s,请单击“更改所有权”按钮。如果要在这些站点上使用%s,请在下面输入许可证密钥,然后单击“激活”按钮。重要升级通知:在%s中如果您不打算在此站点(或任何其他站点)上使用此%s,是否也要取消%s?立即安装免费版本立即安装免费版本更新立即安装立即安装更新正在安装插件:%s无效的克隆解析操作。模块ID无效。无效的新用户ID或电子邮件地址。无效网站详细信息集合。发票%2$s 是 %4$s 的副本吗?%2$s 是一个新网站吗?%2$s 是 %4$s 的新主页吗?处于活动状态这是您客户的网站吗?如果您希望从WP管理员那里隐藏敏感信息,例如电子邮件,许可证密钥,价格,账单地址和发票,则为%s。似乎无法激活许可证。许可证停用似乎失败。看起来您不再处于试用模式,所以没有什么需要取消的:)看来你还在%s计划中。如果你确实升级或更改了计划,这可能是我们这边的问题-对不起。您的站点当前似乎没有活动许可证。它需要许可证激活。似乎身份验证参数中的一个不正确。请更新您的公钥、密钥和用户ID,然后重试。这是一个临时的 %s - 我正在解决一个问题这不是我要找的加入测试计划只是让您知道%s的加载项信息是从外部服务器提取的。钥匙请分享什么地方出了错,以便我们可以为未来的用户修复它...请告诉我们原因以便我们改进。最后上次更新时间最后一个许可证已安装最新的免费版本已安装最新版本了解更多长度许可证许可协议许可证ID许可证密钥许可问题?许可证密钥许可证密钥为空。终身喜欢%s吗?成为我们的大使并赚取现金 ;-)加载数据库选项本机日志记录器长期副本消息方法迁移迁移许可证将选项迁移到网络移动应用程序模块模块路径模块类型有关%s的详细信息姓名网络博客网络用户新的新版本可用新网站已安装较新的免费版本(%s)安装了较新的版本(%s)新闻稿下一个不否 - 仅将此站点的数据移动到 %s无 ID%s没有承诺-随时取消%s天没有承诺-随时取消!不需要信用卡永不过期非到期%s的任何计划都不支持试用期。好许可证过期后,您仍然可以使用免费版本,但您将无法使用%s功能。许可证过期后,您将无法再使用%s,除非您使用有效的高级许可证再次激活。选择加入选择退出选择加入以使“%s”更好!其他所有者电子邮件所有者 ID所有者名称PCI兼容付费附加组件必须部署到Freemius。PayPal 帐户电子邮件地址付款付款以美元为单位,每月通过 PayPal 处理。计划计划%s不存在,因此无法启动试用。计划%s不支持试用期。计划ID请在这里联系我们请通过以下信息与我们联系:请下载%s。请输入购买后在电子邮件中收到的许可证密钥:请输入许可证密钥以启用调试模式:请随时提供任何相关的网站或社交媒体统计数据,例如每月独特的网站访问量、电子邮件订户数量、关注者数量等(我们将对这些信息保密)。请按照以下步骤完成升级如果您希望我们与您联系以获取安全和功能更新、教育内容和偶尔的优惠,请告知我们:请注意,取消后,我们将无法获取更新/新订阅的过期定价。如果您选择在未来手动续订,在价格上涨后,将向您收取更新后的价格。请提供有关您打算如何推广%s的详细信息(请尽可能具体)。请提供您的全名。插件插件主页插件ID插件安装变更日志说明常见问题解答功能和定价安装其他注意事项评论插件是一个“服务软件”,这意味着它没有高级代码版本。插件插件和主题同步高级版本高级%s版本已成功激活。已安装高级加载项版本。高级版高级版本已激活。定价隐私政策继续进程 ID处理产品计划摘要推广方式省公钥购买许可证购买更多快速反馈配额重新发送激活电子邮件向新客户介绍我们的%s,并在每次销售成功时获得%s佣金!更新许可证立即续订许可证请求需要WordPress版本复位失活休眠结果软件开发工具包SDK 路径保存%s已保存计划的 Crons屏幕截图按网址搜索密钥从外部域运行的安全HTTPS%s页面似乎关于您的订阅取消发生了一些暂时的问题。请过几分钟再试一次。看来关于您的试用取消发生了一些暂时的问题。请过几分钟再试一次。好像你得到了最新的版本。选择国家发送许可证密钥设置数据库选项模拟试用推广模拟试用推广单站点许可证站点 ID站点已成功选择加入。站点跳过&%sSlug暂停 & %s因此,当%s不再活动时,您可以重新使用许可证。社交媒体(脸书、推特等)很抱歉给您带来不便,如果您能给我们一个机会,我们将竭诚为您服务。对不起,我们无法完成电子邮件更新。具有相同电子邮件的另一个用户已注册。起点开始调试开始试用开始我的免费%s国家停止调试提交提交 & %s订阅支持支持论坛从服务器得到的同步数据税务/增值税ID服务条款感谢您申请我们的推广联盟计划,不幸的是,我们现在决定拒绝您的申请。请在30天后再试。感谢您申请我们的推广联盟计划,我们将在未来14天内审查您的详细信息,并将与您联系提供更多的信息。非常感谢您使用%s及其附加组件!非常感谢您使用%s!非常感谢您使用我们的产品!谢谢您!谢谢%s!感谢您确认所有权变更。刚刚向%s发送了一封电子邮件以获得最终批准。%s破坏了我的网站%s不起作用%s没有按预期工作%s很好,但我需要您不支持的特定功能%s不起作用%s突然停止工作以下产品安装过程已开始,可能需要几分钟才能完成。请等待完成-不要刷新此页。以下产品已进入安全模式,因为我们注意到 %3$s 是 %1$s 的精确副本:%2$s以下产品已进入安全模式,因为我们注意到 %3$s 是这些网站的精确副本:%1$s%2$s远程插件包不包含具有所需段塞的文件夹,重命名失败。%s的升级已成功完成。主题主题切换主题有一个%s的%s可用。有新版本的%s可用。此插件尚未标记为与您的WordPress版本兼容。此插件尚未用当前版本的WordPress进行测试。时间戳标题要进入调试模式,请输入许可证所有者的密钥(用户ID=%d),您可以在用户仪表板的“我的配置文件”部分找到该密钥:总计城镇审判类型无法连接到文件系统。请确认您的凭据。无限许可证无限更新佣金无上限。最多%s个站点更新更新许可证更新、公告、营销,无垃圾邮件升级上传并激活下载的版本W00t用户仪表盘用户 ID用户密钥用户值验证邮件刚刚发送到%s。如果5分钟后找不到,请检查垃圾邮件框。已验证的验证电子邮件版本%s已发布。查看详细信息查看付费功能警告我们看不到与该电子邮件地址关联的任何活动许可证,您确定它是正确的地址吗?我们在系统中找不到您的电子邮件地址,您确定地址正确吗?无法加载加载项列表。这可能是我们这边的问题,请几分钟后再来。我们对%s,%s进行了一些调整我们很高兴介绍Freemius网络级集成。网站、电子邮件和社交媒体统计(可选)欢迎使用%s!首先,请输入您的许可证密钥:你期望什么?什么功能?你的%s是什么?你愿意付出什么价钱?你在找什么?%s的名字是什么?您打算在哪里推广%s?WordPress.org 插件页面要将 %s 合并到 %s 中吗?是否继续更新?是是-%s是的 - 两个地址都是我的是 - 将我的所有数据和资产从 %s 移至 %s是的,%%2$s 正在替换 %%4$s。我想将我的 %s 从 %%4$s 迁移到 %%2$s。是的,%2$s 是 %4$s 的副本,用于测试、暂存或开发。是的,%2$s 是一个与 %4$s 不同的新网站。您以前已经试用过。您只需单击一次就可以开始%1$s天的%2$s计划免费试用。你们都很好!您已经在试用模式下运行%s。你离%s只差一步。您仍然可以享受所有%s功能,但您将无法获得%s安全和功能更新以及支持服务。您没有访问高级版本的有效许可证。您有%s许可证。您购买了 %s 许可证。您已成功更新%s。您将此网站 %s 标记为 %s 的临时副本。您将此网站 %s 标记为这些网站的临时副本您可能错过了,但您不必共享任何数据,只需选择%s即可。您已经选择加入我们的使用情况跟踪,这有助于我们不断改进%s。您已经选择了我们的使用情况跟踪,这有助于我们不断改进它们。%s加载项计划已成功升级。您的%s免费试用已成功取消。您的%s许可证被标记为白标,以对WP管理员隐藏敏感信息(例如,您的电子邮件,许可证密钥,价格,账单地址和发票)。如果您希望将其还原,则可以通过%s轻松完成。如果这是一个错误,您也可以%s。您的%s许可证已成功停用。您的帐户已用%s计划成功激活。您的%s会员申请已被接受!登录到您的分支区域,地址为%s。您的会员帐户已暂时挂起。你的电子邮件已被成功验证-你太棒了!您的免费试用已过期。%1$s立即升级%2$s以继续使用%3$s而不中断。您的免费试用已过期。您仍然可以继续使用我们所有的免费功能。你的许可证被取消了。如果您认为这是一个错误,请联系支持人员。您的许可证过期了。%1$s立即升级%2$s以继续使用%3$s而不中断。您的许可证过期了。您仍然可以继续使用所有%s功能,但您需要续订许可证才能继续获取更新和支持。您的许可证过期了。您仍然可以继续永远使用免费的%s。您的许可证已成功激活。您的许可证已成功停用,您回到了%s计划。您的姓名已成功更新。您的计划已成功激活。您的计划已成功更改为%s。您的计划已成功升级。您的订阅已成功取消。您的%s计划许可证将在%s后过期。您的试用已成功开始。邮政编码就在在此处激活许可证活跃的没有%s,%s无法运行。没有插件,%s无法运行。抬头允许剩下%s正在激活年API关闭调试恭喜此路不通已连接下载最新版本下载最新免费版本每月到期路径发送电子邮件瞬间每年每年一次计划没有秘密SDK 版本许可证同步同步许可证作者关闭打开基于%s开始免费试用关闭关闭数据天停用中代表不%s要%s向我发送安全和功能更新、教育内容和服务。%s计划帐单%s最好的嘿哎呀嘿%s,小时小时安装哟哈许可证站点毫秒新测试版新版本未验证价格定价可选的版本产品现在还原秒好像你输入的钥匙和我们的记录不符。请给我发送安全和功能更新,教育内容和优惠。跳过嗯开始试用订阅切换中上述网站这儿有最新的%s版本试用试用删除降级编辑隐藏选择加入选择退出购买显示跳过更新升级%s以前PK!iӎӎ$freemius/languages/freemius-cs_CZ.monu[ Q%A%nS%h%d+&S&%& ' '"')'6<'s'_((#(( ( ( (((())@)R)V)u))))) ))))** ,*6*E*L*5T** *'** ***G*G+f+,2#,!V,x,,,,,,,,, ,, --/-6-J- ^- k- u---Y- .. *.6.?.D.T.(m.%.:... // %/ 0/=/S/[/ `/k/ ~/ ///////00 ;0F0 L0Z0a^0000 0 0 1112 2 %2 22?2jN22 22A24U14444)485-:5h5|5'5575p5h6n6 6666%66 617.97Oh7777B7,58b8 g8 t888 8888 88 9 9"9 +95999@9H9O9 V9 b9n9999!99 9999:: ,:::>:E:M:i: o:{: : :):::4:";5';(];;;-;;U;6=<1t<~<%=D=K= [=e=(t=*="=1=+>&I>p>x>>>>>>>>> >? ?!?0? I?W?n?w?????? ?? ?5?l@&@@@@@@ @@LAfOAA AAA A AA BB $B1B BB MB\XBBBBCB@CVCvCdC-aDD DD'D DDDEEEEEEEEFFF*(FSF*[F^FFFFGdGmG vGG GGGWG"H?HBH6I==I{I II-IIIJ*+JVJZJcJ$wJJ>JJ K&3K9ZK<KbKP4LUL_L;MKM("NGKN#N%N)N$O,OUO) P7P;IP6PPPPP$Q8QRQnQQ&Q*Q7Q2RIRgR3RRRRR S*(S1SSSS#SSST T#T:T OT\TNeTTTT UU-U1>UpUxUUU U U U UU UCVFVQKVV V VVVV VW W W 'W 3W @W NW XW bW nW {W,WX[b\eq\f\>],Z] ] ]]]4]]b^/_!M_o_ _ __ _ ___W_ `$`-`6`=`L` Q` [`h`{```` ```aD aQaXa `aa aaaOab+bb.b(cDcac xcc cc ccc ccc*d3d;dWdldd d ddtd1eAe Vedemete"e8e=e"f+f0fEfMfafifyfff ff f ffffg ,g6g Igjgggggogh$h @hJhYhihphvhii i iiji Gj UjbjTijkmkEl0^l$ll9l% m3mJm!_mm.mwm+n1n KnVnonn.nn n!n+nC*onooo]oGp KpUpkp)}p"pppppqq)q;q [q gqqqyqqqqq qqqqq+q$r ;rFrUrXr mr"yrrrr r/rrs s s2s1Fsxss=ssAs(/t Xtft3ttXt<*u6guu%Jvpvwv vvvvvv vvvvw&w"7wZw!aw wwwwwwwwxx /x9x Sx]x ax mxxxxx x>xx*y yyy yyzzyzuz { {#{>{ C{ P{]{e{t{{{ { {q{5| H|!V|@x| ||z|^]}+}}}~ ~ )~6~=~~~~~O~ Aby 1$  + 9E|M ʀԀ )b4)RW=>' <J%_!Ƀ ,F9_"̄/6V_ag)J+0F\-+ш4*2]Q2T$     + 8C,_ ȋˋӋ ۋ ,CIQYip w Y +19>A R^nszW ڍ   7BHOjry  ʎ!w+k <npC1:t?( Y Jcyi&AQ_4iZrD$U= x`;?U^ R|X.W)X jk@=#dE26v'V~ *!dVnNz}rgo`%@B5pHcvasjSIt947h/WK{T0ul^~3efo6YIG-y>Hm[+{L  |28z#NbA&M.;:FS esgf MDh )ZTC<]Eu,L_8qP*ml}\wK('>]"O9OQab71%\J,xq/"R$503P-B[FG %s to access version %s security & feature updates, and support. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate this add-onActivatedAdd Ons for %sAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAffiliateAfter your free %s, pay as little as %sAgree & Activate LicenseAll TypesAllow & ContinueAmountAn unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackAre you sure you want to delete all Freemius data?Are you sure you want to proceed?Auto renews in %sAverage RatingAwesomeBecome an affiliateBillingBilling & InvoicesBlockingBlog IDBodyBusiness nameBuy a license nowBuy licenseCan't find your license key?CancelCancel %s & ProceedCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClear Updates TransientsClick here to use the plugin anonymouslyClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryDateDeactivateDeactivate LicenseDeactivationDebug LogDetailsDon't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedEmailEmail addressEndEnter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsFileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGreat, 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.Have a license key?How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't know what is cURL or how to install it, help me!I don't like to share my information with youI found a better %sI no longer need the %sI only needed the %s for a short periodIDIf you have a moment, please let us know why you are %sIf you'd like to use the %s on those sites, please enter your license key below and click the activation button.In %sInstall Free Version NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.Invalid new user ID or email address.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It's not what I was looking forJoin the Beta programKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense KeyLicense issues?License keyLicense key is empty.LifetimeLocalhostLogLoggerMessageMethodModuleModule PathModule TypeMore information about %sNameNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - just deactivateNo IDNo credit card requiredNo expirationO.KOpt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleReviewsPluginsPlugins & Themes SyncPremiumPremium versionPremium version already active.PricingPrivacy PolicyProceedProductsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackRe-send activation emailRenew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySingle Site LicenseSite IDSitesSkip & %sSlugSorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a new version of %s available.TimestampTitleTo enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUp to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWe'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.We're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?WordPress.org Plugin PageWould you like to proceed with the update?YesYes - %sYes - do your thingYou already utilized a trial before.You are just one step away - %sYou do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.Your account was successfully activated with the %s plan.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$sYour subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal CodeaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.allowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %sclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingproduct versionVersionsecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Karolína Vyskočilová , 2019-2022 Language-Team: Czech (Czech Republic) (http://www.transifex.com/freemius/wordpress-sdk/language/cs_CZ/) Language: cs_CZ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js %s pro přístup k verzi %s zajišťující podporu a nejen bezpečnostní aktualizace. Placená verze %1s je již nainstalována. Aktivujte jí, abyste mohli těžit z %2s funkcí. %3s%1s okamžitě zastaví všechny budoucí opakující se platby a licence k plánu %s vyprší za %s.%1$s okamžitě zastaví všechny budoucí opakující se platby a licence k plánu %s vyprší za %s.Dokončit aktivaci „%s“Rozšíření %s bylo úspěšně zakoupeno.%s instalací%s licencíPřed %s%s a jeho doplňky%s provizi, když zákazník zakoupí novou licenci.%s bezplatná zkušební verze byla úspěšně zrušena. Jelikož toto rozšíření nenabízí bezplatnou verzi, bylo automaticky deaktivováno. Chcete-li jej v budoucnu používat, budete si muset zakoupit licenci.%s je pouze prémiové rozšíření. Před aktivací pluginu si musíte nejprve zakoupit licenci.%s je nový vlastník účtu.%s minimální částka výplaty.%s nebo vyšší%s hodnocení%s hodnocení%s s%s hvězda%s hvězd%s krát%s krát%s pro přístup k verzi %s zajišťující podporu a nejen bezpečnostní aktualizace.API←➤ÚčetDetaily účtuAkceAktivovatAktivovat %sAktivovat %s plánAktivovat %s funkceAktivovat bezplatnou verziAktivovat licenciAktivovat toto rozšířeníAktivovanýRozšíření pro %sDoplněkDoplňkyRozšíření musí být nasazeno na WordPress.org nebo na Freemius.AdresaPartnerPo bezplatné %s platit jen v %sAktivovat licenciVšechny typyPovolit a pokračovatČástkaBěhem nastavování uživatelského beta módu došlo k neočekávané chybě.Došlo k neznámé chybě.Aktualizováním na Beta verzi nahradíte nainstalovanou verzi %s nejnovějším vydáním Beta verze - používejte s opatrností a ne na produkčních webech. Varovali jsme vás.Anonymní zpětná vazbaOpravdu chcete smazat veškerá Freemius data?Opravdu chcete pokračovat?Automaticky se obnoví za %sPrůměrné hodnoceníÚžasnýStaňte se naším afiliátemFakturaceFakturace a platbyBlokováníBlog IDTěloJméno firmyKoupit licenci nyníKoupit licenciNemůžete najít svůj licenční klíč?ZrušitZrušit %s > pokračovatZrušit předplatnéZrušit zkušební verziZrušenaRuším %sRuším %s...Ruším předplatnéZrušení zkušební verze okamžitě zablokuje přístup ke všem prémiovým funkcím. Opravdu chcete pokračovat?Změnit licenciZměnit vlastnictvíZměnit plánPokladnaMěstoVyčistit API cacheVyčistit transitenty aktualizacíKlikněte zde pro anonymní používání tohoto pluginuKlikněte pro zobrazení plné velikosti snímku obrazovky %dProduktyKódKompatibilní až poKontaktKontaktovat podporuSupportPřispěvateléNelze aktivovat %s.ZeměDatumDeaktivovatDeaktivovat licenciDeaktivaceLadící logDetailyNemáte licenční klíč?Přispějte na tento pluginSnižuji vaše předplatnéStáhnoutStáhnout verzi %sStáhnout nejnovější verzi %sStáhnout nejnovější verziStaženoEmailEmailová adresaKonecZadejte emailovou adresu použitou při objednávce, abychom vám na něj mohli znovu poslat licenční klíč.ChybaChyba přijatá ze serveru:VypršeloVyprší za %sDalší doménySouborFiltrAby bylo vyhověno WordPress.org pokynům, před zahájením zkušebního období vás žádáme, abyste se rozhodli pro uživatele a necitlivé informace o webu, aby %s umožňoval periodicky odesílat data do %s za účelem kontroly aktualizací verzí a ověření zkušební verze.ZdarmaZkušební verze zdarmaVerze zdarmaFreemius APIFreemius DebugFreemius SDK nemohl najít hlavní soubor pluginu. S aktuální chybou se obraťte se na sdk@freemius.com.Stav FreemiusCelé jménoFunkceVýborně, nainstalujte prosím cURL a povolte ji v souboru php.ini. Dále vyhledejte v souboru php.ini direktivu 'disable_functions ' a odeberte všechny zakázané metody začínající na "curl_". Chcete-li se ujistit, že byla úspěšně aktivována, použijte 'phpinfo() '. Jakmile je aktivován, deaktivujte %s a znovu jej aktivujte.Máte licenční klíč?Jak se vám líbí %s? Otestujte všechny naše %s nadstandardní funkce s %d-denní zkušební verze zdarma.Jak nahrát a aktivovat?Jakým způsobem budete mé produkty propagovat?je drahý a nemohu si ho už dovolitNedokázal jsem jej zprovoznitNevím, co je cURL nebo jak jej nainstalovat, pomozte mi!Nechci s vámi sdílet své informacenašel jsem lepší %sjiž nepotřebuji %spotřeboval jsem %s jen dočasněIDMáte-li chvilku, dejte nám vědět, proč %sPokud chcete použít %s na těchto stránkách, zadejte platný licenční klíč a klikněte na tlačítko aktivovat.Za %sNainstalovat verzi zdarmaInstalovatNainstalovat aktualizaciInstaluji plugin: %sNeplatné ID modulu.Neplatné ID uživatele nebo emailová adresa.FakturaJe aktivníLicenci se nepodařilo aktivovat.Deaktivace licence pravděpodobně selhala.Zkuušební režim už vám skončil, takže už není co rušit :)Není to to, co jsem hledalOdebírat betaverzeKlíčDejte nám prosím vědět, co nefungovalo, ať to můžeme opravit pro další uživatele...Dejte nám prosím vědět z jakého důvodu, ať to můžeme zlepšit.PosledníPoslední aktualizacePoslední licenceNainstalována nejnovější verze zdarmaNainstalována nejnovější verzePřečtěte si víceDélkaLicenceLicenční smlouvaLicenční klíčProblémy s licencí?Licenční klíčLicenční klíč je prázdný.DoživotníLocalhostZáznamLoggerZprávaMetodaModulCesta k moduluTyp moduluVíce informací o %sJménoNovýNová verze k dispoziciNovější verze zdarma (%s) nainstalovánaNovější verze (%s) nainstalovánaNewsletterNásledujícíNeNe - jen deaktivovatŽádné IDKreditní karta není vyžadovánaBez vypršeníOKZúčastnit seOdhlásit seZúčastněte se, aby byl "%s" ještě lepší!jinéE-mail vlastníkaID vlastníkaJméno vlastníkaKompatibilní s PCIPlacený doplněk musí být nasazen na Freemius.E-mailová adresa účtu PayPalPlatbyVyplácení probíhá přes PayPal jednou za měsíc a v USD.Druh členstvíPlán %s neexistuje, proto nemůžete používt zkušební verzi.Plán %s nepodporuje zkušební období.ID členstvíKontaktujte nás prosím zdeKontaktujte nás prosím s následující zprávou:Stáhněte si prosím %s.Zadejte licenční klíč, který najdete v emailu odeslaném po provedení objednávky:Pro povolení módu ladění chyb zadejte licenční klíč.Dokončete upgrade provedením následujících krokůVyberte si, jestli chcete být informování o bezpečnostních aktualizacích, vylepšení funkcionality, vzdělávacímu obsahu nebo občasných obchodních nabídkách:Zadejte prosím jméno a příjmení.PluginHlavní stránka pluginuID pluginuInstalace pluginuHistorie změnPopisFAQVlastnosti a ceníkInstalaceVaše hodnoceníPluginyPluginy a synchronizace šablonPrémiumPrémiová verzePrémiová verze je již aktivní.CeníkZásady ochrany osobních údajůPokračovatProduktyOkresVeřejný klíčKoupit licenciZakoupit dalšíRychlá zpětná vazbaZnovu poslat aktivační emailObnovit licenciObnovte svou licenci teďŽádostiVyžaduje verzi WordPressVýsledekSDKCesta l SDKUložit %sPlánované cronySnímky obrazovkyHledat podle adresyTajný klíčZabezpečená stránka HTTPS %s spuštěná z externí doményOmlouváme se, ale měli jsme nějaký dočasný problém se zrušením vaší zkušební licence. Zkuste to znovu za několik minut.Pravděpodobně máte nejnovější verzi.Vyberte zemiOdeslat licenční klíčLicence pro jednu instalaciID stránkyWebyPřeskočit & %sZkratkaOmlouváme se za způsobené nepříjemnosti, ale když se nám dáte šanci, tak se vám ze všech sil pokusíme pomoci.Omlouváme se, ale aktualizaci emailu jsem nemohli dokončit. Uživatel s vámi zadaným emailem už je registrován.ZačátekZačít TrialZačít můj bezplatný %sKrajOdeslat & %sPředplatnéPodporaFórum podporySynchronizovat data ze serveruDIČPodmínky službyDěkujeme!Děkujeme %s!Děkujeme za potvrzení změny vlastnictví. Email byl právě odeslán na adresu %s, ke konečnému schválení.%s rozbil můj web%s nefungoval%s nefungoval podle očekávání%s je skvělý, ale potřebuji funkci, kterou není podporovaná%s nefunguje%s náhle přestal pracovatProces instalace byl zahájen a může trvat několik minut. Počkejte prosím na dokončení - neobnovujte tuto stránku.Balíček remote pluginů neobsahuje složku s žádoucím "slug" a přejmenování nefunguje.Aktualizace %s byla úspěšně dokončena.ŠablonaZměna šablonyŠablonyJe k dispozici nová verze %s.Datum a časNadpisPro zapnutí módu ladění chyb zadejte tajný klíč vlastníka licence (uživatel s ID = %d), který najdete v sekci "Můj profil" vaší nástěnky.CelkemMěstoZkouškaTypNelze se připojit k systémovému souboru. Potvrďte prosím svá pověření.Neomezené množství instalacíNeomezené aktualizaceAž pro %s webůAktualizovatAktualizovat licenciAktualizace, oznámení, marketing, žádný spamUpgradeNahrát a aktivovat stáhnutou verziCože?Uživatelská nástěnkaID uživateleUživateléHodnotaOvěřovací zpráva byla právě odeslána na email %s. Pokud ji nenajdete do 5 min, zkontrolujte prosím složku pro spam.OvěřenoOvěřit e-mailByla vydána verze %s.Zobrazit podrobnostiZobrazit placené funkceVarováníNemohli jsme najít vaši e-mailovou adresu v systému, jste si jisti, že je to správná adresa?Udělali jsme několik vylepšení %s, %sZkontaktujeme vaší hostingovou společnost a zkusíme vyřešit tento problém. Na %s dostanete upozornění, jakmile budeme vědět něco nového.Jsme rádi, že vám můžeme ukázat integraci Freemiusu i v rámci sítě webů.Statistika o webová stránc, emaiul a sociálních médiích%s vás vítá! Pro začátek zadejte svůj licenční klíč:Co jste očekávali?Jaká funkce?Jaké je vaše "%s"?Jakou cenu byste byli ochotni platit?Co jste hledali?Jak se %s jmenuje?Název pluginu na WordPress.orgChcete pokračovat v aktualizaci?AnoAno - %sAno - udělejte, co potřebujeteO zkušební licenci nelze žádat dvakrát.Jste jen na krok od - %sNemáte platnou licenci pro přístup k prémiové verzi.Máte licenci „%s“.Zakoupili jste licenci %s.Úspěšně jste aktualizovali %s.Účet byl úspěšně aktivován s %s plánem.Váš e-mail byl úspěšně ověřen - jste skvělý!Platnost bezplatné zkušební verze vypršela. %1$s Upgradujte nyní%2$s abyste mohli pokračovat v používání %3$s bez přerušení.Platnost bezplatné zkušební verze vypršela. Stále můžete pokračovat v používání všech našich bezplatných funkcí.Vaše licence byla zrušena. Pokud si myslíte, že je to chyba, obraťte se na naší podporu.Vaše licence vypršela. %1$sObnovte předplatné%2$s, abyste mohli mohli %3$s používat bez omezení.Vaše licence vypršela. Stále však můžete používat všechny funkce verze %s, ale pro získání technické podpory a nejnovějších aktualizací budete muset obnovit svou licenci.Vaše licence vypršela. Stále však můžete free verzi %s bez omezení.Vaše licence byla úspěšně aktivována.Vaše licence byla úspěšně deaktivována, jste zpět na plánu %s.Vaše jméno bylo úspěšně aktualizováno.Vaše licence byla úspěšně aktivována.Váše předplatné bylo úspěšně změněn na %s.Váš plán byl úspěšně aktualizován.Váš server blokuje přístup k Freemium API, což je zásadní pro synchronizaci %1$s. Obraťte se na svého poskytovatele , aby přidal do svého whitelistu %2$sVaše předplatné bylo úspěšně zrušeno. Platnost licence %s vyprší za %s.Vaše zkušebí verze byla úspěšně spuštěna.PSČ / směrovací číslo%s nelze spustit bez %s.%s nelze spustit bez tohoto pluginu.povolitZbývá %sProbíhá aktivacerokAPISkrýtDebuggingGratulujemeZablokovánoPřipojenoStáhněte si nejnovějšíStáhněte si nejnovější bezplatnou verziMěsíčněExpiraceSložkaProbíhá odesílání emailůpoRočněRočněJedenkrátDruh členstvíTajný klíč chybíSDK verzeLicenceSynchronizovatSynchronizovat licenceAutorVypnutoZapnutozaloženo na %sSkrýtSkrýtdeaktivujetedelegovatneposílejte mi bezpečnostní aktualizace a vylepšení, vzdělávací obsah a nabídky.%s plánÚčtováno %sNejlepšíDobrý denJejdaDobrý den %s,JupílicenceWebymsnová Beta verzenová verzenení ověřenoCenaCeníkVerzesposílejte mi bezpečnostní aktualizace a vylepšení, vzdělávací obsah a nabídky.přeskočitHmmspustit zkušební verzipředplatnépřepínámnejnovější %s verze zdezkušebníTrialSmazatPřejít na nižší verziUpravitSkrýtZúčastnit seOdhlásit seZakoupitZobrazitPřeskočitAktualizovatVylepšitPřed %sPK!p2WWfreemius/languages/index.phpnu[%'> M> Z> d>o>v>~>>>@>H>"?O5?M?j?>@@@@A%A5AAA AAABB&,B-SBB BBBBBB5BC$C 4C >C'JCrC C CCoC#D*DGDTEfEE/FBF"^FF(F2F!F>Ga[G+G0GH,HCHRHZHnHsH{HHHHH H HH HEHy)IIIII nJyJJ J J JJJYJMK\K mK yKKKKK K(K1K%"L:HLL LLLL L LLL L7L7M 5]mt]g]pJ^^^y^U_n_ ___ __%_ `>`F`c`z` `&``1_a.aOabAbbyb2lcccac 7dDd[dB_d,dd d dd e %e0e7e?e Qe \ehe xeee4ee eeeefff'f7f Rf^f ef qf}ff,f f ffggg g!gg hhhh%1hWh%]h+hh h h/himi~ij jujjdklm*m 0muDuW]u uuuuuv/v6v:vCvKvQv avmv v5vsvl4w&www w wyy4yHyPyly ry|y y=y&yLyf?zz z zzzz zz z {{ {+{ A{N{_{||%<}/b}})} } }\}O~~~C/smGxd.- ǁԁہ'"MςG,e S_TΆԆن߆E*=Ofu|** dz  ʈ' BObijWԉn,"@cB6*=a Œ-ӌ&5/\#*ʍ /4QdUD $QMvď/֏o&>Ր & <4DqZTR.:.i-;ؔ9ZN3<ݕbP}UΖ_$K(kG#ܘ%)&$PuU)Vǚ;ܚ6>Oϛ$ $@\&z*7̜93Rɝݝ*1%Ws#Ǟٞ ,AUZ _lNuğ,=NSY1t Ҡ 5 > L9XC֡ۡ 7= P\ k u  Ƣ Ӣ]=vDxs4'%Щ 04ݪnӫ'Bj$*Ȭ # ,6=\DQ[MjW!%.7 =K  -Ee3y2 'BIFQ 4Ų  &1Gdzγeh'U}/)A%k'+?*%2Pv-G("p ɸո   ) /:I bWp|ȹ/E u Sax  ˻oTc x$ ڼ+J7^ Ľ̽ ?^ dq п ޿ 6"+ZN Fg$!+hr0 aov{b  D"(%/`U  }GB4<tqIh)?i$3J4H!}k 8)bVeY_Qvut>9X^%1DWq"0"!A] |)AeB\GD%?e2)(Rtn ! h< ) 7EL S ak| D *.5 LV ^h'y  / '(9b 3*@k 416,c5|Z+R# )5 HET OD)Z2\EO;S^]):A Q[ o y  S#9/A+q  .AI b p| q+;Zcx,   #B7z2B;Wr  J(*mS~@FXk   )J142 -* X czm%*UV*p>sc3  '+HtSPP," ?KQp$M#) /;a?-=8E~  COc%)+/#Nrl0/41 L 9 HV    6  , H ;a  & $    . CB M P E% =k i &'=eF= U*vDL_3f~{1<,Mi.=9$e^2:y2qdi[)G)Z&-'O., ?I`f%      !(3 \fo t   ! 0 ;FZ` e qV{ "( /9< N[jpwdL `n      " +9@Hyl~|'4mf*f-/i#$M}#:  Y*D5ui^]Lyptw;xFk|K3 !Pt'o.Yg_8,!@vhBf,0``j %7?6Cz9kzJ[,h42eSWBXr62JjL| "c<(:{@ 6Zlpo\/^+-nAl5%w"T0=\O7EnP)<Jvs&q?uNyA}{FE;#O8 =_{FDSLDM3aqRGv 5Q[rISZg>%$]U~9T u2cGKn;~go$UO0X)a +mQ_s1ZxGp:^`QmNY &9W!R& [xjqdb4>3?B]\t'eAbH/dR=>w)a eHX}sKNC+V*.@c(r 1 "E <ITd IU1H-VV8WziP.kb( CMh7HmmW00t %s to access version %s security & feature updates, and support. 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. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"The ", e.g.: "The plugin"The %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s is the new owner of the account.%s minimum payout amount.%s opt-in was successfully completed.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.Click here to learn more about updating PHP.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.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.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsAccount is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again.ActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre both %s and %s your email addresses?Are you sure you want to delete all Freemius data?Are you sure you want to proceed?Are you sure you would like to proceed with the disconnection?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundleBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s.Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutCityClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCommunicationCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeCurrent %s & SDK versions, and if active or uninstalledDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDiagnostic InfoDiagnostic data will no longer be sent from %s to %s.Disabling white-label modeDisconnecting the website will permanently remove %s from your User Dashboard's account.Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue 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 violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Duplicate WebsiteDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEmail address updateEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used during the purchase and we will resend you the license key.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtensionsExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.For delivery of security & feature updates, and license management, %s needs toFreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.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.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!Homepage URL & title, WP & PHP versions, and site languageHow do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't know what is cURL or how to install it, help me!I don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you skip this, that's okay! %1$s will still work just fine.If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there.If you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid clone resolution action.Invalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Is ActiveIs active, deactivated, or uninstalledIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.Keep SharingKeep automatic updatesKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerLong-Term DuplicateMessageMethodMigrateMigrate LicenseMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNames, slugs, versions, and if active or notNetwork BlogNetwork UserNever miss an important updateNever miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.NewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - just deactivateNo - only move this site's data to %sNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to get email notifications for security & feature updates, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires PHP VersionRequires WordPress VersionReset Deactivation SnoozingResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSharing 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.Simulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStay ConnectedStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank 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.Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you for updating to %1$s v%2$s!Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.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:This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.This plugin requires a newer version of PHP.This will allow %s toTimestampTitleTo avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.To ensure compatibility and avoid conflicts with your installed plugins and themes.To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View %s StateView Basic %s InfoView Basic Profile InfoView Basic Website InfoView Diagnostic InfoView License EssentialsView Plugins & Themes ListView detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.We made a few tweaks to the %s, %sWe'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.We're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress & PHP versions, site language & titleWordPress.org Plugin PageWould you like to merge %s into %s?Would you like to proceed with the update?YesYes - %sYes - both addresses are mineYes - do your thingYes - move all my data and assets from %s to %sYes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.You already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesYou might have missed it, but you don't have to share any data and can just %s the opt-in.You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your WordPress user's: first & last name, and email addressYour account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$sYour subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactivate a license hereactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismisscomplete the opt-indatadaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,hourhoursinstalled add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionproductsrevert it nowsecondssecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipstart the trialsubscriptionswitchingthe above-mentioned sitesthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Oliver Heinrich, 2022 Language-Team: German (Germany) (http://www.transifex.com/freemius/wordpress-sdk/language/de_DE/) Language: de_DE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js HmmW00t %s, um auf die Sicherheits- und Funktionsupdates der Version %s und den Support zuzugreifen.Der %sDownload-Link%s, der Lizenzschlüssel und die Installationsanleitung wurden an %s gesendet. Wenn du die E-Mail nach 5 Minuten nicht finden kannst, überprüfe bitte dein Spam-Postfach. Die kostenpflichtige Version von %1$s ist bereits installiert. Bitte aktiviere sie, um von den %2$s Funktionen zu profitieren. %3$sDie %s%1$s wurde in den abgesicherten Modus versetzt, weil wir festgestellt haben, dass %2$s eine exakte Kopie von %3$s ist.%1$s wird sofort alle zukünftigen wiederkehrenden Zahlungen stoppen und deine %2$s Planz-Lizenz wird in %3$s auslaufen.%1$s wird sofort alle zukünftigen wiederkehrenden Zahlungen stoppen und deine %s Plan-Lizenz wird in %s auslaufen.Schließe jetzt die "%s"-Aktivierung ab%s Add-on wurde erfolgreich erworben.%s Installationen%s Lizenzenvor %s%s und seine Add-ons%s automatische Sicherheits- und Funktionsupdates und kostenpflichtige Funktionen funktionieren bis %s (oder bis zum Ablauf deiner Lizenz, je nachdem, was zuerst eintritt).%s Provision, wenn ein Kunde eine neue Lizenz kauft.%s kostenlose Testversion wurde erfolgreich abgebrochen. Da das Add-on nur Premium ist, wurde es automatisch deaktiviert. Wenn du es in Zukunft nutzen möchtest, musst du eine Lizenz erwerben.%s ist ein reines Premium Add-on. Du musst zuerst eine Lizenz erwerben, bevor du das Plugin aktivieren kannst.%s ist die E-Mail-Adresse meines Kunden%s ist meine E-Mail-Adresse%s ist der neue Besitzer des Kontos.%s Mindestauszahlungsbetrag.%s Opt-In wurde erfolgreich abgeschlossen.%s oder höher%s Bewertung%s Bewertungen%s s%s Stern%s Sterne%s mal%s mal%s, um auf die Sicherheits- und Funktionsupdates der Version %s und den Support zuzugreifen.%s Tracking-Cookie nach dem ersten Besuch, um das Ertragspotenzial zu maximieren.Bezahlte Funktionen von %s%sKlicke hier%s um die Webseite auszuwählen, auf denen du die Lizenz aktivieren möchtest.Click here to learn more about updating PHP.Eine Bestätigungs-E-Mail wurde gerade an %s gesendet. Der Besitzer der E-Mail-Adresse muss die Aktualisierung innerhalb der nächsten 4 Stunden bestätigen.Eine Bestätigungs-E-Mail wurde gerade an %s gesendet. Du musst die Aktualisierung innerhalb der nächsten 4 Stunden bestätigen. Wenn du die E-Mail nicht findest, überprüfe bitte deinen Spam-Ordner.API←➤KontoKonto DetailsDie Aktivierung des Kontos steht noch aus. Bitte überprüfe deine E-Mails und klicke auf den Link, um dein Konto zu aktivieren, und sende dann das Affiliate-Formular erneut.AktionenAktivierenAktiviere %sAktiviere %s PlanAktiviere %s FunktionenKostenlose Version freischaltenLizenz freischaltenAktiviere die Lizenz auf allen ausstehenden Seiten.Aktiviere die Lizenz auf allen Seiten im Netzwerk.Aktiviere dieses Add-onAktiviertAdd-Ons für %sAdd-Ons von Modul %sWeitere Domain hinzufügenAdd-onAdd-onsDas Add-on muss auf WordPress.org oder Freemius bereitgestellt werden.AdresseAdresse Zeile %dAffiliateAffiliateNach deinem kostenlosen %s zahlst du so wenig wie %sZustimmen & Lizenz aktivierenAlle AnfragenAlle ArtenErlauben & FortfahrenAlternativ kannst du auch diesen Schritt überspringen und die Lizenz später auf der Netzwerk-Kontoseite deines %s aktivieren.BetragEin automatischer Download und die Installation von %s (kostenpflichtige Version) von %s wird in %s starten. Wenn du es manuell machen möchtest - klicke jetzt auf den Abbruch-Button.Beim Versuch, den Beta-Modus für den Benutzers einzustellen, ist ein unbekannter Fehler aufgetreten.Beim Versuch, auf den White-Label-Modus der Lizenz umzuschalten, ist ein unbekannter Fehler aufgetreten.Ein unbekannter Fehler ist aufgetreten.Ein Update auf eine Beta-Version ersetzt deine installierte Version von %s durch die neueste Beta-Version - verwende sie mit Vorsicht und nur auf Testseiten. Du wurdest gewarnt.Anonymes FeedbackAuf alle ausstehenden Webseiten anwenden.Auf alle Seiten im Netzwerk anwenden.Bewirb dich, um ein Affiliate zu werdenSind %s und %s beide deine E-Mail-Adressen?Bist du sicher, dass du alle Freemius-Daten löschen möchtest?Bist du sicher, dass du fortfahren willst?Möchtest du wirklich mit der Trennung fortfahren?Da wir 30 Tage für mögliche Rückzahlungen reservieren, zahlen wir nur Provisionen aus, die älter als 30 Tage sind.Mit dem Konto des Lizenzinhabers verknüpfen.Die automatische Installation funktioniert nur für eingeloggte Nutzer.Verlängert sich automatisch in %sAutomatische InstallationDurchschnittliche BewertungFantastischPartner werdenBetaAbrechnungAbrechnung & RechnungenBlockierenBlog IDHauptteilPaketPaket-PlanGeschäftsnameJetzt eine Lizenz kaufenLizenz kaufenIndem du den Benutzer wechselst, stimmst du zu, den Besitz des Accounts zu übertragen:Durch das Trennen der Website werden zuvor geteilte Diagnosedaten über %1$sgelöscht und sind für %2$snicht mehr sichtbar.Du kannst deinen Lizenzschlüssel nicht finden?Abbrechen%s abbrechen & fortfahrenAbbrechen %s - Ich benötige keine Sicherheits- und Funktionsupdates mehr und auch keinen Support für %s, da ich nicht vorhabe, das %s auf dieser oder einer anderen Seite zu verwenden.%s abbrechen?Installation abbrechenAbonnement kündigenTestversion abbrechenAbgebrochenAbbruch von %sStoppe %s...Das Abonnement kündigenWenn du die Testversion abbrichst, wird der Zugang zu allen Premium-Funktionen sofort gesperrt. Bist du sicher?Lizenz ändernEigentümer wechselnPlan ändernBenutzer ändernKasseStadtAPI Cache löschenLöschen von Aktualisierungen RestenHier klickenKlicke hier, um das Plugin anonym zu nutzenKlicke, um Bewertungen zu sehen, die eine Bewertung von %s abgegeben habenKlicke, um den Screenshot in voller Größe zu sehen %dProdukteCodeKommunikationKompatibel bis zuKontaktKontaktiere SupportKontaktMitwirkendeKonnte %s nicht aktivieren.LandCron TypAktuelle %sund SDK-Versionen und falls aktiv oder deinstalliertDatumDeaktivierenLizenz deaktivierenWenn du das %s deaktivierst oder deinstallierst, wird die Lizenz hier automatisch deaktiviert und du kannst sie auf einer anderen Seite verwenden.Wenn du deine Lizenz deaktivierst, werden alle Premium-Funktionen gesperrt, aber du kannst die Lizenz auf einer anderen Seite aktivieren. Bist du sicher, dass du fortfahren möchtest?DeaktivierungDebug LogDer Debug-Modus wurde erfolgreich aktiviert und wird in 60 Minuten automatisch wieder deaktiviert. Du kannst ihn auch früher deaktivieren, indem du auf den Link "Stop Debug" klickst.An die Website Admins delegierenAlle Konten löschenDetailsDiagnoseinformationenDiagnosedaten werden nicht mehr von %s bis %sgesendet.Deaktivieren des White-Label-ModusDas Trennen der Website wird %sdauerhaft aus dem Konto Ihres Benutzer-Dashboards entfernt.%s bitte nicht stornieren - ich bin immer noch daran interessiert, Sicherheits- und Funktionsupdates zu erhalten, sowie den Support kontaktieren zu können.Du hast keinen Lizenzschlüssel?Für dieses Plugin spendenDowngrade deinen PlanDownloadDownload %s VersionBezahlte Version herunterladenLade die neueste %s Version herunterLade die neueste Version herunterHeruntergeladenAufgrund der neuen %sEU Datenschutzgrundverordnung (DSGVO)%s Compliance-Anforderungen ist es erforderlich, dass du deine ausdrückliche Zustimmung gibst und damit bestätigst, dass du an Bord bist :-)Aufgrund eines Verstoßes gegen unsere Partnerschaftsbedingungen haben wir beschlossen, dein Affiliate-Konto vorübergehend zu sperren. Wenn du Fragen hast, wende dich bitte an den Support.Duplizierte WebseiteWährend des Update-Prozesses haben wir %d Site(s) entdeckt, die noch auf eine Lizenzaktivierung warten.Während des Update-Prozesses haben wir %s Site(s) im Netzwerk entdeckt, die noch deine Aufmerksamkeit benötigen.E-MailE-Mail-AdresseE-Mail-Adresse aktualisierenAktivieren des White-Label-ModusEndeE-Mail-Adresse eingebenGib die Domain deiner Website oder anderer Webseiten an, von denen aus du planst, %s zu bewerben.Gib die E-Mail-Adresse ein, die Du beim Kauf verwendet hast, und wir senden Dir den Lizenzschlüssel erneut zu.Gib unten die E-Mail-Adresse ein, die du für das Upgrade verwendet hast und wir senden dir den Lizenzschlüssel erneut zu.Gib die neue E-Mail-Adresse einFehlerFehler vom Server empfangen:AbgelaufenLäuft in %s abErweiterungenExtra DomainsZusätzliche Domains, von denen aus du das Produkt vermarkten wirst.DateiFilterUm die Richtlinien von WordPress.org einzuhalten, bitten wir dich vor dem Start der Testversion um ein Opt-In mit deinen Benutzer- und nicht sensiblen Seite-Informationen, damit %s regelmäßig Daten an %s senden kann, um nach Versions-Updates zu suchen und um deine Testversion zu validieren.Für die Bereitstellung von Sicherheits- und Funktionsupdates sowie die Lizenzverwaltung muss %sKostenlosKostenlose TestversionKostenlose VersionFreemius APIFreemius DebugDas Freemius SDK konnte die Hauptdatei des Plugins nicht finden. Bitte kontaktiere sdk@freemius.com mit dem aktuellen Fehler.Freemius StatusFreemius ist unser Dienstleister für Lizenzierung und Software-UpdatesVollständiger NamePositionErhalte Provisionen für automatisierte Abonnementverlängerungen.Erhalte Updates für aktuelle Beta-Versionen von %s.Toll, bitte installiere cURL und aktiviere es in deiner php.ini Datei. Suche außerdem nach der 'disable_functions' Direktive in deiner php.ini Datei und entferne alle deaktivierten Methoden, die mit 'curl_' beginnen. Um sicherzustellen, dass sie erfolgreich aktiviert wurde, benutze 'phpinfo()'. Danach aktiviere, deaktiviere das %s und aktiviere es anschließend wieder.Hast du einen Lizenzschlüssel?Hallo, wusstest du, dass %s ein Partnerprogramm hat? Wenn du die %s magst, kannst du unser Affiliate werden und etwas Geld verdienen!URL und Titel der Homepage, WP- und PHP-Versionen und Sprache der WebsiteWie gefällt dir %s bis jetzt? Teste alle %s-Premium-Funktionen mit der kostenlosen %d-Tage-Testversion.Wie kann ich es hochladen und aktivieren?Wie wirst du uns bewerben?Ich stimme zu - ändere den BenutzerIch kann es nicht mehr bezahlenIch habe nicht gewusst wie ich es zum Laufen bringeIch weiß nicht, was cURL ist oder wie man es installiert. Bitte hilf mir!Ich möchte meine Informationen nicht mit dir teilenIch habe ein besseres %s gefundenIch habe mein Konto hochgestuft, aber wenn ich versuche, die Lizenz zu synchronisieren, bleibt der Plan %s.Ich brauche das %s nicht mehrIch habe das %s nur für einen kurzen Zeitraum benötigtIDWenn dies ein langfristiges Webseiten-Duplikat ist, dann brauchst du nach %s bitte %s.Wenn du es anklickst, wird diese Entscheidung an die Administratoren der Seite delegiert.Wenn du einen Moment Zeit hast, lass uns bitte wissen, warum du das Plugin deaktivieren willst:Wenn Du das überspringst, ist das in Ordnung! %1$swird immer noch funktionieren.Wenn Du stattdessen das Abonnement deines %1$sPlans kündigen möchtest, navigiere bitte zu %2$sund kündige es dort.Wenn du die Eigentümerschaft des Kontos von %s an %s abgeben möchtest, klicke auf den Button Eigentümer wechseln.Wenn du das %s auf diesen Seiten nutzen möchtest, gib bitte deinen Lizenzschlüssel unten ein und klicke auf den Aktivierungsbutton.Wichtiger Hinweis zum Upgrade:In %sFür den Fall, dass du NICHT vorhast, diesen %s auf dieser Seite (oder einer anderen Seite) zu verwenden - möchtest du den %s auch löschen?Kostenlose Version jetzt installierenUpdate der kostenlosen Version jetzt installierenJetzt installierenUpdate jetzt installierenInstalliere das Plugin: %sUngültige Klon-Auflösungsaktion.Ungültige Modul-ID.Ungültige neue Benutzer-ID oder E-Mail Adresse.Ungültige Website-Detailsammlung.RechnungIst %2$s ein Duplikat von %4$s?Ist %2$s eine neue Website?Wurde %4$s auf %2$s umgezogen?Ist AktivIst aktiv, deaktiviert oder deinstalliertIst dies die Seite deines Kunden? %s wenn du sensible Informationen wie deine E-Mail, Lizenzschlüssel, Preise, Rechnungsadresse & Rechnungen vor dem WP Admin verstecken möchtest.Es sieht so aus, als ob die Lizenz nicht aktiviert werden konnte.Es sieht so aus, als wäre die Lizenzdeaktivierung fehlgeschlagen.Es sieht so aus, als wärst du nicht mehr im Testmodus, also gibt es nichts zu stornieren :)Es sieht so aus, als wärst du immer noch im %s-Plan. Wenn du ein Upgrade oder einen Planwechsel durchgeführt hast, ist das wahrscheinlich ein Problem auf unserer Seite - sorry.Es sieht so aus, als ob deine Seite derzeit keine aktive Lizenz hat.Das erfordert eine Lizenzaktivierung.Es scheint, dass einer der Authentifizierungsparameter falsch ist. Aktualisiere deinen Public Key, Secret Key & User ID und versuche es erneut.Es ist eine temporäre %s - ich behebe ein ProblemEs ist nicht das, wonach ich gesucht habeAm Beta Programm teilnehmenIch wollte dich nur darauf hinweisen, dass die Add-on-Informationen von %s von einem externen Server bezogen werden.Weiter teilenBehalten automatische Updates beiSchlüsselBitte teile uns mit, was nicht funktioniert hat, damit wir es für zukünftige Nutzer beheben können...Bitte nenne uns den Grund, damit wir uns verbessern können.LetzteZuletzt aktualisiertLetzte LizenzAktuellste kostenlose Version installiertAktuellste Version installiertMehr erfahrenLängeLizenzLizenzvertragLizenz IDLizenzschlüsselLizenzprobleme?LizenzschlüsselDer Lizenzschlüssel ist leer.LebenslangMagst du %s? Werde unser Botschafter/Affiliate und verdiene Geld ;-)Lade DB EinstellungLocalhostLogLoggerLangfristiges DuplikatNachrichtMethodeMigrierenLizenz migrierenMigriere die Einstellungen ins NetzwerkMobile AppsModulModul PfadModul TypMehr Informationen über %sNameNamen, Slugs, Versionen und ob aktiv oder nichtNetzwerk BlogNetzwerk BenutzerVerpasse nie wieder ein wichtiges UpdateVerpasse keine wichtigen Updates, erhalte Sicherheitswarnungen, bevor sie öffentlich bekannt werden, und erhalte Benachrichtigungen über Sonderangebote und tolle neue Funktionen.NeuNeue Version verfügbarNeue WebsiteNeuere kostenlose Version (%s) InstalliertNeuere Version (%s) InstalliertNewsletterNächsteNeinNein - nur deaktivierenNein - verschiebe nur die Daten dieser Seite nach %sKeine IDKeine Verpflichtung für %s - jederzeit kündigenKeine Verpflichtung für %s Tage - jederzeit kündbar!Keine Kreditkarte erforderlichKein AblaufdatumNicht auslaufendKeiner der Pläne von %s unterstützt eine Probezeit.OKSobald deine Lizenz abläuft, kannst du die Free Version immer noch nutzen, aber du hast KEINEN Zugriff auf die %s Features.Sobald deine Lizenz abläuft, kannst du das %s nicht mehr nutzen. Es sei denn, du aktivierst ihn erneut mit einer gültigen Premiumlizenz.Opt-InAbmeldenMelde dich an, um E-Mail-Benachrichtigungen für Sicherheits- und Funktionsupdates zu erhalten und einige grundlegende Informationen zur WordPress-Umgebung zu teilen.Melde dich an, um E-Mail-Benachrichtigungen für Sicherheits- und Funktionsupdates zu erhalten und einige grundlegende Informationen zur WordPress-Umgebung zu teilen. Dies wird uns helfen, die Kompatibilität von %smit Ihrer Website zu verbessern und uns auf das zu konzentrieren, was du benötigst.Melden dich an, um E-Mail-Benachrichtigungen für Sicherheits- und Funktionsupdates, Bildungsinhalte und gelegentliche Angebote zu erhalten und einige grundlegende Informationen zur WordPress-Umgebung zu teilen.Melde dich an, um E-Mail-Benachrichtigungen für Sicherheits- und Funktionsupdates, Bildungsinhalte und gelegentliche Angebote zu erhalten und einige grundlegende Informationen zur WordPress-Umgebung zu teilen. Dies wird uns helfen, die Kompatibilität mit deiner Website zu verbessern und uns auf das zu konzentrieren, was du benötigst.Mach mit, um "%s" besser zu machen!AndereBesitzer EmailBesitzer IDName des BesitzersPCI-konformDas kostenpflichtige Add-on muss auf Freemius veröffentlicht werden.PayPal Konto E-Mail AdresseZahlungenDie Auszahlungen erfolgen in USD und werden monatlich über PayPal abgewickelt.PlanPlan %s existiert nicht, es kann keine Testversion gestartet werden.Der Plan %s unterstützt keine Probezeit.Plan IDBitte kontaktiere uns hierBitte kontaktiere uns mit der folgenden Nachricht:Bitte lade %s herunter.Bitte gib den Lizenzschlüssel ein, den du in der E-Mail direkt nach dem Kauf erhalten hast:Bitte gib den Lizenzschlüssel ein, um den Debug-Modus zu aktivieren:Du kannst uns gerne relevante Statistiken über deine Website oder soziale Medien zur Verfügung stellen, z.B. monatliche Besuche der Website, Anzahl der E-Mail-Abonnenten, Follower, etc. (wir werden diese Informationen vertraulich behandeln).Bitte folge diesen Schritten, um das Upgrade abzuschließenBitte lass uns wissen, wenn du möchtest, dass wir dich für Sicherheits- und Funktionsupdates, Bildungsinhalte und gelegentliche Angebote kontaktieren:Bitte beachte, dass wir nicht in der Lage sind, veraltete Preise für Verlängerungen/neue Abonnements nach einer Kündigung beizubehalten. Wenn du dich dafür entscheidest, das Abonnement in Zukunft nach einer Preiserhöhung, die in der Regel einmal im Jahr stattfindet, manuell zu verlängern, wird dir der aktualisierte Preis berechnet.Bitte gib Details an, wie du beabsichtigst, %s zu fördern (bitte sei so genau wie möglich).Bitte gib deinen vollständigen Namen an.PluginPlugin HomepagePlugin IDPlugin installierenChangelogBeschreibungFAQFunktionen & PreiseInstallationWeitere NotizenBewertungenDas Plugin ist eine "Serviceware", was bedeutet, dass es keine Premium Version hat.PluginsPlugins & Themes SyncPremiumPremium %s Version wurde erfolgreich aktiviert.Premium Add-on Version bereits installiert.Premium VersionPremium Version bereits aktiv.PreiseDatenschutzrichtlinieFortfahrenProzess-IDVerarbeitungProdukteProgramm ZusammenfassungPromotion MethodenProvinzÖffentlicher SchlüsselLizenz kaufenMehr kaufenSchnelles FeedbackKontingentAktivierungsmail erneut sendenEmpfehle neue Kunden an unsere %s und verdiene %s Provision für jeden erfolgreichen Verkauf, den du vermittelst!Lizenz erneuernVerlängere deine Lizenz jetztAnfragenRequires PHP VersionBenötigt WordPress VersionZurücksetzen des Deaktivierungs-SchlummernsErgebnisSDKSDK Pfad%s speichernGespeichertGeplante CronsScreenshotsSuche über die AdresseGeheimer SchlüsselSichere HTTPS %s Seite, die von einer externen Domain geladen wirdEs scheint, als ob wir ein temporäres Problem mit der Kündigung deines Abonnements haben. Bitte versuche es in ein paar Minuten erneut.Es scheint, als hätten wir ein temporäres Problem mit der Abmeldung deiner Testversion. Bitte versuche es in ein paar Minuten erneut.Sieht so aus, als hättest du die neueste Version.Land auswählenLizenzschlüssel sendenDB Option setzenDas Teilen von Diagnosedaten mit hilft dabei, Funktionen bereitzustellen, die für deine Website relevanter sind, Inkompatibilitäten mit WordPress- oder PHP-Versionen zu vermeiden, die deine Website beschädigen können, und zu erkennen, auf welche Sprachen und Regionen das Plugin übersetzt und angepasst werden sollte.Netzwerk Upgrade simulierenTrial Promotion simulierenEinzelplatzlizenzWebseiten-IDWebseite erfolgreich eingeloggt.WebseitenÜberspringen & %sURLSnooze & %sSo kannst du die Lizenz wiederverwenden, wenn die %s nicht mehr aktiv ist.Soziale Medien (Facebook, Twitter, etc.)Wir entschuldigen uns für die Unannehmlichkeiten und sind hier, um zu helfen, wenn du uns eine Chance gibst.Sorry, wir konnten das E-Mail-Update nicht abschließen. Ein anderer Benutzer mit der gleichen E-Mail ist bereits registriert.StartDebugging startenStarte TestversionStarte mein kostenloses %sStaatBleib in KontaktDebugging stoppenAbsendenAbschicken & %sAbonnementUnterstützungSupport ForumDaten vom Server synchronisierenSteuer-/Umsatzsteuer-IDAllgemeine GeschäftsbedingungenDanke, dass du uns die Chance gibst, das Problem zu beheben! Eine Nachricht wurde soeben an unser technisches Personal gesendet. Wir werden uns bei dir melden, sobald wir ein Update für %s haben. Wir danken dir für deine Geduld.Vielen Dank, dass du dich für unser Partnerprogramm beworben hast. Leider haben wir uns an dieser Stelle entschieden, deine Bewerbung abzulehnen. Bitte versuche es in 30 Tagen erneut.Vielen Dank, dass du dich für unser Partnerprogramm beworben hast. Wir werden deine Angaben in den nächsten 14 Tagen überprüfen und uns mit weiteren Informationen bei dir melden.Vielen Dank für die Aktualisierung auf %1$s v %2$s!Vielen Dank, dass du %s und seine Add-ons benutzt!Vielen Dank, dass du %s benutzt!Vielen Dank, dass du unsere Produkte benutzt!Danke dir!Danke %s!Danke für die Bestätigung des Eigentümerwechsels. Eine E-Mail wurde soeben an %s zur endgültigen Genehmigung gesendet.%1$swird regelmäßig wichtige Lizenzdaten an %2$ssenden, um nach Sicherheits- und Funktionsaktualisierungen zu suchen und die Gültigkeit Ihrer Lizenz zu überprüfen.Das %s hat meine Seite kaputt gemachtDas %s hat nicht funktioniertDas %s hat nicht wie erwartet funktioniertDas %s ist toll, aber ich brauche ein bestimmtes Feature, das nicht unterstützt wirdDas %s funktioniert nichtDas %s funktionierte plötzlich nicht mehrDie folgenden ProdukteDer Installationsprozess hat begonnen und kann ein paar Minuten dauern. Bitte warte, bis er abgeschlossen ist - lade diese Seite nicht neu.Die folgenden Produkte wurden in den abgesicherten Modus versetzt, weil wir festgestellt haben, dass %2$s eine exakte Kopie von %3$s:%1$s istDie folgenden Produkte wurden in den abgesicherten Modus versetzt, weil wir festgestellt haben, dass %2$s eine exakte Kopie dieser Seiten ist:%3$s%1$sDas Remote-Plugin-Paket enthält keinen Ordner mit dem gewünschten Slug und das Umbenennen hat nicht funktioniert.Das Upgrade von %s wurde erfolgreich abgeschlossen.ThemeTheme wechselnThemesEs ist ein %s von %s verfügbar.Es ist eine neue Version von %s verfügbar.Beim Verarbeiten Ihrer Anfrage ist ein unerwarteter API-Fehler aufgetreten. Bitte versuche es in ein paar Minuten erneut und wenn es immer noch nicht funktioniert, kontaktiere den Autor von %s mit den folgenden Angaben:Dieses Plugin wurde als nicht kompatibel mit deiner Version von WordPress markiert.Dieses Plugin wurde nicht mit deiner derzeitigen Version von WordPress getestet.This plugin requires a newer version of PHP.Dies wird es %s ermöglichenZeitstempelTitelUm zu vermeiden, dass deine Website aufgrund von WordPress- oder PHP-Versionsinkompatibilitäten beschädigt wird, und um zu erkennen, welche Sprachen und Regionen von %sübersetzt und angepasst werden sollten.Um die Kompatibilität zu gewährleisten und Konflikte mit deinen installierten Plugins und Themes zu vermeiden.Um in den Debug-Modus zu gelangen, gib bitte den geheimen Schlüssel des Lizenzinhabers (UserID = %d) ein, den du in deinem "Mein Profil"-Bereich deines "Benutzer Dashboards" findest:Damit du verwalten und kontrollieren kannst, wo die Lizenz aktiviert wird, und um sicherzustellen, dass %s Sicherheits- und Funktionsupdates nur an von dir autorisierte Websites geliefert werden.Um zusätzliche Funktionen bereitzustellen, die für deine Website relevant sind, Inkompatibilitäten von WordPress- oder PHP-Versionen, die deine Website beschädigen können, und um zu erkennen, welche Sprachen und Regionen für %sübersetzt und angepasst werden sollten.TotalStadtTestversionTypEs kann keine Verbindung zum Dateisystem hergestellt werden. Bitte bestätige deine Anmeldedaten.Unbegrenzt LizenzenUnbegrenzte UpdatesUnbegrenzte Provisionen.Bis zu %s SeitenUpdateLizenz aktualisierenUpdates, Ankündigungen, Marketing, kein SpamUpgradeLade die heruntergeladene Version hoch und aktiviere sieBenutzer DashboardBenutzer-IDBenutzerschlüsselBenutzerWertVerifizierungsmail wurde gerade an %s gesendet. Wenn du sie nach 5 Minuten nicht finden kannst, überprüfe bitte deine Spam-Ordner.VerifiziertE-Mail verifizierenVersion %s wurde freigegeben.%s Status anzeigenGrundlegende %sInformationen anzeigenGrundlegende Profilinformationen anzeigenGrundlegende Website-Informationen anzeigenDiagnoseinformationen anzeigenGrundlegende Lizenzen anzeigenListe der Plugins & Themes anzeigenDetails ansehenBezahlte Funktionen ansehenWarnungWir können keine aktiven Lizenzen sehen, die mit dieser E-Mail-Adresse verbunden sind. Bist du sicher, dass es die richtige Adresse ist?Wir konnten deine E-Mail-Adresse nicht im System finden. Bist du sicher, dass dies die richtige Adresse ist?Wir konnten die Liste der Add-ons nicht laden. Es ist wahrscheinlich ein Problem auf unserer Seite, bitte versuche es in ein paar Minuten wieder.Wir haben dieses Opt-in eingeführt, damit du kein wichtiges Update verpasst und uns hilfst, die Kompatibilität von %smit deiner Website zu verbessern und uns auf das zu konzentrieren, was du benötigst.Wir haben ein paar Anpassungen an den %s, %s gemachtWir stellen sicher, dass wir dein Hosting-Unternehmen kontaktieren und das Problem beheben. Du bekommst eine Folge-E-Mail an %s, sobald wir ein Update haben.Wir freuen uns, dir die Freemius Netzwerk-Integration vorstellen zu können.Website-, E-Mail- und Social Media-Statistiken (optional)Willkommen bei %s! Um loszulegen, gib bitte deinen Lizenzschlüssel ein:Was hast du erwartet?Welche Funktion?Wie lautet dein(e) %s?Welchen Preis würdest du als gerechtfertigt erachten?Wonach hast du gesucht?Wie lautet der Name des %s?Wo wirst du %s bewerben?WordPress- und PHP-Versionen, Sprache und Titel der WebsiteWordPress.org Plugin SeiteMöchtest du %s in %s zusammenführen?Willst du mit dem Update fortfahren?JaJa - %sJa - beide Adressen sind meineJa - mach dein DingJa - verschiebe alle meine Daten und Vermögenswerte von %s nach %sJa, %%2$s ersetzt %%4$s. Ich möchte meine %s von %%4$s nach %%2$s migrieren.Ja, %2$s ist ein Duplikat von %4$s für Test-, Staging- oder Entwicklungszwecke.Ja, %2$s ist eine neue und andere Website, die von %4$s getrennt ist.Du hast schon einmal einen Testzeitraum in Anspruch genommen.Du bist einen Klick davon entfernt, deinen kostenlosen %1$s-Tage Testzeitraum des %2$s-Planes zu starten.Ihr seid alle gut!Du testest %s bereits.Du bist nur einen Schritt entfernt - %sDu kannst immer noch alle %s-Funktionen nutzen, aber du wirst keinen Zugang zu %s-Sicherheits- und Funktionsupdates und keinen Support bekommen.Du hast keine gültige Lizenz, um auf die Premium-Version zuzugreifen.Du hast eine %s Lizenz.Du hast eine %s-Lizenz erworben.Du hast deine %s erfolgreich aktualisiert.Du hast diese Website, %s, als temporäres Duplikat von %s markiert.Du hast diese Website, %s, als temporäres Duplikat dieser Websites markiertDu hast es vielleicht übersehen: du musst keine Daten teilen und kannst einfach das Opt-in %s.You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.Du hast dich bereits für unser Nutzungs-Tracking entschieden, was uns hilft, %s weiter zu verbessern.Du hast Dich bereits für unser Nutzungs-Tracking entschieden, was uns hilft, die Benutzerfreundlichkeit weiter zu verbessern.Dein %s Add-on Plan wurde erfolgreich upgegradet.Deine %s kostenlose Testversion wurde erfolgreich storniert.Deine %s-Lizenz wurde als White-Label gekennzeichnet, um sensible Informationen vor dem WP Admin zu verbergen (z.B. deine E-Mail, Lizenzschlüssel, Preise, Rechnungsadresse und Rechnungen). Solltest du das wieder rückgängig machen wollen, kannst du das ganz einfach über dein %s tun. Wenn dies ein Fehler war, kannst du es auch %sDeine %s-Lizenz wurde erfolgreich deaktiviert.Dein WordPress-Benutzer: Vor- und Nachname und E-Mail-AdresseDein Konto wurde erfolgreich mit der %s-Lizenz aktiviert.Dein Affiliate-Antrag für %s wurde angenommen! Logge dich in deinen Affiliate-Bereich ein unter: %s.Dein Mitgliedskonto wurde vorübergehend gesperrt.Deine E-Mail wurde erfolgreich verifiziert - Glückwunsch!Deine kostenlose Testversion ist abgelaufen. %1$sUpgrade jetzt%2$s, um das %3$s weiterhin ohne Unterbrechungen zu nutzen.Dein kostenloser Testzeitraum ist abgelaufen. Du kannst aber weiterhin alle unsere kostenlosen Funktionen nutzen.Deine Lizenz wurde gelöscht. Wenn du denkst, dass es ein Fehler ist, kontaktiere bitte den Support.Deine Lizenz ist abgelaufen. %1$sUpgrade jetzt%2$s, um das %3$s weiterhin ohne Unterbrechungen zu nutzen.Deine Lizenz ist abgelaufen. Du kannst weiterhin alle %s Funktionen nutzen, aber du musst deine Lizenz erneuern, um weiterhin Updates und Support zu erhalten.Deine Lizenz ist abgelaufen. Du kannst das kostenlose %s trotzdem für immer weiter nutzen.Deine Lizenz wurde erfolgreich aktiviert.Deine Lizenz wurde erfolgreich deaktiviert, du bist zurück im %s Plan.Dein Name wurde erfolgreich aktualisiert.Dein Plan wurde erfolgreich aktiviert.Dein Plan wurde erfolgreich auf %s geändert.Dein Plan wurde erfolgreich upgegradet.Dein Server blockiert den Zugriff auf Freemius' API, die für die %1$s-Synchronisation entscheidend ist. Bitte kontaktiere deinen Host, um %2$s auf die Whitelist zu setzenDein Abonnement wurde erfolgreich gekündigt. Dein %s Plan wird in %s ablaufen.Dein Testzeitraum wurde erfolgreich gestartet.PLZ / PostleitzahlDirekt anLizenz hier aktivierenAktiv%s kann nicht ohne %s laufen.%s kann ohne das Plugin nicht laufen.Kopf hocherlauben%s übrigAktivieren vonJahrAPIVerwerfenFehlersucheHerzlichen GlückwunschBlockiertVerbundenNeuester DownloadNeueste kostenlose Version herunterladenMonatlichVerfallsPfadE-Mail sendenmtl.JährlichJährlichEinmalPlanKein geheimer SchlüsselSDK VersionenLizenzSyncSynchronisiere LizenzAutorAusAnbasierend auf %sStarte die kostenlose TestversionSchließenSchließencomplete the opt-inDatenTagedeaktivieredelegiereschicke mir %sKEINE%s Sicherheits- und Funktionsupdates, Bildungsinhalte und Angebote.%s PlanAbgerechnet wird %sBesteHeyHopplaHey %s,StundeStundenInstalliertHurraLizenzWebseitenmsneue Beta Versionneue Versionnicht geprüftPreisPreiseoptionalVersionProduktejetzt rückgängig machen.ses scheint, dass der von dir eingegebene Schlüssel nicht mit unseren Aufzeichnungen übereinstimmt.schicke mir Sicherheits- und Funktionsupdates, Bildungsinhalte und Angebote.überspringendie Testversion startenAbonnementveränderedie oben erwähnten Websitesdie neueste %s Version hiertesteTestLöschenDowngradeBearbeitenVersteckenAnmeldenAbmeldenKaufenAnzeigenÜberspringenUpdateUpgradevor %sPK!G_G$freemius/languages/freemius-ru_RU.monu[ A#S#%E$ k$ w$$6$$_v%#%% & !& +&6&=&E&N&V&H_&&&&&& &&&'' .'8'G'\'o'v'5~'' ' '''( ( ((2(C(J(((2)!L)an)0)****9*A*U*]*f* k*y**** * *Y*6+E+ V+b+k+p+(+1+%+,,,, /, :,G,], e,o, t,, , ,,,,,,,-1- M-X-- -.Y.ab.... . /;/J/O/V/U0 Z0 e0 r00j00 1131O1~c1U182T2m2)2-22S2H3'`3373g3+4 14=4P4f4y4 414.4O4<5A5y5x6a66B6,A7n7 s7 77 777 7 77478 %8/838:8B8 I8U8 \8 h8t8888 888%8+89 49 B9/O99m999: :):>:[:4d::5:(::;-;J;U^;;1}<[< =*=1= A=K=(Z=*="=1=+>*/>&Z>N>>>>.>)%?O?_??? ? ???? ????W@j@s@@@@@@ @ @5@lA&tAAA AAAA AA&AL!BfnBB BBB B C C C 6CCCTCC D\DDDEC0EtEEEd0F-FF FFMFG+G sG}GGGGGEGGGHH*H1H*@HkH*sH^HHI IdIvI II IIIiIW7J"J6JJ J K-KKKiK&KKK$KMKLL&LZM.vM.M9MZN3iN<NUN0OKO(PG@P#P)P$P)P%Q7Q;TQ6Q>QR!RAR$WR|RRRR&R*S>SUSsS3SSSSTT*4T1_TTT#TTUU #U/UOUfU {UUUUUUV1VDVXV hV uV VV VQVW WW8W KWWW fW pW zW W W W W W W WJW6&\D]\\\\^\pE]^EQ_4____``'`>` M`X`a#a ?aMa_aya"a;a)a5bTblb$b6b$b" c]/c cccicD?ddd*ddd f6*fWaf>ffg6h0hhii6iSimi i@i i$i"j/Bjrjj"k3kk k llb.lxle mpmwmm9mmm0n HnVn jntn.nn0n& o0o7CoB{oo o3o,#pPp!bp"q"q qqlr bsos/sBs9sx8tt tYt"w98w"rw w-wwxxxyx5?yuypz6V{9{;{^|Qb|||,}:}}r}i~" 3C%w  _T:Twl\ɂ( Ych=($6? Yg"y!'b4I"~ ;‡ ( GTk' = w%zB9[b4# XeTv9ˌUjd!zNk)W;vڑQA F/T" ۓ/1Oc @(iQyJ˕;3 o5y Ė3$> R^,}A},͘ јޘ) 7qCO4 6@(w (Л82Ӝ *+ /C:b5Ӡ%43ۢ8-Bͤ *IoYnɥ8U g r.Q2;!K:L/9,/f$8T 1 ŭP%Ms5/ɮ IM!%GG8Ȱ'N.v9`tT|pH L1:NGE> ̹ڹ#;M_t!Ժ .N b T ):Ul}!, ޼)&P`p  ɽѽ  &: CQX*`-Ӿ7Ie {߿~>0Z'5-yI`Gozqw}#YcRWFpfX`.Kl4?lw2U@+_z6.jS(r,=e*;$G,ND'ao{x7dhLhX6k" M(s[x 2_g#dSU  m0^EQ-jN!!e"H4Y]@&v|7T{cJs+v>*Z 38$y[^\3n:1t9J p&QuA1 \|)fi9FBIr~PCW:PEB=8D Vk}/OROa)/iMubqb V<<mLt5]%;nKCgA H ?%T%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s tracking cookie after the first visit to maximize earnings potential.APIAccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate Free VersionActivate LicenseActivate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.Address Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.Anonymous feedbackApply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBillingBlockingBodyBusiness nameCan't find your license key?CancelCancel InstallationCancel SubscriptionCancel TrialCancelledCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivationDebug LogDelete All AccountsDetailsDon't have a license key?Donate to this pluginDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.In %sInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Version InstalledLearn moreLengthLicenseLicense KeyLicense keyLifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMobile appsModuleModule PathModule TypeMore information about %sNameNewNewer Version (%s) InstalledNewsletterNextNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Opt InOpt OutOtherPCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProcess IDProcessingProgram SummaryPromotion methodsProvincePublic KeyPurchase LicenseQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!RequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSingle Site LicenseSite IDSitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThis plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWebsite, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatinge.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlike websitesSitesmillisecondsmsnot verifiednounPricenounPricingproduct versionVersionsecondssecsomething somebody says when they are thinking about what you have just said.Hmmstart the trialswitchingthe latest %s version heretrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Leo Fajardo , 2021 Language-Team: Russian (Russia) (http://www.transifex.com/freemius/wordpress-sdk/language/ru_RU/) Language: ru_RU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js Закончить активацию %s сейчас Покупка %s плагина успешно состоялась%s установок %s лицензий %s тому назад %s вознаграждения, если клиент купит новую лицензию.Бесплатный период пользования %s закончился. Этот плагин является премиум продуктом и он был деактивирован автоматически. Если Вы планируете дальнейшее его использование, пожалуйста купите лицензию. %s является премиум продуктом. Необходимо купить лицензию перед активацией плагина. %я является новым владельцем аккаунта%s минимальная сумма выплаты %s или выше%s оценка %s оценки %s секунд %звездочка %s звездочки %s время %s раз %s данные cookies предоставляются после первого посещения, чтобы максимально увеличить вероятность заработка. APIЛичный кабинет ДеталиДействия Активировать Активировать %sАктивируйте план %sАктивировать бесплатную версию?Активировать лицензиюАктивируйте этот функционал Активирован Функционал для %sФункционал модуля %sДобавьте другое доменное имя Функционал плагина Настройки плагина Функционал должен быть заявлен на WordPress.org или Freemius Поле для адреса %dПартнерПартнерство После окончания Вашего бесплатного %s, платите всего лиш %sСогласиться и активировать лицензию Все запросы Все типыРазрешить и продолжитьКоличество Автоматическое скачивание и установка %s ( платной версии) %s начнется через %s. Если Вы хотите все сделать в ручном режиме, нажмите на кнопку "Отменить" сейчас. Анонимный отзыв Подать заявку на партнерство Вы уверенны, что хотите удалить все данные Freemius?Вы уверены, что хотите продолжить?Мы выделяем 30 дней для поступления возвратов и поэтому вознаграждения выплачиваются за покупки, которые были совершены более чем 30 дней назад.Авто установка работает только для зарегистрированных пользователей.Автоматическое продление в %sАвтоматическая установка Средний рейтинг Отлично!Стать партнеромСистема оплаты Блокирование Основная часть Название бизнеса Не можете найти лицензионный ключ? Отмена Отменить установку Отменить подписку Отменить тестовый период Аннулирована Отказ от пользования тестовым периодом автоматически блокирует доступ ко всем премиум возможностям. Вы уверены, что хотите отказаться?Изменить лицензию Сменить владельца лицензии Изменить план Оплата Город Очистить кэш APIНажмите здесь, чтобы пользоваться плагином анонимно. Нажмите, чтобы посмотреть отзывы, которые сформировали рейтинг %sКликните, чтобы посмотреть снимок %d на широком экране. КодСовместима с Свяжитесь с намиСвязаться со службой поддержкиКонтакты Контрибьюторы Невозможно активировать %sСтрана Тип задачиДата Деактивировать Деактивировать лицензию ДеактивацияЖурнал устранения ошибок Удалить все аккаунтыДетальнейУ Вас нет лицензионного ключа?Инвестировать в разработку плагина Скачать Скачайте версию %sСкачайте последнюю версию %sСкачай последнюю версиюЗагружен Из-за нарушения условий партнерства мы вынуждены временно заблокировать Ваш аккаунт. Если у Вас возникли вопросы, пожалуйста, обратитесь в службу поддержки. Электронный адрес Электронный адрес Конец Введите домен Вашего сайта или других сайтов на которых Вы намерены продвигать %s.Введите ниже адрес своей электронной почты, которую Вы использовали для обновлений и мы Вам отправим повторно Ваш лицензионный ключ. ОшибкаОшибка сервераСрок действия закончился Окончание срока пользования через %sДополнительные доменные имена Дополнительные доменные имена, где Вы будете продвигать продукт. ФайлФильтр В соответствии с руководством WordPress.org, перед началом тестового периода мы просим, чтобы Вы присоединились к нашему сообществу предоставив информацию о Вашем сайте и также Ваши личные данные, тем самым разрешив %s периодически отправлять сообщения на %s для уведомлений об обновлениях и подтверждения Вашего тестового периода. Бесплатная Бесплатный период пользования Бесплатная версия Freemius APIИсправление ошибок FreemiusFreemius SDK не удалось найти основной файл плагина. Пожалуйста, свяжитесь с sdk@freemius.com с текущей ошибкой.Cостояние Freemius Полное имяФункция Получай вознаграждение за автоматические продления пользования. У Вас есть лицензионный ключ?Привет! Знали ли Вы, что %s предоставляет реферальную программу? Если Вам нравится %s, Вы можете стать нашим представителем и зарабатывать!Тебе нравится пользоваться %s? Воспользуйся всеми нашими премиум возможностями на протяжении %d - дневного тестового периода. Как загрузить и активировать?Как Вы намерены продвигать нас?Я больше не могу оплачивать это. Я не могу понять как сделать так, чтобы оно работалоЯ не хочу делиться личной информацией с ВамиЯ нашел лучший %sЯ провел апгрейд аккаунта, но при попытке синхронизировать лицензию, мой тарифный план не меняется. %s больше не понадобится.%s требовалась на короткое времяIDЕсли у Вас есть время, пожалуйста, сообщите причину почему Вы %sЕсли Вы передаете права пользования аккаунтом %s %s нажмите кнопку " Сменить права использования"В %sУстановить сейчас Провести обновления сейчас Установка плагина: %sНеверный ID модуляСчет активный Вероятно возникли трудности с активацией лицензии. Вероятно деактивация лицензии не состоялась. Возможно, Ваш тестовый период уже закончился. Вероятно Вы все еще пользуетесь сервисом согласно плану %s. Если Вы обновляли или меняли свой тарифный план, то вероятно существуют какие-то трудности связанные с Вашим программным обеспечением. Извините. Вероятно Ваш сайт не использует активную лицензию сейчас. Вероятно один из параметров является неверным. Обновите свой Public Key, Secret Key&User ID и повторите попытку.Это не то, что я искал. Сообщаем, что информация о дополнительных настройках %s предоставляется со стороннего сервера. Ключ Пожалуйста, сообщите о функционале, который не работает, чтобы мы смогли исправить его для дальнейшего использования. Пожалуйста, укажите причину, чтобы мы могли исправиться. Последний Последнее обновление Последняя лицензия Последняя версия установленаУзнать большеДлинна Лицензия Лицензионный ключ Лицензионный ключНа бессрочный период Вам нравится %s? Стань нашим партнером и зарабатывай ;-)Загрузить опцию базы данных Локальный хостинг Журнал изменений Программа сохранения изменений Сообщение Метод Мобильные приложения МодульПуть модуля Тип модуля Больше информации о %sИмяНовое Более новая версия %s установлена Рассылка Следующий No IDБез обязательств платить %s - аннулируй пользование в любое время Бесплатное пользование на протяжении %s дней. Отмена в любое время. Не требуются данные платежной картыБессрочный период пользования Бессрочный Тарифные планы %s не предусматривают тестовый период. O.K.По окончанию срока действия Вашей лицензии, Вы сможете пользоваться бесплатной версией, но у Вас не будет доступа к возможностям %s. ПрисоединитьсяОтказаться от использованияДругиеЖалоба PCIПлатный функционал должен быть заявлен в FreemiusЭлектронный адрес аккаунта PayPalПлатежиВыплаты производятся в долларах США через PayPal.Тарифный план Тарифного плана %s не существует, поэтому Вы не можете начать тестовый период. Тарифный план %s не предусматривает тестового периода. ID тарифного плана Пожалуйста, напишите нам сообщение здесь. Пожалуйста, напишите нам сообщение следующего содержания:Пожалуйста, скачайте %sПожалуйста введите лицензионный ключ, который Вы получили на электронный адрес сразу после покупки. Пожалуйста, предоставьте соответственную статистику вебсайта или страницы социальных сетей, например, количество уникальных посетителей, количество подписчиков, читателей, т. д. ( эта информация останется конфиденциальной). Пожалуйста, пройдите эти шаги для того, чтобы произвести апгрейдПожалуйста, предоставьте максимально детальную информацию о том, как Вы планируете продвигать %s.Пожалуйста, введите Ваше полное имяПлагин Главная страница плагина ID плагина Установка плагина Журнал изменений Описание Часто задаваемые вопросы Функционал&тарифные планы Установка Другие заметки Отзывы Плагин является 'Serviсeware'. Это означает, что он не имеет премиум версию кода. Плагины Синхронизация плагинов и шаблонов Премиум Премиум версия %s была успешно активирована. Премиум версия плагина была установленаПремиум версия Премиум версия уже активированаЦены Политика КонфиденциальностиID процесса Обработка данных Краткое описание программы Методы продвижения Провинция Public Key Купите лицензию Выделенный объем памятиОтправить письмо активации еще раз Порекомендуй %s новым пользователям и зарабатывай %s c каждой успешной продажи. Запросы Необходима версия WordPress РезультатSDKпуть SDKЭкономия %sСпланированные задачиСнимки экрана Secret Key Безопасная страница HTTPS %s воспроизводится с внешнего ресурса К сожалению у нас возникли трудности с отменой Вашего тестового периода. Пожалуйста, повторите попытку через несколько минут.Вероятно, Вы пользуетесь последней версиейВыбрать страну Отправить лицензионный ключУстановить опцию базы данных Лицензия на один сайт Site IDСайтов Пропустить & %sОписательная часть URL Социальные сети ( Facebook, Twitter, etc.)Извините за неудобство. Мы будем рады помочь, если Вы нам предоставите эту возможность. Извините, нам не удалось обновить электронный адрес. Другой пользователь с таким же адресом уже был зарегистрирован. НачалоНачать тестовый периодНачать мой бесплатный %sШтат Отправить&%sПоддержка Форум поддержки Синхронизация данных с сервера ID налога/НДС Пользовательское соглашениеСпасибо за подачу заявки на партнерство. К сожалению, мы приняли решение отказать Вам в этой возможности. Пожалуйста, повторите попытку через 30 дней. Спасибо за подачу заявки на партнерство. Мы рассмотрим Ваши данные на протяжении следующих 14 дней и свяжемся с Вами. Спасибо %sСпасибо, что подтвердили изменение прав использования. Вам отправлено письмо на %s для окончательного подтверждения. %s повредила мой сайт%s не сработала%s не сработала как ожидалось%s отличная возможность, но мне нужен определенный функционал, который вы не поддерживаете. %s не работает%s внезапно перестала работать Процесс установки уже начат и может занять несколько минут. Пожалуйста, подождите окончания процесса и не обновляйте эту страницу. Удаленный пакет плагинов не содержит папку с нужным описанием URL и смена имени не срабатывает. Обновление %s было успешно завершеноШаблон Переключатель шаблона Шаблоны Этот плагин не отмечен как совместимый з Вашей версией WordPress Этот плагин не был тестирован с Вашей текущей версией WordPress. Маркер времени Название ИтогоНаселенный пункт Тестовый период ТипНевозможно присоединиться к системе файлов. Пожалуйста, подтвердите свои данные. Неограниченная лицензия Неограниченные обновления Неограниченное вознаграждение до %s сайтов Обновить Обновить лицензиюНовости, объявления, маркетинг, без спамаСделать апгрейд Загрузите и активируйте скачанную версиюВау!User ID Пользователи Значение Письмо подтверждение было только что отправлено на %s. Если Вы не получите его через 5 минут, пожалуйста, проверьте папку спам.Подтвержден Подтвердите электронный адрес Релиз версии %s состоялся. Смотреть детальней Просмотр платных возможностейПредупреждение Активная лицензия выданная на этот электронный адрес не была найдена. Вы уверены, что предоставили правильный электронный адрес?К сожалению, Ваш почтовый адрес не найден в системе. Вы уверены, что предоставили правильный адрес? Мы усовершенствовали в %s, %s для лучшей работы Вебсайт, электронный адрес и статистика социальных сетей (не обязательно)Каковы были Ваши ожидания? Какой функционал?Какой Ваш %s?Какая стоимость была бы для Вас приемлемой? Что именно Вы ищите? Какое название %s?Где Вы намерены продвигать %s?Страница плагинов WordPress.orgДа - %sВы уже использовали Ваш тестовый периодВы уже на расстоянии одного клика от начала Вашего бесплатного %1$s - дневного тестового периода по тарифному плану %2$s. Все прошло хорошо!Вы уже пользуетесь тестовой версией %s Вам осталось совсем немножко %sУ Вас нет необходимых лицензионных прав для пользования премиум версиейУ Вас есть лицензия %s.Вы успешно обновили Ваш %sВозможно, Вы не обратили внимание, но Вы не обязаны делиться никакими данными и можете просто %s кнопку "Присоединиться". Ваш %s план был успешно обновленВаш бесплатный тестовый период был успешно отменен. Ваша учетная запись была успешно активирована согласно плану %sВаша заявка на партнерство с %s принята! Войдите в Ваш кабинет партнера на %sВаш партнерский аккаунт временно недоступен. Ваш электронный адрес был успешно подтвержден и Вы просто молодец!Ваша лицензия была аннулирована. Если Вы считаете, что это ошибка, пожалуйста свяжитесь с нашей службой поддержки. Срок действия Вашей лицензии закончен. Вы можете продолжать пользоваться всеми возможностями %s продлив Вашу лицензию. Вы также будете получать доступ к обновлениям и поддержке. Срок действия Вашей лицензии закончился. Вы можете продолжать пользоваться бесплатной версией %s на бессрочной основе.Ваша лицензия была успешно активирована. Ваша лицензия была успешно деактивирована и Вы снова пользуетесь планом %s.Ваше имя было успешно обновленоВаш тарифный план был успешно изменен на %s.Ваш тарифный план был успешно изменен. Ваш тестовый период успешно начатИндекс Все верно!%s не работает без %s.%s не может работать без плагина. Внимание!Осталось %s Активация на один год APIЗакрыть Устранение ошибокПоздравления! Заблокировано Соединено Скачать последнюю версиюПомесячно Срок пользования Путь Электронное письмо отправляется Вам на почту на один месяцЕжегодно Один раз в год Один раз Тарифный план Нет секрета Версии SDKЛицензия Синхронизировать Синхронизация лицензии АвторВыключить Включить Основан на %sНачни тестовый период!Закрыть Закрыть Деактивация %s план Оплачивать %sЛучший Привет!Упс!Здравствуйте %sУра!Сайтов мс не подтвержден Стоимость ЦеныВерсия секХм...Начать тестовый периодПереключение Последняя версия %s здесьТестовый периодУдалитьПонизить план Редактировать Спрятать Присоединится Отписаться КупитьПоказать ПропуститьОбновить Сделать апгрейд%s тому назад PK! }$freemius/languages/freemius-fr_FR.monu[T +A+S+%, =, I,U,\,6o,,_[-#-- - . ..".*.3.;.@D.H..O.1/5/T/t/|/// /////&/-0K0 `0j0y00005000 0 1'1<1 U1 b1l1o}11122"2223!53aW303334"4*4>4F4O4W4 \4j4 |44444 S5^5r5 5 5 555Y526A6 R6^6g6l6|6(616%6:7Q7V7g7o7 7 777 77 77x7[8 8 89939t;999999:2: N:Y:[:fM;; ;;Y;a&<<<< < <;<===> > )> 6>C>jR>> >>3>?~'?U??@1@)L@-v@@S@ A'$ALAMOA7AgAp=BBByBHCaC CCCC CC C1C..DO]DDA-EyoEEa FkFBoF,FF F FFG 5G@GGGOG aG mGyGG4GG GGGGGH H'H .H :HFH`H eH rHHH!HH HHHH%H+ICI [I iI/vIImI~JJJJJ JJ J J)J K=K4FK{K5K(KKK-K,LU@LL1_M~MN[-OOOO OO(O*P",P1OP+P*P&PNPNQVQlQ.tQ)QQQQRR R 'R2R;RKR]R fRqRRRRWR SS-S6SQSXS\SeSmS }SS S5SsSlPT&TTT UU+UDUXU`U|U UU&ULUfVlV rV~VV V VV VV VVVW/%XUX)uX X X\XY'Y:YCYYYYYdYZ-ZZ ZZ['%[MM[G[ [[[[[\E \O\b\t\\\\*\\*\^]m]u]{]d]] ]] ^"^5^i=^W^"^B"_6e__ __-__`&2`Y`s`w`$`M``/a5aoUa>ab&bZBbTbRb.Ec.tc9cZc38d<ldbdP eU]e_efKf(fG#g#kg)g$gUg)4h^hph;h6h>i?iEi`ii$iiii j&+j*Rj7}jjjj3k7kLkbkzkk*k1kl$l#8l\lxll llll llNmWmvmmmmm1mnn/n ?n Kn Xn cnpn nnQnn n oo-o3o FoRo ao ko uo o o o o o o oohs":t!]tt t tt>ttbu*'vRv rv }v vv v vvvivZ.wwWwxxxxx1x9x AxLxbxxx1x1xy(y0y@yUyoyvyB~yyy y y*yz:zNz]z~szzz{&{-{! |FB|!|}|X)}#}} } }} }}~~ ~~;~+O~{~~~ ^knh{$̀/2!9Tρ ށ  (x?_ n&xÃ$^!ф%" 6Dsmx dc,#2OLT  '4xH ӊߊK4T]!B*d02ߌg.1P;6qr(m( 7I$א/ (229eU5˒ vOsM`͔/ߔ,;DL_o?- &."7Zov#Öǖז#5 O p{ 6>ʗ &74G| %ԙڙ7)a PۚT18j{1ɛX=:(c]nr$ - D NZ^ x Z) 37;5sءߡ ,5Kb jxxŢ>#W{ ƣգ >\ߤ-`0"-<D_ eq*v[om t §.ӧ! --(ީ z&ǪYH,`}73 .-a\G "3Y8Ʈ߮ / H1U  1;%Ms" xj+0SDZPl&޲* 7TX.ac0 ;ZFO"nd!b)/=Ck6D$iwgpwعPY$VP{̻-(WB( ü ϼ#ۼ* * 8BQelp w "+Ž  "'.5>F^n v о׾޾ q j u Ϳӿڿ  %DJ [ erz  m4=b~&6}A+sBu[#H _>8B <n_Sdr kT5^dMD:No(* buK>Eep%"TP 32~9$xW;C^zD|L=sjQJUIkXYZU95]t\tFh!:@7wh {-p-&mfGc N/<KqCVR1%y.@OSlgx WRL)'a4$z[\2{lcG8MFQ6(a`f?A;o }H+?Ei7/X3,|*rOPJ# V]Yy01`g')v,"Z0.ewqi!I vjn %s to access version %s security & feature updates, and support.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBillingBlockingBlog IDBodyBusiness nameBuy a license nowBuy licenseCan't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClear Updates TransientsClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.During the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.Invalid site details collection.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense KeyLicense keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicenseQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageYesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew versionnot verifiednounPricenounPricingproduct versionVersionsecondssecskipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Boris Colombier , 2018 Language-Team: French (France) (http://www.transifex.com/freemius/wordpress-sdk/language/fr_FR/) Language: fr_FR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js %s pour accéder aux mises à jour de sécurité et de fonctionnalités de la version %s, et au support.Compléter "%s" Activer MaintenantL'Add-on %s a bien été acheté.%s Installations%s LicencesIl y a %s%s et ses add-onsCommission de %s quand un client achète une nouvelle licence.La période d'essai du %s a bien été annulé. L'add-on a été désactivé car il ne fonctionne qu'avec la version premium. Si vous souhaitez l'utiliser ultérieurement, vous devrez acheter une licence.%sest un add-on pour la version premium. Vous devez acheter une licence avant d'activer le plugin.%s est le nouveau propriétaire du compte.Montant de paiement minimum %s.%s ou plus%s notation%snotations %s sec%s étoile%s étoiles%s fois%s fois%s pour permettre les mises à jour de sécurité et de fonctionnalités de la version %s, et le support.Cookie de tracking de %s après la première visite pour maximiser les potentiels de gain.Fonctionnalités payantes de %s%sCliquez ici %s pour choisir les sites sur lesquels vous souhaitez activer la licence.API←➤CompteDétails du compteActionsActiverActiver %sActiver la formule %sActiver les fonctionnalités %sActivez la version gratuiteActiver la licenceActiver la licence sur tous les sites en attente.Activer la licence sur tous les sites du réseau.Activer cet add-onActivéAdd Ons pour %sAdd Ons du module %sAjouter une autre adresseAdd-OnAdd-OnsLes add-ons doivent être déposés sur WordPress.org ou Freemius.AdresseAdresse ligne %dAffiliationAffiliationAprès vos %s gratuits, payez seulement %sValider & Activer la licenceToutes les demandesTous les typesAutoriser & ContinuerÉventuellement, vous pouvez l'ignorer pour l'instant et activer la licence plus tard, sur votre page de compte du réseau %s.MontantUn téléchargement et une installation automatique de %s (version premium) de %s va commencer dans %s. Si vous voulez le faire manuellement, cliquez sur le bouton d'annulation maintenant.Commentaire anonymeActiver sur tous les sites en attente.Effectuer sur tous les sites dans le réseau.Postuler pour devenir un affiliéÊtes-vous sûr de vouloir supprimer toutes les données de Freemius ?Êtes-vous de vouloir continuer ?Comme nous bloquons sur 30 jours pour les remboursements éventuels, seules sont payées les commissions de plus de 30 jours.L'installation automatique ne fonctionne que pour les utilisateurs qui se sont inscrits.Renouvellements automatique dans %sInstallation automatiqueNote moyenneFormidableDevenir un affiliéFacturationBloquantBlog IDBodyRaison socialeAcheter une licence maintenantAcheter une licenceVous ne trouvez pas votre clef de licence ?AnnulerAnnuler %s et poursuivreAnnuler %s - Je n'ai plus besoin de mises à jour de sécurité et de fonctionnalités, ni de support pour %s parce que je n'ai pas l'intention d'utiliser le %s sur ce site, ou tout autre site.Annuler %s ?Annuler l'installationAnnuler l'abonnementAnnuler la période d'essaiAnnuléAnnulation de %sAnnulation de %s...Annuler votre abonnementAnnuler la période d'essai va immédiatement bloquer les fonctionnalités premium. Souhaitez-vous continuer ?Changer la licenceChangement De PropriétaireChanger de formulePaiementVilleVider le cache APIVider les transients de mise à jourCliquer ici pour utiliser le plugin anonymementCliquez pour voir les avis avec une notation de %sCliquez pour voir la capture d'écran %d en pleine tailleProduitsCodeCompatible jusqu'àContactContacter l'AssistanceContactez NousContributeursImpossible d'activer %s.PaysType de CronDateDésactiverDésactiver la licenceDésactiver ou désinstaller le %s désactivera automatiquement la licence, que vous pourrez utiliser sur un autre site.Désactiver la licence bloquera toutes les fonctionnalités premium mais vous permettra d'activer la licence sur un autre site. Êtes-vous sûr de vouloir continuer ?DésactivationDebug LogDéléguer aux administrateurs du siteSupprimer tous les comptesDétailsNe pas annuler %s - Je veux toujours recevoir les mises à jour de sécurité et de fonctionnalités, ainsi que d'être en mesure de contacter le support.Vous n'avez pas de clef de licence ?Faire une donation pour ce pluginRétrograder votre formuleTéléchargementTélécharger la version %sTélécharger la dernière version %sTélécharger la dernière versionTéléchargéSuite à une violation de nos conditions d'affiliation, nous avons décidé de bloquer temporairement votre compte d'affilié. Si vous avez la moindre question, merci de contacter le support.Durant le processus de mise à jour nous avons détecté %d site(s) toujours en attente d'activation de la licence.Durant le processus de mise à jour nous avons détecté %s site(s) dans le réseau que vous devez vérifier.EmailAdresse emailFinIndiquez l'adresse de votre site ou d'autres sites sur lesquels vous pensez faire la promotion du %sIndiquez ci-dessous l'adresse email que vous avez utilisez pour la mise à jour et nous allons vous renvoyer le code de la licence.ErreurUne erreur a été reçu depuis le serveur :ExpiréExpire dans %sAdresses supplémentairesAdresses supplémentaires depuis lesquelles vous ferez la promotion du produit.FichierFilterPour être en accord avec les directives de WordPress.org, avant que nous commencions la période d'essai, nous vous demandons de nous permettre de récupérer votre nom d'utilisateur et des informations non sensibles du site afin de permettre au %s de communiquer avec %s pour vérifier les mises à jour et valider votre période d'essai.GratuitEssai gratuitVersion gratuiteAPI FreemiusDébuggage FreemiusLe SDK Freemius ne trouve pas le fichier principal du plugin. Merci de contacter sdk@freemius.com en indiquant l'erreur.État de FreemiusNom completFonctionObtenez des commissions pour les renouvellements automatiques d'abonnement.Vous avez une clef de licence ?Dites, savez-vous que %s propose un système de affiliation ? Si vous aimez le %s vous pouvez devenir notre ambassadeur et gagner de l'argent !Que pensez-vous de %s ? Testez nos %s fonctionnalités premium avec %d jours d'essai gratuit.Comment téléverser et activer ?Comment allez-vous faire de la promotion ?Je ne peux plus payer pour çaJe ne comprends pas comment le faire fonctionnerJe ne veux pas partager mes informations avec vousJ'ai trouvé un meilleur %sJ'ai mis à jour mon compte mais quand j'essaie de synchroniser la licence, la formule est toujours %s.Je n'ai plus besoin du %sJe n'ai besoin de %s que pour une courte périodeIDSi vous cliquez, cette décision sera déléguée aux administrateurs des sites.Si vous avez un instant, merci de nous indiquer pourquoi %sSi vous voulez transférer la propriété du compte de %s à %s cliquez sur le bouton Changement De PropriétaireSi vous voulez utiliser le %s sur ces sites, merci d'indiquer votre clé de licence ci-dessous et de cliquer sur le bouton d'activation.Information importante de mise à jour :Dans %sDans le cas où vous n'avez PAS l'intention d'utiliser ce %s sur ce site (ou tout autre site) - voulez-vous aussi annuler le %s ?Installer la version gratuite maintenantInstaller la dernière mise à jour gratuite maintenantInstaller maintenantInstaller la mise à jour maintenantInstallation du plugin : %sID du module non valide.Récupération des détails du site non valide.FactureEst actifIl semble que la licence ne puisse être activée.Il semble que la désactivation de la licence a échoué.Il semble que vous ne soyez plus en période d'essai donc il n'y a rien à annuler :)Il semble que vous soyez encore sur la formule %s. Si vous avez mis à jour ou changer votre formule, le problème est probablement de votre côté - désolé.Il semble que votre site n'ait pas de licence active.Il semble que l'un des paramètres d'authentification soit faux. Veuillez mettre à jour votre Public Key, votre Secret Key ainsi que vote User ID et essayez à nouveau.Ce n'est pas ce que je rechercheSachez que les informations de l'add-ons de %s sont issus d'un serveur externe.ClefMerci de nous indiquer ce qui ne fonctionne pas afin que nous puissions le corriger pour les futurs utilisateurs...S'il vous plait, dites nous pourquoi afin que nous puissions nous améliorer.DernierDernière mise à jourDernière licenceLa dernière version gratuite a été installéDernière Version InstalléeEn savoir plusLongueurLicenceContrat de licenceClef de licenceClef de licenceLa clé de licence est vide.À vieVous aimez %s ? Devenez notre ambassadeur et gagnez du cash ;-)Chargement des options de la base de donnéesLocalhostLogLoggerMessageMéthodeMigrer les options vers le réseauApplications mobilesModuleChemin d'accès du moduleType de modulePlus d'informations à propos de %sNomRéseau de BlogRéseau d'UtilisateurNouveauUne nouvelle version est disponibleLa nouvelle version gratuite ( %s ) a été installéNouvelle Version (%s) InstalléeNewsletterSuivantNonID manquantPas d'engagement durant %s - annuler quand vous voulezPas d'engagement durant %s jours - annuler quand vous voulez !Pas besoin de carte bancairePas d'expirationSans expirationAucune formule du %s ne propose de période d'essai.O.KUne fois la licence expirée vous pourrez toujours utiliser la version gratuite mais vous n'aurez PAS accès aux fonctionnalités de %s.Une fois votre licence expirée, vous ne pourrez plus utiliser le %s, sauf si vous l'activez à nouveau avec une licence premium valide.InscriptionDésinscriptionInscrivez-vous pour améliorer "%s" !AutreEmail du propriétaireID du propriétaireNom du propriétaireCompatible PCILes add-ons payant doivent être déposés sur FreemiusAdresse email du compte PayPalPaiementsLes paiements se font en Dollars US et sont effectués mensuellement via PayPal.FormuleLa formule %s n'existe pas, il n'est pas possible de commencer une période d'essai.La formule %s ne propose pas de période d'essai.ID de la formuleMerci de nous contacter iciMerci de nous contacter avec le message suivant :Merci de télécharger %s.Merci d'indiquer le code de licence que vous avez reçu par email juste après l'achat :N'hésitez pas à indiquer des statistiques pertinentes concernant votre site ou vos réseaux sociaux telles que le nombre de visiteurs mensuel, le nombre d'abonnés, de followers, etc... (C'est informations resteront confidentielles)Merci de suivre ces étapes pour finaliser la mise à jourMerci de nous indiquer si vous souhaitez que nous vous contactions pour les mises à jour de sécurité et de fonctionnalités, du contenu instructif et des offres spéciales :Veuillez noter que nous ne serons pas en mesure de garantir le maintien des prix actuels pour les renouvellements/nouveaux abonnements après une annulation. Si vous choisissez de renouveler l'abonnement manuellement à l'avenir, après une augmentation de prix, qui se produit généralement une fois par an, le prix mis à jour vous sera facturé.Merci d'indiquer en détail comment vous allez faire la promotion du %s (en étant aussi précis que possible)Merci d'indiquer vos prénom et nom.PluginSite Web du pluginID du pluginInstallation du PluginChangelogDescriptionFAQFonctionnalités & TarifsInstallationAutres InformationsCommentairesLe plugin est un "Serviceware" ce qui veut dire qu'il n'a pas de version premium de code.PluginsSynchronisation des plugin et des thèmesPremiumLa version premium de %s a été activée avec succès.La version premium de l'add-on est déjà installée.Version premiumVersion premium déjà active.TarifsPolitique de confidentialitéPoursuivreID du processusTraitement en coursProduitsSommaire du programmeMéthodes de promotionRégionClef publiqueAcheter une licenceCommentaires rapidesQuotaRenvoyer l'email d'activationParrainez des nouveaux clients pour notre %s et gagnez une commission de %s sur chaque vente réussie que vous affiliez.Renouvelez votre licenceRenouvelez votre licence maintenantDemandesVersion de WordPress requiseRésultatSDKChemin d'accès du SDKÉconomisez %sCrons programmésCaptures d'écranRecherche par adresseClef secrêtePage %s sécurisée HTTPS, s'exécutant sur un domaine externeIl semble que nous ayons un problème temporaire avec l'annulation de votre abonnement. Merci de réessayer dans quelques minutes.Il semble que nous ayons un problème temporaire pour annuler votre période d'essai. Merci de réessayer dans quelques minutes.Il semble que vous ayez la dernière version.Choisir le paysEnvoyer le code de la licenceMise en place des options de la base de donnéesSimuler la mise à jour du réseauSimuler la promotion d'essaiLicence 1 siteSite IDSite ajouté avec succès.SitesPasser & %sSlugRéseaux sociaux (Facebook, Twitter, etc.)Désolé pour le dérangement et nous sommes là pour vous aider si vous nous le permettez.Désolé, nous ne pouvons pas mettre à jour l'email. Il existe déjà un autre utilisateur avec cette adresse.DébutEssai gratuitCommencer ma %s gratuiteÉtatEnvoyer & %sInscriptionSupportForum de SupportSynchronisation des données depuis le serveurCode TVAConditions générales de serviceMerci d'avoir postulé à notre programme d'affiliation, malheureusement, nous avons décidé pour le moment de décliner votre dossier. Merci d'essayer à nouveau d'ici 30 jours.Merci d'avoir postulé à notre programme d'affiliation, nous regarderons votre dossier durant les 14 prochains jours et nous reviendrons vers vous avec d'autres informations.Merci beaucoup d'utiliser %s et ses add-ons !Merci beaucoup d'utiliser %s !Merci beaucoup d'utiliser nos produits !Merci !Merci %s !Merci pour la confirmation du changement de propriétaire. Un email vient d'être envoyé à %s pour la validation finale.Le %s a cassé mon siteLe %s n'a pas fonctionnéLe %s n'a pas fonctionné comme prévuLe %s est bien mais j'ai besoin de fonctionnalités spécifiques que vous ne proposez pasLe %s ne fonctionne pasLe %s a soudainement arrêté de fonctionnerL'installation a commencé et peut prendre quelques minutes pour se finir. Merci de patienter jusqu'à ce qu'elle soit terminée - veuillez ne pas rafraichir cette page.Le package du plugin à télécharger ne contient pas de dossier avec le bon slug et iln'a pas été possible de le renommer.La mise à jour du %s s'est terminée avec succès ThèmeChangement de ThèmeThèmesIl y a une %s de %s disponible.Il y a une nouvelle version disponible de %s. Ce plugin n'a pas été indiqué comme étant compatible avec votre version actuelle de WordPressCe plugin n'a pas été testé avec votre actuelle version de WordPressTimestampTitreTotalVillePériode d'essaiTypeImpossible de se connecter au système de fichiers. Merci de confirmer vos autorisations.Licences sites illimitésMises à jour illimitéesCommissions illimitées.Jusqu'à %s SitesMise à jourMettre à jour la licenceMises à jour, annonces, marketing, pas de spamMise à jourTéléverser et activer la version téléchargéeGénialUser IDUtilisateursValeurUn email de vérification vient d'être envoyé sur %s. Si vous ne le recevez pas d'ici 5 minutes, merci de vérifier dans vos spams.VérifiéVérifier l'emailLa version %s vient d'être publiée.Voir les détailsVoir les fonctionnalités payantesAttentionNous ne trouvons aucune licence active associée avec cette adresse email, êtes-vous qu'il s'agit de la bonne adresse ?Nous ne trouvons pas votre adresse mail dans notre système, êtes-vous qu'il s'agit de la bonne adresse ?Nous avons fait quelques modifications au %s, %sNous sommes impatient de vous présenter l'intégration Freemius au niveau réseau.Statistiques du site web, de l'adresse email et des réseaux sociaux (optionnel)À quoi vous attendiez-vous ?Quelle fonctionnalité ?Quel est votre %s ?Quel prix seriez-vous prêt à payer ?Que recherchez-vous ?Quel est le nom du %s ?Où allez-vous faire la promotion du %s ? Page WordPress.org du pluginOuiOui - %sVous avez déjà utilisé la période d'essai.Vous êtes à 1 clic de commencer votre période d'essai gratuite de %1$s jours de la formule %2$s.Vous êtes tout bon !Vous utilisez déjà le %s en période d'essai. Il ne reste qu'une étape - %sVous pouvez toujours profiter de toutes les fonctionnalités de %s mais vous n'aurez plus accès aux mises à jour de sécurité ou de fonctionnalités de %s, ni au support.Vous n'avez pas de licence valide pour accéder à la version premium.Vous avez une license pour %s.Votre %s a bien été mis à jour.Peut-être que cela vous a échappé mais vous n'êtes pas obligé de partager la moindre information et vous pouvez juste %s l'enregistrement.Vous avez déjà validé notre suivi d'utilisation qui nous permet de continuer à améliorer le %s.Vous avez déjà validé notre suivi d'utilisation qui nous permet de continuer à les améliorer.Votre Add-on %s a bien été mis à jour.Votre période d'essai %s a bien été annulé.Votre compte a été activé avec succès avec la formule %s.Votre dossier d'affiliation pour %s a été accepté ! Identifiez-vous dans votre espace affilié sur : %s.Votre compte affilié a été suspendu temporairement.Votre email a été vérifié avec succès - vous êtes FORMIDABLE !Votre période d'essai gratuite est terminée. %1$sFaites la mise à jour maintenant%2$s pour continuer à utiliser le %3$s sans interruption.Votre période d'essai gratuite est terminée. Vous pouvez continuer à utiliser toutes nos fonctionnalités gratuites.Votre licence a été annulé. Si vous pensez qu'il s'agit d'une erreur, merci de contacter le support.Votre licence a expiré.%1$sFaites la mise à jour maintenant%2$s pour continuer à utiliser le %3$s sans interruption.Votre licence a expiré. Vous pouvez toujours utiliser les fonctionnalités %s mais vous devrez renouveler votre licence pour recevoir les mises à jour et une assistance.Votre licence a expiré. Vous pouvez toujours utiliser la version gratuite indéfiniment.Votre licence a bien été activée.Votre licence a bien été désactivé, vous utilisez à présent la formule %s.Votre nom a été mis à jour.Votre formule a bien été modifié vers %s. Votre formule a bien été mise à jour.Votre abonnement a bien été annulé. Votre licence de la formule %s expirera dans %s.Votre période d'essai a bien démarré.Code postalDirectement%s ne peut pas fonctionner sans %s.%s ne peut pas fonctionner sans le plugin.Avertissementautoriser%s restante(s)Activation en coursannéeAPIFermerDebuggageFélicitationsBloquéConnectéTélécharger la dernière versionTélécharger la dernière version gratuiteMensuelExpirationCheminEmail en cours d'envoimoisAnnuelAnnuelUne foisFormuleClef secrète manquanteVersions du SDKLicenceSynchroniserSynchroniser la licenceAuteurOffOnBasé sur %sCommencer l'essai gratuitFermerFermerDésactivationdéléguerne %sPAS%s m'envoyer de mises à jour de sécurité ou de fonctionnalités, ni de contenu instructif, ni d'offre.Formule %s%s FacturéBestHeyOupsHey %s,YoupilicenceSitesmsNouvelle versionNon vérifiéTarifTarifsVersionsecpasserHmmcommencer la période d'essaiabonnementChangementla dernière version de %s iciessaiPériode d'essaiSupprimerRétrograderÉditerCacherInscriptionDésinscriptionAcheterAfficherPasserMise à jourMise à jourIl y a %sPK! ĕ $freemius/languages/freemius-it_IT.monu[eD5l&@3AA33n&424Z4h#5d5S5%E6 k6 w66666C7z7_/888#88 9 9 9&9-959>9F9@O9H99O9j<::C;G;f;;;;; ;;;;;&<-/<]< r<|<<<<<5<<= = ='&=N= g= t=~=o==>G>T>B?a? @@":@]@(z@2@!@a@+ZA0AAAAAA BBB+B4Bcyy y&y<zD>zZzTzR3{.{.{{-|9$}Z^}3}<}b*~P~U~_4K/({G#%)6$`U)ہ4L;a6>Ԃ4T$jŃ&*&7Q3ׄ  6Nb*1܅# 0L^ nzƆˆ І݆N5Trćʇ13C T ` m x 9ɈCGQL ʼn߉  . 8 B N [ i s } K֎}gXl_Ő"%.Hw :~Uv̓)%0Vet ^[kB|ΕK #* 1;Pn,.× 3MT<Z  +ʘ %2tFÙMfY"1"#">+a7Ŝg)LSvʝ . 3@S\d ivF#'=  3=Qe # 0>D'V ~-I@BKRen # ǢԢ ٢}s ۤ#|$!<\ v'ϧvާwUͨӨ $J@" /6R Zf7x$ '9 ?;O{h"#Įܮ!)?^Y+үI)Ұ]lZ#DZ^!P#r (ٲ-Fem ˳0g<fմ<=ӵ&~8A J3~[/-#< `  Ҹ A#e { ¹Թ *G LYhn '% * &,0C]"Ļջ4u !#5Y_r = I SXY-% [*Cʿ5w 2SS*   &* C Q \Ug7-#Qb   /C'I[q !"$ GQ U bow @|nv-%=Tq   9(&QO/ 5#Aev |  'V~( zOg&wH*"6`u7b *UIN P Wap@u *# N%\  OZ#i{gCs%ME881$Di"##*>Ki>2j''Eo&=G@}FCM<D3?Ii@H4h}g\NeW/1Ja.,-*6]a4 '; cn w  "  '38 @ LV\ kx   #_&   (1:IFMR#$,Khn}   jd,y<<"< NF;*YnG"C i%S7M=PL4X:wD6:,H|Z(F/A!9'XX6 Dra-meOTr/uu2CngWv &[dhHK'To3Gv9A@F_Q\{UlVN91N]Rx&b[}O0 ^pj8(.5#GR3!iQ Qf  0o 2m {OE ZT23c] P e ?yVE-SgD"U$CESK7za ]@!x=%^;kBY >?z8;sRV6`s>_}PwMba.+[ f-ld#`0LU @\)Y$qeZ|c8+~4#=J*B.k:t$JW\p'L*>)Iqh/tKb%(5^?&W4AIM)~1BH`75J_I+c1, %s to access version %s security & feature updates, and support. 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. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"The ", e.g.: "The plugin"The %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.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.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre both %s and %s your email addresses?Are you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDisabling white-label modeDon't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue 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 violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Duplicate WebsiteDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEmail address updateEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid clone resolution action.Invalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Is ActiveIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerLong-Term DuplicateMessageMethodMigrateMigrate LicenseMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - only move this site's data to %sNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires WordPress VersionReset Deactivation SnoozingResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTo enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageWould you like to merge %s into %s?Would you like to proceed with the update?YesYes - %sYes - both addresses are mineYes - move all my data and assets from %s to %sYes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.You already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesYou might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactivate a license hereactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdatadaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,hourhoursinstalled add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionproductsrevert it nowsecondssecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe above-mentioned sitesthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Leo Fajardo , 2022 Language-Team: Italian (Italy) (http://www.transifex.com/freemius/wordpress-sdk/language/it_IT/) Language: it_IT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2; X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js %sper accedere alla versione%s per aggiornamenti, funzionalità e supporto.Il %s %slink per scaricare%s, la licenza e le istruzioni d'installazione sono state inviate a %s. Se non trovi l'email entro 5 minuti cerca nella cartella dello spam.La versione a pagamento di %1$sè già installata. Attiva questione versione per iniziare ad usare le funzionalità di %2$s.%3$sIl %s%1$s è entrato in modalità sicurezza perché è stato rilevato che %2$s è una copia esatta di %3$s./%1$sfermerà tutti i pagamenti ricorrenti futuri e il tuo piano %2$sche scadrà in %3$s.%1$s fermerà subito tutti i futuri pagamenti ricorrenti e il tuo piano licenza scadrà in %s.,Completa l'attivazione di "%s" oraL' add-on %s è stato acquistato con successo.%s Installazioni%s Licenze%s fa%se i suoi addon%s con aggiornamenti automatici di sicurezza e funzionalità con le funzionalità a pagamento manterrà il funzionamento senza interruzioni fino a %s (o quando la licenza scade, oppure quello che avviene prima).%scommissione quando un utente acquista una nuova lcienza.Il periodo di prova gratuito %s è stato annullato con successo. Siccome l'add-on è premium, è stato disattivato automaticamente. Se vorrai usarlo in futuro, dovrai comprare una licenza.%s è un add-on premium. Devi comprare una licenza prima di poter attivare il plugin.%s è il mio indirizzo email%s è il mio indirizzo email%s è il nuovo proprietario dell'account.%s quantità minima per il pagamento.%s o superiore%s valutazione%s valutazioni%s sec%s stella%s stelle%s volta%s volte%sper accedere alla versione %sper aggiornamenti di sicurezza, nuove funzionalità e supporto.%s cookie di tracciamento dopo che la prima visita per massimizzare i margini di guadagno. Funzionalità a pagamento di %s%sClicca qui%s per scegliere i siti dove vuoi attivare la licenza.Un email di conferma è stata inviata a %s. Il proprietario dell'email deve confermare l'aggiornamento nelle prossime 4 ore.Una email di conferma è stata inviata a %s. Puoi confermare l'aggiornamento nelle prossime 4 ore. Se non puoi trovare l'email verifica la tua cartella dello spam.API←➤AccountDettagli dell'accountAzioniAttivaAttiva %sAttivare il piano %sAttiva le funzionalità di %sAttiva versione gratuitaAttiva licenzaAttiva le licenze su tutti i siti in attesa.Attiva la licenza su tutti i siti del network.Attivare questo addonAttivatoAdd-on per %sAddon del modulo %sAggiungi un altro dominioAdd-onAddonL'add-on dev'essere distribuito da WordPress.org o Freemius.IndirizzoRiga indirizzo %dAffiliatiAffiliazioneDopo il tuo %s gratuito, paghi solamente %sAccetta e attiva la licenzaTutte le richiesteTutti i tipiConsenti & ContinuaIn caso puoi saltare per adesso e attivare la licenza successivamente nella tua pagina di attivazione network di %s.ImportoUn download con installazione automatica di %s (versione a pagamento) da %s inizierà in %s. Se preferisci farlo manualmente, fai clic sul pulsante per annullare.Un errore sconosciuto è avvenuto durante l'attivazione della modalità beta.Un errore sconosciuto è avvenuto mentre si passava alla licenza in modalità whitelabel.Un errore sconosciuto è avvenuto.Un aggiornamento per la versione Beta sostituirà la versione installata di %scon l'ultima versione Beta, utilizzare con attenzione e non su siti in produzione. Sei stato avvisato!Feedback anonimoApplica su tutti i siti in attesa.Applica su tutti i siti della rete.Applica per diventare un affiliatoSono entrambi %s e %s tuoi indirizzi email?Sei sicuro di voler eliminare tutti i dati di Freemius?Sei sicuro di voler procedere?Ci riserviamo 30 giorni in caso di rimborsi, paghiamo le commissioni se sono più vecchie di 30 giorni.Associa con il proprietario della licenzaL'installazione automatica funziona solo per gli utenti che hanno dato il consenso.Rinnovo automatico in %sInstallazione automaticaValutazione mediaFantasticoDiventa un affiliatoBetaFatturazioneRicevute e FattureBloccatoBlog IDBodyPiano BundleNome della compagniaCompra una licenza oraCompra la licenzaCambiando l'utente accetti di trasferire la proprietà dell'account a:Non trovi la tua chiave di licenza?AnnullaAnnulla %s & ProseguiAnnulla %s, non ho bisogno di aggiornamenti di funzionalità e sicurezza o supporto per %sperché non ho intenzione di usare %ssu questo sito o qualunque altro sito.Annulla %s?Annulla installazioneAnnulla sottoscrizioneAnnulla prova gratuitaAnnullatoCancellazione di %sCancellazione %s...Cancella la sottoscrizioneCancellando il periodo di prova gratuito bloccherai immediatamente l'accesso a tutte le funzionalità premium. Vuoi continuare?Cambia licenzaCambia ProprietarioCambia pianoCambia utenteCassaElimina cache APISvuota le Transient degli aggiornamentiClicca quiFai clic qui per usare il plugin anonimamenteFai clic per vedere le recensioni che hanno fornito una valutazione di %sFare clic per visualizzare lo screenshot in grandi dimensioni %dProdottiCodiceCompatibile fino aContattiContatta il supportoContattaciContributoriNon é stato possibile attivare %s.NazioneTipo di CronDataDisattivaDisattiva licenzaDisattivare o disinstallare %s che disabiliterà automaticamente la licenza, che permetterà di utilizzarla in un altro sito.Disattiva la tua licenza bloccando tutte le funzionalità premium ma potrai attivare la licenza su un altro sito. Sei sicuro di voler continuare?DisattivazioneDebug LogLa modalità Debug è stata attivata con successo e sarà disattivata automaticamente in 60 minuti. Puoi disattivarla prima cliccando sul link "Ferma Debug".Delega ai proprietari del sitoEliminare tutti gli accountDettagliDisabilita la modalità white labelNon annullare %s, sono interessato in ottenere gli aggiornamenti di sicurezza, nuove funzionalità o contattare il supporto.Non hai una chiave di licenza?Fai una donazione a questo pluginTorna al piano precedenteDownloadScarica la versione %sScarica la versione a pagamentoScarica l'ultima versione di %sScarica l'ultima versioneScaricatoCausa la %sDirettiva per la protezione dei Dati Europea (GDPR)%sabbiamo adeguato i requisiti che fornisci per il consenso, confermando che accetti di lasciare i dati.A causa della violazione dei nostri termini di affiliazione abbiamo deciso di bloccare temporaneamente il tuo account affiliativo. Se hai domande contatta il supporto.Sito duplicatoDurante la procedura di aggiornamento abbiamo individuato%d sito/i che sono in attesa della attivazione della licenza.Durante la procedura di aggiornamenti abbiamo individuato %s sito/i del network che sono in attesa di un tuo controllo.EmailIndirizzo emailAggiorna l'indirizzo emailAbilita la modalità white labelFineInserisci l'indirizzo emailInserisci il dominio del tuo sito o altri siti da dove vuoi promuovere %s.Inserisci qui sotto l'indirizzo email che hai usato per registrare l'aggiornamento e ti invieremo di nuovo la chiave di licenza.Inserisci il nuovo indirizzo emailErroreErrore ricevuto dal server:ScadutoScade in %sDomini aggiuntiviDomini aggiuntivi dove ci sarà il modulo promozionale.FileFiltroPer essere accettato del regolamento WordPress.org, prima di attivare il periodo di prova devi accettare di condividere informazioni come il tuo utente e dati non sensibili. Permettendo a %s di inviare dati periodicamente a %s per verificare gli aggiornamenti e approvare il periodo di prova.GratuitoProva gratuitaVersione gratuitaFreemius APIDebug FreemiusL'SDK di Freemius non è riuscito a trovare il file principale del plugin. Per favore contatta sdk@freemius.com riportando l'errore.Stato di FreemiusFreemius è il nostro servizio di licenze e aggiornamentiNome completoFunzioneOttieni delle commissioni dal sistema automatizzato di rinnovo.Ottieni gli aggiornamenti per le nuove versioni Beta di %s.Hai una chiave di licenza?Ciao, sai che %s ha il programma di affiliazione? Se ti piace %s puoi diventare un nostro ambasciatore e guadagnare denaro!Come sta andando con %s? Prova tutte le funzionalità premium di %s con una prova gratuita di %d giorni.Come faccio a caricare ed attivare?Come ci promuoverai?Accetto - Cambia utenteNon posso piú pagarloNon capisco come farlo funzionareNon voglio condividere i miei dati con teHo trovato un migliore %sHo aggiornato il mio account, ma quando cerco di sincronizzare la licenza, il piano rimane %s.Non ho più bisogno di %sHo avuto bisogno di %s per un breve periodoIDSe questo è un duplicato a lungo termine per mantenere gli aggiornamenti automatico e funzionalità a pagamento dopo %s, verifica %s.Se fai clic questa decisione sarà delegata agli amministratori del sito.Se hai un attimo, facci sapere perché %sPuoi abbandonare la proprietà dell'account %s a %scliccando il pulsante Cambia proprietario.Se vuoi utilizzare %s su questi siti, inserisci la tua licenza sotto e fai clic sul pulsante di attivazione.Avviso Importante di aggiornamento:In %sIn caso NON hai pianificato di usare %s su questo sito (o ogni altro sito) vuoi cancellare %s?Installa la versione gratuita oraInstalla l'ultima versione gratuitaInstalla oraInstalla l'aggiornamento oraInstallazione plugin: %sAzione di risoluzione del clone fallita.ID modulo non valida.Nuovo utente ID o indirizzo email non valido.Raccolta dati siti non valida.Fattura%2$s è un duplicato di %4$s?%2$s è un nuovo sito?%2$s è la nuova home di %4$s?è attivaSi tratta di un sito di un cliente?%sse vuoi puoi nascondere informazioni sensibili come la tua email, licenza, prezzi e indirizzi dalla tua bacheca di WP.Sembra che la licenza non possa essere attivata.Sembra che la disattivazione della licenza non sia riuscita.Sembra che tu non stia più usando la prova gratuita, quindi non c'è niente che tu debba annullare :)Sembra che tu sia ancora usando il piano %s. Se hai effettuato un upgrade o cambiato il piano, è probabile che ci sia un problema nei nostri sistemi.Sembra che il tuo sito non disponga di alcuna licenza attiva.Richiede la attivazione della licenza.Sembra che uno dei parametri di autenticazione sia sbagliato. Aggiorna la tua chiave pubblica, Secret Key & User ID e riprova.È una %s temporanea. Sto solo cercando di risolvere un problema.Non é quello che stavo cercandoEntra nel programma BetaLe informazioni sugli add-on di %s vengono scaricate da un server esterno.ChiaveCondividi cosa non ha funzionato in modo da migliorare il prodotto per gli utenti futuri...Spiegandoci il motivo ci aiuterai a migliorare.UltimoUltimo aggiornamentoUltima licenzaUltima versione gratuita installataVersione più recente installataScopri altroLunghezzaLicenzaLicense AgreementLicense IDChiave della licenzaProblemi di licenza?Chiave di licenzaLa chiave licenza è vuota.Tutta la vitaTi piace %s? Diventa il nostro ambasciatore e guadagna denaro ;-)Carica opzioni del DBLocalhostLogLoggerDuplicato a lungo termineMessaggioMetodoSpostaSposta la licenzaMigra le opzioni al NetworkApplicazioni mobileModuloPercorso moduloTipo di moduloUlteriori informazioni su %sNomeNetwork BlogUtente NetworkNuovoNuova versione disponibileNuovo sitoNuova versione gratuita (%s) installataVersione più recente (%s) installataNewsletterSuccessivoNoNo sposta solo i dati di questo sito su %sNessun IDNessun impegno con %s - cancella quando vuoiNessun impegno per %s giorni - puoi annullare in qualsiasi momento!Nessuna carta di credito richiestaNessuna scadenzaNon in scadenzaNessuno dei piani di %ssupporta il periodo di prova.OKQuando la tua licenza scadrà, potrai comunque continuare a usare la versione gratuita, ma NON avrai accesso alle funzionalità %s.Quando la tua licenza scadrà non potrai più usare %s, a meno che lo attivi di nuovo con una licenza premium valida.IscrivitiCancella iscrizioneAbilita "%s" per renderlo migliore!AltroEmail proprietarioID proprietarioNome proprietarioPCI compliantGli add-on a pagamento devono essere distribuiti da Freemius.Indirizzo account email PaypalPagamentiI pagamenti sono in Dollari Americani e processati mensilmente da PayPal.PianoIl piano %s non esiste, per questo motivo non è possibile iniziare il periodo di prova.Il piano %s non supporta il periodo di prova.ID PianoContattaci quiContattaci con il seguente messaggio:Scarica %s.Per favore inserisci la chiave di licenza che hai ricevuto via mail subito dopo l'acquisto:Inserisci la chiave della licenza per abilitare la modalità Debug:Facci sapere ogni sito o statistiche social valide, es: visite uniche mensili, numero di sottoscrizioni email, follower ecc (tratteremo queste informazioni come riservate).Segui i passi seguenti per completare l'aggiornamentoFacci sapere se vuoi essere contattato per aggiornamenti di sicurezza e di funzionalità, contenuti formativi e offerte occasionali:Si prega di notare che non saremo in grado di garantire lo stesso prezzo per rinnovi/sottoscrizioni dopo la cancellazione. Se scegli di rinnovare l'abbonamento manualmente in futuro, dopo un aumento del prezzo, che di solito avviene una volta l'anno, ti verrà addebitato il nuovo prezzo.Fornisci i dettagli su come intendi promuovere %s. (sii più esplicativo possibile)Per favore inserisci il tuo nome completo.PluginHomepage del pluginPlugin IDInstallazione del pluginChangelogDescrizioneFAQCaratteristiche & prezziInstallazioneAltre noteRecensioniIl plugin è un "Serviceware", quindi non dispone di una versione del codice Premium.PluginSincronizzazione plugin e temiPremiumLa versione 1%s Permium è stata attivata con successo.Versione Premium dell'add-on già installata.Versione premiumVersione Premium già attiva.PrezziPolitica sulla privacyProseguiID processoElaborazioneProdottiSommario programmaMetodi promozionaliProvinciaChiave pubblicaAcquista licenzaContinua a comprareSuggerimenti rapidiQuotaInvia nuovamente l'email di attivazioneComunica nuovi clienti al nostro %s e guadagna %s di commissione per ogni vendita avvenuta!Rinnova licenzaRinnova la tua licenza oraRichiesteRichiede la versione di WordPressResetta l'avviso di disattivazioneRisultatoSDKPercorso SDKRisparmia %sSalvatoAzioni programmateScreenshotCerca per indirizzoChiave segretaPagina sicura su protocollo HTTPS %s eseguita su dominio esternoSembra che stai avendo dei problemi temporanei con la cancellazione della sottoscrizione. Prova nuovamente tra pochi minuti.Stiamo avendo qualche problema temporaneo con l'annullamento del periodo di prova. Riprova tra qualche minuto.Sembra che tu abbia la versione più recente.Seleziona NazioneInvia chiave di licenzaImposta opzione del DBSimula aggiornamento networkSimula la prova TrialLicenza per sito singoloID del sitoSito accettato con successo.SitiSalta & %sSlugSilenzia e %sPuoi riutilizzare la licenza quando %s non è piu attivo.Social network (Facebook, Twitter, ecc.)Siamo spiacenti per l'inconveniente e siamo qui per aiutarti con il tuo permesso.Siamo spiacenti, non siamo riusciti a completare l'aggiornamento via email. Un altro utente con lo stesso indirizzo email è già registrato.AvviaAvvia DebugInizia il periodo di prova gratuitoInizia la mia %sStatoFerma DebugInviaInvia e %sSottoscriviSupportoForum di supportoSincronizza i dati dal serverNumero Partita Iva o VATTermini del ServizioGrazie per la partecipazione al nostro programma di affiliazione, sfortunatamente abbiamo valutato di rifiutare la tua richiesta. Prova nuovamente fra 30 giorni.Grazie per la partecipazione al nostro programma di affiliazione, valuteremo la tua richiesta durante i prossimi 14 giorni e ti contatteremo per maggiori informazioni.Grazie per utilizzare %se i suoi addon!Grazie per utilizzare %s!Grazie per utilizzare i nostri prodotti!Grazie!Grazie %s!Grazie per aver confermato il cambiamento del proprietario. Un' email è stata appena inviata a %s per la conferma finale.%s ha rotto il mio sito%s non funziona%s non ha funzionato come mi aspettavo%s è ottimo ma ho bisogno di una funzionalità specifica non supportata%s non funziona%s ha improvvisamente smesso di funzionareI prodotti seguentiIl processo d'installazione è iniziato e potrebbe impiegare alcuni minuti per completarsi. Attendi finchè non ha finito, assicurandoti di non ricaricare questa pagina.Il prodotto sottostante è stato messo in modalità sicurezza perchè abbiamo notato che %2$s è una copia esatta di %3$s: %1$sIl prodotto sottostante è stato messo in modalità sicurezza perchè abbiamo notato che %2$s è una copia esatta di questi siti %3$s: %1$sIl pacchetto remoto del plugin non contiene una cartella con lo slug desiderato e la rinominazione non ha funzionato.L'aggiornamento di %s è stato completato con successo.TemaCambio temaTemiC'è un %sdi %s disponibile.C'è una nuova versione di %s disponibile.Questo plugin non è stato segnato come compatibile con la tua versione di WordPress.Questo plugin non è stato testato con la versione corrente di WordPress.TimestampTitoloAbilita la modalità Debug, inserisci la chiave segreta del proprietario della licenza (UserID = %d), che puoi trovare nella sezione "Profilo" della dashboard utente:TotaleCittadinaProva gratuitaTipoImpossibile accedere al filesystem. Conferma le tue credenziali.Licenze illimitateAggiornamenti IllimitatiCommissioni illimitate.Fino a %s sitiAggiornaAggiorna licenzaAggiornamenti, annunci, marketing, no spamAggiornamentoCarica e attiva la versione scaricataForteBacheca UtenteID utenteChiave utenteUtentiValoreL'email di verifica è stata inviata a %s. Se dopo 5 minuti non è ancora arrivata, per favore controlla nella tua casella di posta indesiderata.VerificatoVerifica emailLa versione %s é stata rilasciata.Visualizza dettagliVedi funzionalità a pagamentoAvvisoNon siamo riusciti a trovare alcuna licenza attiva associata al tuo indirizzo email, sei sicuro che sia l'indirizzo giusto?Non siamo riusciti a trovare il tuo indirizzo email nel sistema, sei sicuro che sia l'indirizzo giusto?Non possiamo caricare la lista degli addon. Probabilmente è un nostro problema, prova di nuovo fra qualche minuto.Abbiamo fatto alcune migliore a %s,%sSiamo felici di presentarvi il supporto al sistema multi network di Freemius.Siti, email e statistiche dei social network (opzionali)Benvenuto nel %s! Per iniziare inserisci la tua licenza:Che cosa ti aspettavi?Quale funzionalitá?Qual è il tuo %s?Che prezzo ritieni opportuno pagare?Che cosa stai cercando?Qual è il nome di %s?Dove vuoi promuovere %s?Pagina dei plugin di WordPress.orgVuoi fondere %s in %s?Vuoi procedere con l'aggiornamento?SiSI - %sSi entrambi gli indirizzi sono mieiSi muovi tutti i miei e risorse da %s a %sSi, %2$s è un duplicato di %4$s per il motivo di test, staging o sviluppo.Si %2$s è un nuovo e sito differente che è separato da %4$s.Hai già utilizzato una prova gratuita in passato.Sei a un clic di distanza dall'iniziare il tuo periodo di prova gratuito di %1$s giorni per il piano %2$s.Sei fantastico!Stai già usando %s in modalità prova.Sei a un passo dalla fine - %sPuoi continuare ad utilizzare le funzionalità%sma non avrai accesso agli aggiornamenti di sicurezza, nuove funzionalità o supporto.Non disponi di una licenza valida per accedere alla versione Premium.Hai la licenza %s.Hai la licenza %s.Hai aggiornato con successo il tuo %s.Hai segnato questo sito, %s, come duplicato temporaneo di %s.Hai marchiato questo sito, %s, come duplicato temporaneo di questi sitiPotresti non averci fatto caso, ma non sei obbligato a condividere i tuoi dati e puoi semplicemente %s la tua partecipazione.Hai già accettato il tracciamento d'uso, ci aiuterà a migliorare %s.Hai già accettato il tracciamento d'uso che ci aiuta a migliorare.Il piano del tuo add-on %s è stato aggiornato con successo.Il tuo periodo di prova gratuito %s è stato annullato con successo.La tua licenza%s è stata segnata come white label per nascondere informazioni sensibili dalla bacheca di WP (es: la tua email, licenza, prezzi e indirizzi). Se vuoi tornare indietro puoi farlo semplicemente tramite%s. Se è stato un errore guarda anche %s.La tua licenza%s è stata disattivata con successo.Il tuo account è stato attivato correttamente con il piano %s.La tua applicazione di affiliazione per %s è stata accettata! Accedi alla tua area di affiliazione a %s.Il tuo account di affiliazione è stato sospeso temporaneamente.Il tuo indirizzo email è stato verificato con successo - SEI UN GRANDE!La tua versione prova è scaduta.%1$s aggiorna ora %2$s per continuare ad usare %3$s senza interruzioni.La tua versione di prova gratuita è scaduta. Puoi continuare ad usare tutte le funzionalità gratuite.La tua licenza è stata cancellata. Se credi sia un errore, per favore contatta il supporto.La tua licenza è scaduta. %1$saggiorna ora %2$sper continuare ad utilizzare %3$s senza interruzioni.La licenza è scaduta. È comunque possibile continuare a utilizzare tutte le funzionalità di %s, ma sarà necessario rinnovare la licenza per continuare a ricevere gli aggiornamenti ed il supporto.La tua licenza è scaduta. Puoi continuare ad usare la versione gratuita %s per sempre.La tua licenza è stata attivata correttamente.La tua licenza é stata disattivata con successo, sei tornato al piano %s.Il tuo nome è stato aggiornato correttamente.Il tuo piano è stato attivato con successo.Il piano è stato cambiato con successo a %s.Il piano è stato aggiornato con successo.La tua sottoscrizione è stata cancellata con successo. La licenza del piano %sscadrà in %s.La versione di prova è stata avviata correttamente.CAPSìattiva una licenza quiAttiva%s non può funzionare senza %s.%s non può funzionare senza il plugin.Attenzionepermetti%s rimanentiAttivazioneannoAPIChiudiDebuggingCongratulazioniBloccatoConnessoScarica l'ultima versioneScarica l'ultima versione gratuitaMensilmenteScadenzaPercorsoInvio emailmeseAnnualeAnnualmenteUna voltaPianoNessuna chiaveVersioni SDKLicenzaSincronizzaSincronizza la licenzaAutoreNon attivoAttivobasato su %sInizia il periodo di prova gratuitoChiudiChiudidatagiornidisattivazione in corsodelega%snon %s mi invierà aggiornamenti di funzionalità e sicurezza, contenuti formativi e offerte.Piano %sFatturato %sMiglioreHeyOpsHey %s,oraoreInstallatoEvvailicenzaSitimsnuova versione Betanuova versionenon verificatoPrezzoPrezziopzionaleVersioneprodottitorna indietrosecsembra che la chiave che hai inserito non risulti nei nostri registri.inviami aggiornamenti di funzionalità e sicurezza, contenuti formativi e offerte.saltaUhmInizia il periodo di prova gratuitosottoscrizionepassa ai siti menzionati sopra stantel'ultima versione %s é quìprovaProva gratuitaEliminaDowngradeModificaNascondiIscrivitiCancella iscrizioneAcquistoMostraSaltaAggiornaAggiornamento%s faPK!Oj--!freemius/languages/freemius-ta.monu[!,A,n;-h-d.Sx.%. . . //6$/[/_0#p00 0 0 000000@0H:11O111 2)212A2I2 R2^2o222&2-23 33.3C3V3]35e333 3 3'33 4 4!4o2444GH555Y6l6"6626!6a707777778 88$8-858 :8 F8T8 f8r8888 =9H9\9 p9 }9 999Y9:+: <:H:Q:V:f:(:1:%::;;;@;Q;Y; i; t;;; ;; ;;x;E< < << ==t%=======> 8>C>>[?f?X@ ^@l@Yp@a@,A2ARA ZA hA;vAAAAB B B BBjBaC pCzC3C2CC~CU}DDDE)#E-ME{ESEE'E#FM&F7tFgFpGGGyGH8H XHdHwHH HH H1H.IO4IIAJyFJJJaJXKB\K,KK K KK L "L-L4Lf*g&AgZhgTgRh.kh.h9hZi3^i<ibiP2jUj_j9kKk( lGIl#l%l)l$mU*m)mmmm;m6*n>annnnn$no6oRono&o*o7op-pKp3dpppppp* q17qiqq#qqqq qr'r>r Sr`rNirrrrs s1sBs1]sssss s s s ss tC!tetQjtt t tttu u#u 2u =f)XΠ'' OVYP"/iRk(@G_M36je}ݩת1(;. ER$٭3(2[ۮYA@`4 ֲ-K]!syG>$ |E¸YI-g>޻c @ý_P#{t@ JM < TuV+NZ,W4g"xY8( 5Le !%,?U.p ; %=GSqR *`g;'U*}y"P&w_%d =![.} Q_1|`rvfP;c.+Z|WoRwOF2y4 $+2$^!,4ai1q7=W $=X,( 4(1I{eFFB'A $'8"`, Z4(@$ 1?X#qH/>4stk7M  > N[x .\d~G@73rk1WhA@@eI H"Ux.g%" !2"F?4"t!1!v1QjYp6UGW=lJi4r Z * #E ki ` &6 s] 9     < 1IC>~OJMgi&^w{ `FHp_vG  S!.!"'#"7K"M"""!"3#C#V#Z#.z#'#*#*#D'$cl$$$ %O %\%l%'|%%% %"%%& &'"&J&N&0Q&u&&'B8'{''(4(())%)'?)g)o)))8)%)E) @*M*m***k++@++ +;+!,!A, c,m,,,,%,----!F-!h---^l}G@!u 7IWUT[ z( i1#* eh.dVrK:}MoA="C8g/wthS>K L;$U J'VLZX0 \pW"Hn{8Fk(~`kb,)u2Ng0yqj{ \_sP YXd+Pa4y~ &|4v oER_A/N!F^[q2-DJ1=r @BzcD?SOG)%+tQ3 c55]9]l7mO v#xfYIE6`exZsM;p'ab3jmn  ,<:Q|>*T9Bw?H<$fiR&.% C6 %s to access version %s security & feature updates, and support. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundle PlanBusiness nameBuy a license nowBuy licenseCan't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClear Updates TransientsClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue 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 violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.During the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.Invalid site details collection.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense KeyLicense keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageWould you like to proceed with the update?YesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,installed add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingproduct versionVersionsecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Sankar Srinivasan , 2019 Language-Team: Tamil (http://www.transifex.com/freemius/wordpress-sdk/language/ta/) Language: ta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js %s பதிப்பின் பாதுகாப்பு & வசதி மேம்படுத்தல் மற்றும் உதவிக்கு%s.%1$sன் விலையுள்ள பதிப்பு ஏற்கனவே நிறுவப்பட்டுள்ளது. %2$s வசதிகளை பயன்படுத்த அதை செயல்படுத்தவும். %3$sவருகின்ற அனைத்து பணம் செலுத்துதல்களையும் %1$s உடன் நிறுத்துகிறது, மற்றும் உங்கள் %2$s திட்ட உரிமம் %3$sல் காலாவதியாகிறது.%1$s உடனடியாக அனைத்து எதிர்வரும் பணம் செலுத்தல்களை நிறுத்தும் மற்றும் உங்கள் %s திட்ட உரிமம் %sல் காலாவதியாகும்."%s" செயல்படுத்தலை முடியுங்கள்%s ஆட்-ஆனை வாங்கிவிட்டீர்கள்.%s நிறுவுதல்கள்%s உரிமங்கள்%s முன்பு%sம் அதன் ஆட்-ஆன்களும்ஒரு வாடிக்கையாளர் புது உரிமம் வாங்கும்போது %s லாபப் பங்கீடு.ஆட் ஆன் விலையுள்ளது என்பதால் %sன் விலையில்லா முன்னோட்டம் ரத்தானது. நீங்கள் உரிமம் வாங்கிப் பயன்பாட்டைத் தொடரலாம்.%s ஒரு விலையுள்ள ஆட்ஆன். ப்ளக்இன் செயல்பட நீங்கள் உரிமம் வாங்கவேண்டும்கணக்கின் புதிய உரிமையாளர் %s.%s குறைந்தபட்ச பணம் பெறுதல்.%s அல்லது அதிகமாக%s மதிப்பீட்டெண்%s மதிப்பீட்டெண்கள்%s sec%s நட்சத்திரம்%s நட்சத்திரங்கள்%s முறை%s முறைகள்%s பாதுகாப்பு மேம்படுத்தல் மற்றும் உதவிக்கு %sவருமானம் அதிகரிக்க, முதல் தள வருகையில் %s tracking cookie.%sன் விலையுள்ள வசதிகள்உங்களுக்கு வேண்டிய தளங்களில் உரிமத்தை செயல்படுத்த %s கிளிக் செய்க %sAPI←➤கணக்குகணக்கு விபரங்கள்செயல்கள்செயல்படுத்து%s செயல்படுத்து%s திட்டம் செயல்படுத்த%s வசதிகளை செயல்படுத்தவிலையில்லா பதிப்பை செயல்படுத்தஉரிமம் செயல்படுத்தமீதமுள்ள எல்லா தளங்களிலும் உரிமத்தை செயல்படுத்துகவலைப்பின்னலில் உள்ள எல்லா தளங்களிலும் உரிமத்தை செயல்படுத்துகஆட்-ஆன் செயல்படுத்தசெயல்படுத்தப்பட்டது%sக்கான ஆட்ஆன்கள்Module %sன் ஆட்ஆன்கள்அடுத்த தளத்தைச் சேர்க்கஆட் ஆன்ஆட்-ஆன்ஸ்ஆட்ஆன் WordPress.org அல்லது Freemius ஏதாவது ஒன்றில் வரிசைப்படுத்தப் பட்டிருக்க வேண்டும்.முகவரிமுகவரி வரி %dAffiliateபுரிந்துணர்வுவிலையில்லா %sக்குப் பிறகு, குறைந்தளவு %s செலுத்துங்கள்ஒப்புக்கொண்டு உரிமத்தை செயல்படுத்துகஅனைத்து வேண்டுகோள்கள்அனைத்து மாதிரிகள்அனுமதித்து தொடர்கஇல்லாவிட்டால், Network-level கணக்குப் பக்கத்தில் எப்போது வேண்டுமானாலும் உரிமத்தை செயல்படுத்திக் கொள்ளலாம்.தொகை%sலிருந்து %s (பணம் செலுத்திய) பதிப்பின் தானியங்கி பதிவிறக்கமும், நிறுவுதலும் %sல் ஆரம்பமாகும். இதை நீங்களே செய்ய விரும்பினால் ரத்து செய்யும் பட்டனை அழுத்தவும். உபயோகிப்பாளரின் பீட்டாவை செயல்படுத்துகையில், புதிய தவறு உருவாகியுள்ளது.என்னதென்றே தெரியாத ஒரு தவறு நேர்ந்துவிட்டதுஎச்சரிக்கை: %sன் முன்னோட்ட Beta பதிப்பின் மேம்பட்ட வடிவம் பழைய பதிப்பின் மீது நிறுவப்படுகிறது. அனாமதேய பின்னூட்டம்மீதமுள்ள அனைத்து தளங்களின் மீதும் பிரயோகிக்கவலைப்பின்னலின் அனைத்து தளங்களின் மீதும் பிரயோகிக்கAffiliate ஆக விண்ணப்பியுங்கள்அனைத்து Freemius தகவலையும் அழிக்க விருப்பமா?மேலே தொடர விருப்பமா?வாடிக்கையாளர் 30 நாட்களுக்கு பணம் திரும்பப் பெறலாம் Refund என்பதால் உங்களுக்கு லாபப் பங்கீடு 30 நாட்களுக்குப் பிறகே கிடைக்கும்.முன்னரே தெரிவு செய்திருந்தால் மட்டுமே தானியங்கி நிறுவுதல் நடைபெறும்.%sல் தானாக புதுப்பிக்கிறதுதானியங்கி நிறுவுதல்உத்தேச மதிப்பீட்டெண்அடி தூள்Affiliate ஆகுங்கள்பீட்டாவசூல்பில் & இன்வாய்ஸ்தடுக்கப்படுகிறதுBlog IDஅமைப்புகூட்டுத்திட்டம்தொழிலின் பெயர்புது உரிமம் வாங்குங்கள்உரிமம் வாங்கLicense Key காணவில்லையா?ரத்து%s ரத்து செய்க & தொடர்கநான் %sஐ இந்த தளத்திலோ அல்லது பிற தளத்திலோ உபயோகிக்க விரும்பவில்லை என்பதால் %sக்கு உதவியோ, பாதுகாப்பு மேம்படுத்தலோ தேவையில்லை. %sஐ ரத்து செய்யவும்.%s ரத்து செய்யவா?நிறுவுதலை ரத்துசெய்சந்தாவை ரத்து செய்வெள்ளோட்டம் ரத்து செய்கரத்தானது%s ரத்தாகிறது%s ரத்தாகிறது...சந்தா ரத்தாகிறதுவெள்ளோட்டத்தை ரத்து செய்தால் அனைத்து விலையுள்ள வசதிகளும் நிறுத்தப்படும். சரியா?உரிமம் மாற்றஉரிமை மாற்றம்திட்டம் மாற்றCheckoutஊர்API Cache நீக்கClear Updates Transientsபிளக்இன்னை அநாமதேயமாக பயன்படுத்த இங்கே கிளிக் செய்யவும்%s குறித்த மதிப்பீடு & விமர்சனங்களை கிளிக் செய்து காண்ககிளிக் செய்து %dன் முழுஅளவு திரைநகல் காண்கதயாரிப்புகள்Codeஒத்திசைவு உயர்நிலைதொடர்புஉதவியை அணுகவும்தொடர்பு கொள்ளுங்கள்பங்கெடுத்தோர்%sஐ செயல்படுத்த முடியவில்லை.நாடுCron மாதிரிதேதிசெயல்நிறுத்துஉரிமத்தை செயல்நிறுத்த%sஐ செயல்நிறுத்தினாலோ, அகற்றினாலோ பிற தளங்களில் பயன்படுத்தும் வாய்ப்பிருந்தும், உரிமம் தானாகவே செயலிழக்கும்.உரிமத்தை செயல்நிறுத்துவதானது, அனைத்து விலையுள்ள வசதிகளையும் நிறுத்திவிடும். ஆனாலும் பிற தளங்களில் செயல்படுத்தலாம். தொடரலாமா?செயல்நிறுத்துDebug Logதள நிர்வாகிகளுக்கான சிறப்பாளர்அனைத்து கணக்குகளையும் அழிக்கவிபரங்கள்%s ரத்துசெய்ய வேண்டாம் - அதற்கு உதவியை அணுகவும், பாதுகாப்பு மேம்படுத்தல்களைப் பெறவும் விரும்புகிறேன்.License Key இல்லையா?இந்த பிளக்இன்னுக்கு நன்கொடை தாருங்கள்உங்கள் திட்டம் கீழ்ப்படுத்தப்படுகிறதுபதிவிறக்கு%s பதிப்பை பதிவிறக்கலாம்%sன் அண்மைய பதிப்பைப் பதிவிறக்கலாம்புதிய பதிப்பை பதிவிறக்கலாம்பதிவிறக்கப்பட்டதுபுதிய %s EU GDPRன் படி %s மேற்பார்வை விதிகளால், ஆட்சேபகர தகவலுக்கு எதிரான உங்கள் நிலையை உறுதி செய்கிறீர்கள் :)எங்கள் affiliate திட்ட விதிமுறை மீறல் காரணமாக உங்கள் affiliate கணக்கை தற்காலிகமாக தடை செய்கிறோம். கேள்விகள் இருந்தால் உதவியை தொடர்பு கொள்ளவும்.மேம்படுத்தல் நடைபெறும்போதே இன்னும் %d தளங்களில் உரிமம் செயல்பாட்டில் இல்லை என்று அறிகிறோம்.மேம்படுத்தல் நடைபெறும்போதே Networkல்லுள்ள %s தளங்கள் உங்கள் கவனிப்பைக் கோருகின்றன என்றறிகிறோம்.மின்னஞ்சல்மின்னஞ்சல் முகவரிமுடிவு%sஐ எந்தெந்த தளங்களில் முன்னிலைப் படுத்துவீர்களோ அந்தத் தளங்களின் பெயர்களை உள்ளிடுங்கள்.தரம் உயர்த்துதலின் போது நீங்கள் உள்ளிட்ட மின்னஞ்சல் முகவரியைத் தந்தால், license keyஐ மீண்டும் அனுப்புகிறோம்.தவறுசெர்வரிடம் இருந்து தவறுச் செய்தி வந்திருக்கிறது.காலாவதியானது%sல் காலாவதியாகிறதுமேலதிக தளங்கள்தயாரிப்புகளை சந்தைப்படுத்தும் மேலதிக தளங்கள்.FileFilterWordpress.orgயின் வழிகாட்டு நெறிமுறைகள்படி, உங்கள் வெள்ளோட்டம் துவங்கும்முன் நாங்கள் கேட்டுக் கொள்வதெல்லாம், உங்கள் பயன்பாட்டுத் தகவலை நாங்கள் பின்தொடர எங்களை அனுமதிக்கும் தெரிவை தெரிவு செய்யுங்கள் என்பதே. இது %sஐ அனுமதித்து தகவலை %sக்கு அனுப்பச்செய்து மேம்படுத்தலுக்கு உதவும். மற்றும் உங்கள் வெள்ளோட்டத்தை உறுதிசெய்யும்.விலையில்லைவிலையில்லா வெள்ளோட்டம்விலையில்லா பதிப்புFreemius APIFreemius தவறுநீக்கி Debugப்ளக்இன் முக்கிய கோப்பை Freemius SDKவால் கண்டறிய முடியவில்லை. தயவுசெய்து பின்வரும் செய்தியுடன் sdk@freemius.comக்கு மின்னஞ்சல் அனுப்பவும்Freemius நிலைமுழுப்பெயர்Functionதானியங்கி சந்தா புதுப்பித்தலுக்கும் லாபப் பங்கீடு பெறுங்கள்.%sன் பீட்டா பதிப்பு மேம்படுத்தலைப் பெறுங்கள்.License key உள்ளதா?வணக்கம். %sன் முகவர் திட்டம் குறித்து உங்களுக்குத் தெரியுமா? %sஐ நீங்கள் விரும்பினால், நீங்களும் முகவராகி பணம் ஈட்டலாம்!%sஐ எந்தளவு விரும்புகிறீர்கள்? %d-நாள் விலையில்லா வெள்ளோட்டத்தில் %sன் விலையுள்ள வசதிகளை சோதித்துப் பாருங்கள்.பதிவேற்றுதல் மற்றும் செயல்படுத்துதல் எப்படி?எங்களை நீங்கள் எப்படி முன்னிலைப் படுத்துவீர்கள்?இதற்குமேல் பணம் செலுத்தமாட்டேன்இதை எப்படி உபயோகிப்பது என்று எனக்குப் புரியவில்லைஎன் தனிப்பட்ட தகவலை உங்களோடு பகிர விரும்பவில்லை.எனக்கு வேறு ஒரு நல்ல %s கிடைத்துவிட்டதுஎன் திட்டத்தை மேம்படுத்திய பின்னும், என் உரிமம் %s என்பதாகவே காட்டுகிறது.இனி எனக்கு %s தேவையில்லைகுறுகிய காலத்திற்கு மட்டும் %s போதும்ஐடிஇதை கிளிக் செய்தால், இந்த முடிவு தள நிர்வாகிகளுக்கு அனுப்பப்படும்.ஏன் நீங்கள் %s என்பதை எங்களுக்குத் தெரிவியுங்கள்உங்கள் %s கணக்கின் உரிமையை %sக்கு மாற்றிட விரும்பினால் உரிமை மாற்றம் பட்டனை அழுத்தவும்.அந்தத் தளங்களிலும் %sஐ உபயோகிக்க விரும்பினால், கீழே License Key உள்ளிட்டு செயல்படுத்தலை அழுத்தவும்.முக்கியமான மேம்படுத்தல் அறிவிப்பு%sல்ஒருவேளை %s இந்த தளத்திலோ அல்லது பிற தளத்திலோ உபயோகிக்கவில்லை என்றால் %sஐ ரத்து செய்ய விரும்புகிறீர்களா?விலையில்லா பதிப்பை நிறுவலாம்விலையில்லா பதிப்பின் மேம்படுத்தலை நிறுவலாம்நிறுவலாம்மேம்படுத்தலை நிறுவலாம்%s: பிளக்இன் நிறுவப்படுகிறதுmodule ID தவறானது.தவறான தள விவர சேர்ப்புஇன்வாய்ஸ்Is Activeஉங்கள் உரிமம் செயல்பாட்டுக்கு வரவில்லையென தோன்றுகிறது.உரிமத்தின் செயல்நிறுத்தம் தோல்வி அடைந்ததுபோல் தெரிகிறது.வெள்ளோட்டத்தில் நீங்கள் இல்லை என்பதால், அதை ரத்துசெய்யத் தேவையில்லை :)நீங்கள் இன்னும் %s திட்டத்திலேயே இருப்பதாகத் தெரிகிறது. நீங்கள் திட்டத்தை மாற்றிய பின்னர் இப்படி இருந்தால், அது எங்கள் தவறு. மன்னிக்கவும். உங்கள் தளத்திற்கு உரிமம் ஏதும் இல்லை என்பது போல் தெரிகிறது.சரிபார்க்கும் வகையினங்களில் ஏதோ ஒன்று தவறுபோல் தெரிகிறது. உங்கள் Public Key, Secret Key & User ID ஆகியவற்றை சரிபார்த்து மீண்டும் முயற்சிக்கவும்.நான் எதிர்பார்த்தது இதுவல்ல.பீட்டா பதிப்பு சோதனையில் சேரவும்%sன் ஆட்-ஆன் தகவலை வெளியிலுள்ள சர்வர் மூலம் எடுக்கிறோம் என்பதை அறியவும்.Keyஎன்ன வேலை செய்யவில்லை என்பதை விளக்கமாக சொன்னால், அதை நாங்கள் சரி செய்வோம்.காரணம் எதுவென்று சொன்னால் எங்களை மேம்படுத்திக் கொள்வோம்.கடைசிகடைசி மேம்படுத்தல்கடைசி உரிமம்சமீபத்திய விலையில்லா பதிப்பு நிறுவப்பட்டதுசமீபத்திய பதிப்பு நிறுவப்பட்டதுமேலும் அறியநீளம்உரிமம்உரிம ஒப்பந்தம்License KeyLicense keyLicense key காலியாக உள்ளது.வாழ்நாள்%sஐ விரும்புகிறீர்களா? எங்கள் Ambassador ஆக பணியாற்றி பணம் பெறலாம் :-)Load DB OptionLocalhostLogLoggerசெய்திவழிமுறைMigrate Options to Networkஅலைபேசி செயலிகள்ModuleModule PathModule மாதிரி%s குறித்த மேலதிக தகவல்பெயர்Network BlogNetwork பயனர்புதியதுபுதிய பதிப்பு கிடைக்கிறதுபுதிய விலையில்லா பதிப்பு (%s) நிறுவப்பட்டதுபுதிய பதிப்பு (%s) நிறுவப்பட்டதுசெய்திக்கடிதம்அடுத்துஇல்லைஐடி இல்லை%sக்கு எந்தக் கடப்பாடும் இல்லை - எப்போதும் ரத்து செய்யலாம்!%s நாட்களுக்கு எந்தக் கடப்பாடும் இல்லை - எப்போதும் ரத்து செய்யலாம்!கடன் அட்டை தேவையில்லைகாலாவதியாகாதுகாலாவதியாகாதது%sன் எந்தத் திட்டங்களிலும் வெள்ளோட்டம் இல்லை.O.Kஉங்கள் உரிமம் முடிந்ததும் நீங்கள் அனைத்து விலையில்லா வசதிகளையும் பயன்படுத்திக் கொள்ள முடியும். ஆனால் %s வசதிகளை அணுக இயலாது.மீண்டும் உரிமத்தை செயல்படுத்தினால் தவிர, உரிமம் காலாவதியானால் %s பயன்படுத்த முடியாது.தெரிவு செய்தெரிவை அகற்று"%s"ஐ சிறப்பானதாக்க தேர்வு செய்யுங்கள்மற்றவைஉரிமையாளர் மின்னஞ்சல்உரிமையாளர் IDஉரிமையாளர் பெயர்PCI compliantவிலையுள்ள ஆட்ஆன் Freemiusல் வரிசைப்படுத்தப் பட்டிருக்க வேண்டும்.PayPal கணக்கின் மின்னஞ்சல் முகவரிபணம் செலுத்தல்கள்பணம் பெறுதல் USDயில் மாதாமாதம் PayPal மூலம் பெறலாம்.திட்டம்%s திட்டம் இல்லை. வெள்ளோட்டம் துவங்க இயலாது.%s திட்டத்திற்கு வெள்ளோட்டம் கிடையாது.திட்ட IDஎங்களை இங்கு அணுகலாம்பின்வரும் செய்தியோடு எங்களைத் தொடர்பு கொள்ளுங்கள்%sஐ பதிவிறக்கலாம்.உங்கள் மின்னஞ்சலுக்கு வந்த License Keyஐ உள்ளிடுங்கள்:இணைய தள அல்லது சமூக ஊடக வருகையாளர் எண்ணிக்கை, மின்னஞ்சல் சந்தாதாரர்கள், பின்தொடர்வோர் போன்ற புள்ளிவிவரங்கள் தருக. (நாங்கள் ரகசியம் காப்போம்)மேம்படுத்தலை முடித்துவைக்க பின்வரும் வழிமுறையைப் பின்பற்றவும்பாதுகாப்பு & மேம்படுத்தல், விளக்கவுரை மற்றும் தள்ளுபடி விவரங்களை உங்களுக்கு நாங்கள் அனுப்ப விரும்பினால் எங்களைத் தொடர்பு கொள்ளுங்கள்.தயவுசெய்து கவனிக்கவும். ரத்துசெய்த பிறகு மீண்டும் புதிய சந்தா/புதுப்பித்தலுக்கு பழைய விலையை எங்களால் வசூலிக்க முடியாது. விலை ஆண்டுக்கொரு முறை உயரும். நீங்கள் இனி புதுப்பிக்க விரும்பினால் புதிய விலையை செலுத்தவேண்டும்.%sஐ எப்படி முன்னிலைப் படுத்துவீர்கள் என்ற விவரம் தரவும். (தயவுசெய்து குறிப்பிட்டுச் சொல்லவும்)உங்கள் முழுப் பெயரைத் தரவும்.ப்ளக்இன்பிளக்இன் முகப்புப்பக்கம்பிளக்இன் IDபிளக்இன் நிறுவுதல்Changelogவிளக்கம்FAQவசதிகள் & விலைநிறுவுதல்பிற குறிப்புகள்கருத்துரைகள்விலையுள்ள நிரல் இல்லாததால் பிளக்இன் "Serviceware" எனப்படும்.பிளக்இன்கள்பிளக்இன் & தீம் SyncPremium%s விலையுள்ள பதிப்பு வெற்றிகரமாக செயல்பாட்டுக்கு வந்தது.விலையுள்ள ஆட்-ஆன் பதிப்பு ஏற்கனவே நிறுவப்பட்டுள்ளது.விலையுள்ள பதிப்புவிலையுள்ள பதிப்பு ஏற்கனவே செயலில் உள்ளது.விலை விவரம்தனியுரிமைக் கொள்கைகள்தொடர்கProcess IDசெயலில்தயாரிப்புகள்திட்டத்தின் சுருக்கம்முன்னிலைப்படுத்தும் வழிமுறைகள்மாநிலப் பரப்புPublic Keyஉரிமம் வாங்குங்கள்மேலும் வாங்குகஉடனடி பின்னூட்டம்ஒதுக்கீடுசெயல்படுத்தும் மின்னஞ்சலை மீண்டும் அனுப்புகஎங்கள் %sக்கு புதிய வாடிக்கையாளர்களை பரிந்துரை செய்து, ஒவ்வொரு விற்பனைக்கும் %s லாபப் பங்கீடாகப் பெறலாம்.உரிமத்தை புதுப்பியுங்கள்உரிமத்தை புதுப்பியுங்கள்வேண்டுகோள்கள்WordPress பதிப்பை வேண்டுகிறதுமுடிவுSDKSDK Path%s சேமிக்கலாம்பட்டியலிட்ட Cronsதிரை நகல்கள்முகவரி மூலம் தேடSecret Keyபாதுகாப்பான HTTPS %s பக்கம், வெளி முகவரியிலிருந்து இயங்குகிறதுஉங்கள் சந்தா ரத்து செய்வதில் ஒரு தொழில்நுட்பக் கோளாறு. மீண்டும் முயற்சிக்கவும்.94%match உங்கள் வெள்ளோட்டம் ரத்து செய்வதில் ஒரு தொழில்நுட்பக் கோளாறு. மீண்டும் முயற்சிக்கவும்புதிய பதிப்பு உங்களுக்குக் கிடைத்துவிட்டது போல் தெரிகிறது.நாட்டைத் தேர்ந்தெடுக்கLicense key அனுப்புகSet DB OptionSimulate Network UpgradeSimulate Trial Promotionஒரு தள உரிமம்இணையதள ஐடிதளம் தெரிவு செய்யப்பட்டது.தளங்கள்கடந்திடு & %sSlugசமூக ஊடகங்கள் (Facebook, Twitter etc.)தவறுக்கு வருந்துகிறோம். எங்களுக்கு ஒரு வாய்ப்புத் தந்தால் உங்களுக்கு உதவக் காத்திருக்கிறோம்.மன்னிக்கவும்... இன்னொரு பயனாளர் இதே மின்னஞ்சல் முகவரியுடன் ஏற்கனவே பதிவு செய்திருக்கிறார்.துவக்கம்வெள்ளோட்டம் துவக்குஎன் விலையில்லா %sஐ துவக்கவும்மாநிலம்சமர்ப்பி & %sசந்தாஉதவிஉதவி மையம்Sync Data From ServerTax / VAT IDசேவை நிபந்தனைகள்எங்கள் affiliate திட்டத்திற்கு விண்ணப்பித்ததற்காக நன்றி. எதிர்பாரா விதமாக உங்கள் விண்ணப்பம் தள்ளுபடி செய்யப்பட்டது. 30 நாட்களில் மீண்டும் விண்ணப்பிக்கவும்.எங்கள் affiliate திட்டத்திற்கு விண்ணப்பித்ததற்காக நன்றி. உங்கள் விவரங்களை பரிசீலித்து, 14 நாட்களில் மேலதிக தகவலோடு தொடர்பு கொள்கிறோம்.%s மற்றும் அதன் ஆட்-ஆன் பயன்படுத்துவதற்கு நன்றி!%s பயன்படுத்துவதற்கு நன்றி!எங்கள் உருவாக்கங்களைப் பயன்படுத்துவதற்கு நன்றி!நன்றி!நன்றி %s!உரிமை மாற்றத்தை உறுதிப்படுத்தியதற்கு நன்றி. இறுதி ஒப்புதலுக்காக %sக்கு இப்போது ஒரு மின்னஞ்சல் அனுப்பப்பட்டுள்ளது.%s எனது இணையதளத்தை செயலிழக்க வைத்துவிட்டது%s வேலை செய்யவில்லை%s நான் எதிர்பார்த்தது போல் இல்லை%s நல்லதுதான். ஆனால், எனக்கு தேவைப்படும் வசதி இதில் இல்லை%s சரிவர வேலை செய்யவில்லை%s திடீரென நின்றுவிட்டதுநிறுவப்படுகிறது. சில நிமிடங்கள் காத்திருக்கவும். இந்தப் பக்கத்தை Refresh செய்யவேண்டாம்.பெயர் மாற்றமுடியாது. Slug உடனான folder, பிளக்இன் பேக்கில் இல்லை.%sன் மேம்படுத்தல் முடிந்ததுதீம்தீம் மாற்றம்தீம்கள்%sன் %s கிடைக்கிறது.%sன் புதிய பதிப்பு இப்போது கிடைக்கிறது.இந்த பிளக்இன் உங்கள் WordPress பதிப்புடன் ஒத்திசைவானது என்று குறிக்கப்படவில்லை.இந்த பிளக்இன் உங்கள் தற்போதைய WordPress பதிப்புடன் சோதிக்கப்படவில்லை.நேர முத்திரைதலைப்புமொத்தம்நகர்வெள்ளோட்டம்மாதிரிFilesystem அணுக இயலவில்லை. உங்கள் உள்ளீடு சரியா என சோதிக்கவும்.பல்தள உரிமம்அளவில்லா மேம்படுத்தல்கள்அளவில்லா லாபப் பங்கீடு.%s தளங்கள் வரைமேம்படுத்துஉரிமம் மேம்படுத்தமேம்படுத்தல், அறிவிப்புகள், வணிக செய்திகள். Spam இல்லைமேம்படுத்துபதிவிறக்கிய பதிப்பை பதிவேற்றி செயல்படுத்தலாம்W00tஉபயோகிப்பாளர் ஐடிபயனர்கள்Valueஉறுதிப்படுத்தும் மின்னஞ்சல் %sக்கு அனுப்பப்பட்டுள்ளது. பார்க்கவும். 5 நிமிடத்தில் மின்னஞ்சல் வரவில்லை என்றால் Spamல் பார்க்கவும்.உறுதிசெய்யப்பட்டதுமின்னஞ்சல் சரிபார்த்திடுங்கள்%s பதிப்பு வெளியாகிவிட்டது.விபரங்களைப் பாருங்கள்விலையுள்ள வசதிகள் என்னவென்று காணுங்கள்எச்சரிக்கைஇந்த மின்னஞ்சலின் பதிவில் எந்த உரிமமும் இல்லை. தங்கள் மின்னஞ்சல் சரியானதா?உங்கள் மின்னஞ்சல் முகவரியைக் காணவில்லை. நீங்கள் அளித்தது சரியானதா?. %s, %sக்கு சில சுவாரஸ்யங்களை உருவாக்கியிருக்கிறோம்Freemius network-level integrationஐ அறிமுகம் செய்வதில் பேருவகை அடைகிறோம்.இணையதளம், மின்னஞ்சல் மற்றும் சமூக ஊடக புள்ளி விவரங்கள் (விரும்பினால் தரலாம்)நீங்கள் என்ன எதிர்பார்த்தீர்கள்?என்ன வசதி?உங்கள் %s என்ன?என்ன விலை உங்களுக்கு வசதியாக இருக்கும்?நீங்கள் என்ன எதிர்பார்க்கிறீர்கள்?%sன் பெயர் என்ன?%sஐ எங்கு எப்படி முன்னிலைப் படுத்துவீர்கள்?WordPress.org பிளக்இன் பக்கம்மேம்படுத்தப்பட்ட பதிப்பில் தொடர விரும்புகிறீர்களா?ஆம்ஆம் - %sநீங்கள் ஏற்கனவே வெள்ளோட்டம் பார்த்துவிட்டீர்களே.%2$s திட்டத்தின் %1$s-நாள் விலையில்லா வெள்ளோட்டத்தைத் துவக்க இன்னும் 1 கிளிக் மட்டுமே.நல்லது... மகிழ்ச்சிநீங்கள் %sஐ வெள்ளோட்ட நிலையில் உபயோகித்துக் கொண்டிருக்கிறீர்கள்.இன்னும் ஒருபடி அருகில் - %sநீங்கள் %sன் வசதிகளை பயன்படுத்த முடியும். ஆனால் %s பாதுகாப்பு & மேம்படுத்தல் மற்றும் உதவியை அணுக இயலாது.விலையுள்ள பதிப்பை அணுக உங்களிடம் உரிமம் இல்லை.உங்களிடம் %sன் உரிமம் உள்ளதுஉங்கள் %s மேம்படுத்தப்பட்டது.விட்டுவிட்டீர்கள், ஆனாலும் நீங்கள் எந்த தகவலையும் பகிர வேண்டியதில்லை %sதெரிந்தெடுப்பு மட்டுமேஉங்கள் பயன்பாட்டைப் பின்தொடர எங்களுக்கு நீங்கள் அளித்த அனுமதியானது, %sஐ மேம்படுத்த உதவும்.உங்கள் பயன்பாட்டைப் பின்தொடர எங்களுக்கு நீங்கள் அளித்த அனுமதியானது, எங்கள் உருவாக்கத்தை மேம்படுத்த உதவும்.உங்கள் %s ஆட்-ஆன் திட்டம் மேம்படுத்தப்பட்டது.உங்கள் %s விலையில்லா வெள்ளோட்டம் ரத்து செய்யப்பட்டது.%s திட்டத்தில் உங்கள் கணக்கின் செயல்பாடு துவங்கியது.%sக்கான உங்கள் Affiliate விண்ணப்பம் ஏற்கப்பட்டது. உள்நுழைந்து %sல் உங்கள் affiliate areaவை அணுகவும்.உங்கள் affiliate கணக்கு தற்காலிகமாக இடைநிறுத்தப்பட்டுள்ளது.உங்கள் மின்னஞ்சல் சரிபார்க்கப்பட்டது - நன்றி!உங்கள் விலையில்லா வெள்ளோட்டம் முடிந்தது. %3$sஐ தொடர்ந்து பயன்படுத்த %1$s %2$sஇவற்றை மேம்படுத்துங்கள்.உங்கள் வெள்ளோட்டம் முடிந்தது. ஆனாலும் பிற விலையில்லா சேவைகளைத் தொடரலாம்.உங்கள் உரிமம் ரத்தானது. இதில் தவறேதும் உணர்ந்தால் உடனடியாக எங்கள் உதவியை அணுகவும்.உங்கள் உரிமம் முடிந்தது. %3$sஐ தொடர்ந்து பயன்படுத்த %1$s %2$s இவற்றை மேம்படுத்துங்கள்.உங்கள் உரிமம் முடிந்தது. எனினும் நீங்கள் %sன் வசதிகளைத் தொடரலாம். எனினும், தொடர் மேம்படுத்தல் மற்றும் உதவிக்கு உங்கள் உரிமத்தைப் புதுப்பிக்கவும்.உங்கள் உரிமம் முடிந்தது. ஆனாலும் %sன் விலையில்லாப் பதிப்பை என்றும் தொடரலாம். உங்கள் உரிமம் செயல்படுத்தப்பட்டது.உங்கள் உரிமம் செயல்நிறுத்தப்பட்டது, %s திட்டத்திற்கு மாற்றப்பட்டுள்ளீர்கள்.உங்கள் பெயர் ஏற்றப்பட்டது.உங்கள் தேர்ந்தெடுத்த திட்டம் துவங்கியது.உங்கள் தேர்ந்தெடுத்த திட்டம் %sக்கு மாறியது.உங்கள் தேர்ந்தெடுத்த திட்டம் மேம்படுத்தப்பட்டது.உங்கள் சந்தா ரத்து செய்யப்பட்டது. உங்கள் %s திட்டம் %s அன்று காலாவதியாகிறது.உங்கள் வெள்ளோட்டம் துவங்கியதுZIP / தபால் குறியீடுRight onசெயல்பாட்டில்%s இல்லாமல் %s இயங்காதுப்ளக்இன் இல்லாமல் %s இயங்காதுHeads upஅனுமதி%s இருக்கிறதுசெயல்படுத்துகிறதுவருடம்APIபோய்த் தொலைதவறை சோதிக்கிறதுவாழ்த்துக்கள்தடுக்கப்பட்டதுஇணைக்கப்பட்டதுஅண்மைய பதிப்பை பதிவிறக்கஅண்மைய விலையில்லா பதிப்பை பதிவிறக்கமாதாமாதம்காலாவதிவழிமின்னஞ்சல் அனுப்பப்படுகிறதுமாதம்ஆண்டுஆண்டுக்காண்டுஒருமுறைதிட்டம்No SecretSDK பதிப்புகள்உரிமம்SyncSync Licenseஉருவாக்கியவர்OffOn%s அடிப்படையிலானதுவிலையில்லா வெள்ளோட்டம் தொடங்கட்டும்... டும்போய்த் தொலைபோய்த் தொலைசெயல்நிறுத்தப்படுகிறதுdelegateபாதுகாப்பு & மேம்படுத்தல், விளக்கவுரை மற்றும் தள்ளுபடி விவரங்களை எனக்கு அனுப்பவும். %s செய்க, %s தேவையில்லை%s திட்டம்%s பில் எழுதப்பட்டதுசிறப்புHeyஅரே ஓ சம்போ!வணக்கம் %s,நிறுவப்பட்டதுYee-hawஉரிமம்தளங்கள்msபுதிய பீட்டா பதிப்புபுதிய பதிப்புஉறுதிப்படுத்தப்படவில்லைவிலைவிலை விவரம்பதிப்புsecபாதுகாப்பு & மேம்படுத்தல், விளக்கவுரை மற்றும் தள்ளுபடி விவரங்களை எனக்கு அனுப்பவும்.கடந்திடுHmmவெள்ளோட்டம் துவங்கலாம்சந்தாswitching%sன் அண்மைய பதிப்பு இதோவெள்ளோட்டம்வெள்ளோட்டம்அழிதரமிறக்குதிருத்துமறைத்திடுதெரிவு செய்தெரிவை அகற்றுவாங்குககாட்டுகடந்திடுமேம்படுத்துமேம்படுத்து%s முன்புPK!X  $freemius/languages/freemius-da_DK.monu[$,*X8Y8^8Ad88nI929Z9hF:d:S;%h; ; ;;;;6f<<_R===#= >%'> M> Z> d>o>v>~>>>@>H>"?O5?M?j?>@@@@A%A5AAA AAABB&,B-SBB BBBBBB5BC$C 4C >C'JCrC C CCoC#D*DGDTEfEE/FBF"^FF(F2F!F>Ga[G+G0GH,HCHRHZHnHsH{HHHHH H HH HEHy)IIIII nJyJJ J J JJJYJMK\K mK yKKKKK K(K1K%"L:HLL LLLL L LLL L7L7M 5]mt]g]pJ^^^y^U_n_ ___ __%_ `>`F`c`z` `&``1_a.aOabAbbyb2lcccac 7dDd[dB_d,dd d dd e %e0e7e?e Qe \ehe xeee4ee eeeefff'f7f Rf^f ef qf}ff,f f ffggg g!gg hhhh%1hWh%]h+hh h h/himi~ij jujjdklm*m 0muDuW]u uuuuuv/v6v:vCvKvQv avmv v5vsvl4w&www w wyy4yHyPyly ry|y y=y&yLyf?zz z zzzz zz z {{ {+{ A{N{_{||%<}/b}})} } }\}O~~~C/smGxd.- ǁԁہ'"MςG,e S_TΆԆن߆E*=Ofu|** dz  ʈ' BObijWԉn,"@cB6*=a Œ-ӌ&5/\#*ʍ /4QdUD $QMvď/֏o&>Ր & <4DqZTR.:.i-;ؔ9ZN3<ݕbP}UΖ_$K(kG#ܘ%)&$PuU)Vǚ;ܚ6>Oϛ$ $@\&z*7̜93Rɝݝ*1%Ws#Ǟٞ ,AUZ _lNuğ,=NSY1t Ҡ 5 > L9XC֡ۡ 7= P\ k u  Ƣ ӢAnZh\dŨ!*0L} 6^_Jʫ%@ Q^m u @Hެ'O=MjۭF  ǯ߯,/2b | ʰ ְ5! 1 ;%Gm o(/GβTk/%?'e(1ִ$>-al+ε50C[sx  Ͷ׶ Ey;#ٷ ĸ ۸ Y w  ǹ)1/E u  ǺԺ 73 8BxS̻ an~%9IR5bXt ˾Ծ" =H[fl sY_#n/8 GR1bO &j55 320Ac~:<ow#$9<^I8c4!&gML7>mgp"X{y* BOg ~% 6T&]15L\>_y28ka 'W.6" $.6 = J Ta q~4  !- HS Y cm,  %  %,R.[4 1+m/~$u,~ !(JPg oy) :@,])>6 W; \[ [|   NV#^',( 1; DN^rz W -:N]r 5sDl0% Vas    !=-'kPhMSey    w%84m) d)L V#em'xd"s $UK,?l S;0 E "<Sgo2~) d { 4 O\qtz_nO+c}B >P=,2G^/x##  3/FQvUD(cO /o8>"<>D{YST.D5s-;'%ZM38bTx_e-X*9"<[U< Z k r                 /  O Z c  g u x               % + ? D  I U Q^                 + < A H Q  Y  c q 9u L     ( 0 J  h  v             yl~|'4mf*f-/i#$M}#:  Y*D5ui^]Lyptw;xFk|K3 !Pt'o.Yg_8,!@vhBf,0``j %7?6Cz9kzJ[,h42eSWBXr62JjL| "c<(:{@ 6Zlpo\/^+-nAl5%w"T0=\O7EnP)<Jvs&q?uNyA}{FE;#O8 =_{FDSLDM3aqRGv 5Q[rISZg>%$]U~9T u2cGKn;~go$UO0X)a +mQ_s1ZxGp:^`QmNY &9W!R& [xjqdb4>3?B]\t'eAbH/dR=>w)a eHX}sKNC+V*.@c(r 1 "E <ITd IU1H-VV8WziP.kb( CMh7HmmW00t %s to access version %s security & feature updates, and support. 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. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s"The ", e.g.: "The plugin"The %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s is the new owner of the account.%s minimum payout amount.%s opt-in was successfully completed.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.Click here to learn more about updating PHP.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.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.APIASCII arrow left icon←ASCII arrow right icon➤AccountAccount DetailsAccount is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again.ActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.An unknown error has occurred.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre both %s and %s your email addresses?Are you sure you want to delete all Freemius data?Are you sure you want to proceed?Are you sure you would like to proceed with the disconnection?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBetaBillingBilling & InvoicesBlockingBlog IDBodyBundleBundle PlanBusiness nameBuy a license nowBuy licenseBy changing the user, you agree to transfer the account ownership to:By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s.Can't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanChange UserCheckoutCityClear API CacheClear Updates TransientsClick hereClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCommunicationCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeCurrent %s & SDK versions, and if active or uninstalledDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Delegate to Site AdminsDelete All AccountsDetailsDiagnostic InfoDiagnostic data will no longer be sent from %s to %s.Disabling white-label modeDisconnecting the website will permanently remove %s from your User Dashboard's account.Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload Paid VersionDownload the latest %s versionDownload the latest versionDownloadedDue 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 violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Duplicate WebsiteDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEmail address updateEnabling white-label modeEndEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used during the purchase and we will resend you the license key.Enter the email address you've used for the upgrade below and we will resend you the license key.Enter the new email addressErrorError received from the server:ExpiredExpires in %sExtensionsExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.For delivery of security & feature updates, and license management, %s needs toFreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFreemius is our licensing and software updates engineFull nameFunctionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.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.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!Homepage URL & title, WP & PHP versions, and site languageHow do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I Agree - Change UserI can't pay for it anymoreI couldn't understand how to make it workI don't know what is cURL or how to install it, help me!I don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you skip this, that's okay! %1$s will still work just fine.If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there.If you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid clone resolution action.Invalid module ID.Invalid new user ID or email address.Invalid site details collection.InvoiceIs %2$s a duplicate of %4$s?Is %2$s a new website?Is %2$s the new home of %4$s?Is ActiveIs active, deactivated, or uninstalledIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.It looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueIt's not what I was looking forJoin the Beta programJust letting you know that the add-ons information of %s is being pulled from an external server.Keep SharingKeep automatic updatesKeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense IDLicense KeyLicense issues?License keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerLong-Term DuplicateMessageMethodMigrateMigrate LicenseMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNames, slugs, versions, and if active or notNetwork BlogNetwork UserNever miss an important updateNever miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.NewNew Version AvailableNew WebsiteNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo - just deactivateNo - only move this site's data to %sNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to get email notifications for security & feature updates, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProcessingProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicensePurchase MoreQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires PHP VersionRequires WordPress VersionReset Deactivation SnoozingResultSDKSDK PathSave %sSavedScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSharing 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.Simulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSnooze & %sSo you can reuse the license when the %s is no longer active.Social media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart DebugStart TrialStart my free %sStateStay ConnectedStop DebugSubmitSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank 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.Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you for updating to %1$s v%2$s!Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.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:This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.This plugin requires a newer version of PHP.This will allow %s toTimestampTitleTo avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.To ensure compatibility and avoid conflicts with your installed plugins and themes.To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.TotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUser DashboardUser IDUser keyUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View %s StateView Basic %s InfoView Basic Profile InfoView Basic Website InfoView Diagnostic InfoView License EssentialsView Plugins & Themes ListView detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.We made a few tweaks to the %s, %sWe'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.We're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)Welcome to %s! To get started, please enter your license key:What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress & PHP versions, site language & titleWordPress.org Plugin PageWould you like to merge %s into %s?Would you like to proceed with the update?YesYes - %sYes - both addresses are mineYes - do your thingYes - move all my data and assets from %s to %sYes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.You already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have purchased a %s license.You have successfully updated your %s.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesYou might have missed it, but you don't have to share any data and can just %s the opt-in.You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your WordPress user's: first & last name, and email addressYour account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully activated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$sYour subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactivate a license hereactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismisscomplete the opt-indatadaysdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,hourhoursinstalled add-onInstalledinterjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew Beta versionnew versionnot verifiednounPricenounPricingoptionalproduct versionVersionproductsrevert it nowsecondssecseems like the key you entered doesn't match our records.send me security & feature updates, educational content and offers.skipstart the trialsubscriptionswitchingthe above-mentioned sitesthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Joachim Jensen, 2019-2020,2022 Language-Team: Danish (Denmark) (http://www.transifex.com/freemius/wordpress-sdk/language/da_DK/) Language: da_DK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js HmmW00t %s to access version %s security & feature updates, and support. 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. The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$sThe %s's%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s.%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s.%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s.Færdiggør aktivering af "%s" nuBetalingen for tilføjelsen %s blev gennemført.%s installeringer1%s licenser%s siden%s og tilføjelser%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first).%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is my client's email address%s is my email address%s er den nye ejer af kontoen.%s minimum payout amount.%s opt-in was successfully completed.%s eller højere%s vurdering%s vurderinger1%s sek%s stjerne%s stjerner%s gang%s gange%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's betalte features%sClick here%s to choose the sites where you'd like to activate the license on.Click here to learn more about updating PHP.A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.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.API←➤KontoKontodetaljerAccount is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again.HandlingerAktiverAktiver %sAktiver %s planAktiver funktioner i %sAktiver gratis versionAktiver licensAkiver licens på alle afventende websteder.Aktiver licens på alle websteder i netværket.Aktiver denne tilføjelseAktiveretTilføjelser til %sTilføjelser til modul %sTilføj andet domæneTilføjelseTilføjelserAdd-on must be deployed to WordPress.org or Freemius.AdresseAdresselinje %dAffiliateAffiliationEfter dine gratis %s er prisen kun %sAccepter & aktiver licensAlle forespørgslerAlle typerTillad & FortsætAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.BeløbAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.An unknown error has occurred while trying to set the user's beta mode.An unknown error has occurred while trying to toggle the license's white-label mode.Der skete en ukendt fejl.An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned.Anonym feedbackAnvend på alle afventende websteder.Anvend på alle websteder i netværket.Ansøg om at blive en affiliateAre both %s and %s your email addresses?Er du sikker på, du vil slette al Freemius data?Er du sikker på, du vil fortsætte?Are you sure you would like to proceed with the disconnection?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Associate with the license owner's account.Auto-installation fungerer kun for tilmeldte brugere.Auto-fornyer om %sAutomatisk installeringGennemsnitlig vurderingSejtBliv en affiliateBetaBetalingFaktureringBlokererBlog-IDBodyBundleBundle PlanFirmanavnKøb en licens nuKøb licensBy changing the user, you agree to transfer the account ownership to:By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s.Kan du ikke finde din licensnøgle?AnnullerAnnuller %s og fortsætCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Annuller %s?Annuller installeringAnnuller abonnementAnnuller prøveperiodeAnnulleretAnnullerer %sAnnullerer %s...Annullerer abonnementetCancelling the trial will immediately block access to all premium features. Are you sure?Skift licensSkift ejerskabSkift planSkift brugerUdtjekningByRyd API-cacheClear Updates TransientsKlik herKlik her for at benytte pluginnet anonymtClick to see reviews that provided a rating of %sKlik for at vise skærmbillede %d i fuld skærmProdukterKodeCommunicationKompatibel op tilKontaktKontakt supportKontakt osBidragsydereKunne ikke aktivere %s.LandCron TypeCurrent %s & SDK versions, and if active or uninstalledDatoDeaktiverDeaktiver licensDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeaktiveringFejlfindingslogDebug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the "Stop Debug" link.Uddeleger til webstedsadministratorerSlet alle kontiDetaljerDiagnostic InfoDiagnostic data will no longer be sent from %s to %s.Disabling white-label modeDisconnecting the website will permanently remove %s from your User Dashboard's account.Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Har du ikke en licensnøgle?Donér til dette pluginNedgraderer din planDownloadDownload 1%s versionHent betalt versionDownload den seneste version af %sDownload den seneste versionDownloadetDue 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 violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.Kopier webstedDuring the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.E-mailE-mailadresseEmail address updateEnabling white-label modeSlutEnter email addressEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used during the purchase and we will resend you the license key.Indtast e-mailadressen, som du benyttede ved opgraderingen, nedenfor og vi vil gensende licensnøglen til dig.Enter the new email addressFejlFejl modtager fra serveren:UdløbetUdløber om %sExtensionsEkstra domænerAndre domæner du vil markedsføre produktet fra.FilFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.For delivery of security & feature updates, and license management, %s needs toGratisGratis prøveperiodeGratis versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius tilstandFreemius is our licensing and software updates engineFulde navnFunktionGet commission for automated subscription renewals.Get updates for bleeding edge Beta versions of %s.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.Har du en licensnøgle?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!Homepage URL & title, WP & PHP versions, and site languageHvad synes du om %s indtil videre? Test alle vores premium funktioner i %s med en %d-dags gratis prøveperiode.Upload og aktivering, hvordan?Hvordan vil du promovere os?I Agree - Change UserJeg kan ikke længere betale for detJeg forstod ikke, hvordan jeg skulle få det til at fungere.Jeg ved ikke hvad cURL er, eller hvordan jeg installerer det. Hjælp mig!Jeg har ikke lyst til at dele mine informationer med jerJeg fandt et bedre %sJeg har opgraderet min konto, men når jeg forsøger at synkronisere licensen, forbliver planen %s.Jeg har ikke længere brug for %sJeg behøvede kun %s i en kort periodeIDIf this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s.If you click it, this decision will be delegated to the sites administrators.Hvis du har tid, så lad os venligst vide hvorfor du %sIf you skip this, that's okay! %1$s will still work just fine.If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there.If you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Vigtig meddelelse til opgradering:Om %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Installer gratis version nuInstaller opdatering til gratis version nuInstaller nuInstaller opdatering nuInstallerer plugin: %sInvalid clone resolution action.Ugyldigt modul-ID.Invalid new user ID or email address.Invalid site details collection.FakturaIs %2$s a duplicate of %4$s?Er %2$s et nyt websted?Is %2$s the new home of %4$s?Er aktivIs active, deactivated, or uninstalledIs this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.Det ser ud til, at licensen ikke kunne aktiveres.Det ser ud til, at licens-deaktiveringen mislykkedes.Det lader ikke til du er i en prøveperiode længere, så der er ikke noget at annullere :-)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.Det ser ud til, at dit websted endnu ikke har en aktiv licens.It requires license activation.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's a temporary %s - I'm troubleshooting an issueDet er ikke, hvad jeg søgteDeltag i beta-programmetJust letting you know that the add-ons information of %s is being pulled from an external server.Keep SharingKeep automatic updatesNøgleVær venlig at dele hvad der ikke virkede så vi kan rette det for kommende brugere....Fortæl os venligst årsagen, så vi kan forbedre det.SidsteSenest opdateretSeneste licenseSeneste gratis version installeretSeneste version installeretLæs mereLængdeLicensLicensaftaleLicens-IDLicensnøgleLicense issues?LicensnøgleLicensnøglen er tom.LivstidLike the %s? Become our ambassador and earn cash ;-)Hent DB-indstillingLocalhostLogLoggerLong-Term DuplicateBeskedMetodeFlytFlyt licensMigrate Options to NetworkMobil-appsModulModul-stiModultypeMere information om %sNavnNames, slugs, versions, and if active or notNetværksblogNetværksbrugerNever miss an important updateNever miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features.NyNy version tilgængeligNyt webstedNyere gratis version (%s) installeretNyere version (%s) installeretNyhedsbrevNæsteNejNej - bare deaktiverNo - only move this site's data to %sIntet IDIngen bindinger i %s - annuller når som helstIngen bindinger i %s dage - annuller når som helst!Betalingskort ikke påkrævetUdløber ikkeUdløber ikkeIngen af %s's planer understøtter prøveperiode.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.TilmeldFrameldOpt in to get email notifications for security & feature updates, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info.Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to.Accepter for at gøre "%s" bedre!AndetE-mailadresse for ejerEjer-IDEjer-navnPCI-kompatibelPaid add-on must be deployed to Freemius.E-mailadresse til PayPal-kontoBetalingerUdbetalinger er i USD og behandles hver måned via PayPal.PlanPlan %s eksisterer ikke og kan derfor ikke starte prøveperiode.Plan %s understøtter ikke en prøveperiode.Plan-IDKontakt os herKontakt os venligst med følgende besked:Download venligst %s.Indtast licensnøglen, du modtog i e-mailen lige efter købet:Please enter the license key to enable the debug mode:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Følg venligst disse trin for at færdiggøre opgraderingenLad os vide, om vi har lov til at kontakte dig med sikkerheds- og feature-opdateringer, informativt indhold og lejlighedsvise tilbud:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Indtast venligst dit fulde navn.PluginPlugin-webstedPlugin-IDPlugin-installeringÆndringslogBeskrivelseFAQFunktioner og priserInstalleringAndre noterAnmeldelserPlugin is a "Serviceware" which means it does not have a premium code version.PluginsSynkronisering af plugins og temaerPremiumPremium-versionen af %s blev aktiveret.Premium tilføjelse er allerede installeret.Premium versionPremium version allerede aktiv.PriserPrivatlivspolitikFortsætProces-IDArbejderProdukterProgramoversigtPromoveringsmetoderProvinsOffentlig nøgleKøb licensKøb flereHurtig feedbackKvoteGensend e-mail om aktiveringRefer new customers to our %s and earn %s commission on each successful sale you refer!Forny licensForny din licens nuForespørgslerRequires PHP VersionKræver WordPress-versionReset Deactivation SnoozingResultatSDKSDK-stiSpar %sGemtPlanlagte cron jobsSkærmbillederSøg efter adressePrivat nøgleSecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Det ser ud til, at du har den seneste udgivelse.Vælg landSend licensnøgleSæt DB-indstillingSharing 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.Simuler netværksopgraderingSimulate Trial PromotionEnkelt site licensWebsteds-IDWebsted er tilmeldt.WebstederSpring over & %sKortnavnUdsæt & %sSo you can reuse the license when the %s is no longer active.Sociale medier (Facebook, Twitter osv.)Vi beklager ulejligheden, og vi er her for at hjælpe, hvis du giver os chancen.Beklager, vi kunne ikke opdatere e-mailen. Der er allerede registreret en anden bruger med samme e-mail.StartStart fejlfindingStart prøveperiodeStart mine gratis %sStatStay ConnectedStop fejlfindingSendSend & %sAbonnementSupportSupportforumSynkroniser data fra serverMoms / VAT IDServicevilkårTak fordi du giver os en chance for at fixe det! En besked er lige blevet sendt til vores tekniske personale. Vi vil vende tilbage, så snart der er nyt om %s. Vi sætter pris på din tålmodighed.Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you for updating to %1$s v%2$s!Mange tak for, at du benytter %s og tilhørende add-ons!Tak fordi du benytter %s!Mange tak for at benytte vores produkter!Mange tak!Tak %s!Tak fordi du bekræftede skift af ejerskab. En e-mail er blevet sendt til %s for sidste godkendelse.The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license.%s ødelagde min webside%s virkede ikke%s virkede ikke som forventet%s er godt, men jeg har brug for en specifik feature, som ikke understøttes%s virker ikke%s stoppede pludseligt med at virkeThe following products'The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$sThe products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$sThe remote plugin package does not contain a folder with the desired slug and renaming did not work.Opgraderingen af %s blev fuldendt.TemaTemaskiftTemaerThere is a %s of %s available.En ny version af %s er tilgængelig.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:Dette plugin er ikke markeret som kompatibel med din nuværende version af WordPress.Dette plugin er ikke blevet testet med din nuværende version af WordPress.This plugin requires a newer version of PHP.This will allow %s toTidsstempelTitelTo avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to.To ensure compatibility and avoid conflicts with your installed plugins and themes.To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your "My Profile" section of your User Dashboard:To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize.To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to.TotalByPrøveperiodeTypeUnable to connect to the filesystem. Please confirm your credentials.Ubegrænsede licenserUbegrænsede opdateringerUbegrænset provision.Op til %s webstederOpdaterOpdater licensOpdateringer, annonceringer, marketing, ingen spamOpgraderUpload og aktiver den downloadede versionBrugerpanelBruger-IDBrugernøgleBrugereVærdiVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerificeretVerificer e-mailVersion %s er blevet udgivet.View %s StateView Basic %s InfoView Basic Profile InfoView Basic Website InfoView Diagnostic InfoView License EssentialsView Plugins & Themes ListVis detaljerVis betalte featuresAdvarselVi kan ikke finde nogen aktive licenser knyttet til den e-mailadresse, er du sikker på, det er den rigtige adresse?Vi kunne ikke finde din e-mailadresse i systemet, er du sikker på, det er den rigtige adresse?We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes.We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to.Vi har foretaget nogle rettelser til %s, %sVi vil kontakte din udbyder og løse problemet. Når vi har opdatinger i sagen, vil vi følge op med en email til dig på %s.We're excited to introduce the Freemius network-level integration.Websted, e-mail, og statistikker for sociale medier (valgfrit)Welcome to %s! To get started, please enter your license key:Hvad forventede du?Hvilken feature?Angiv venligst %s?Hvilken pris ville du foretrække at betale?Hvad ledte du efter?Hvad er navnet på %s?Hvor vil du promovere %s?WordPress & PHP versions, site language & titleWordPress.org Plugin-sideWould you like to merge %s into %s?Vil du fortsætte med opdateringen?JaJa - %sYes - both addresses are mineJa - fortsæt bareYes - move all my data and assets from %s to %sYes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s.Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development.Yes, %2$s is a new and different website that is separate from %4$s.Du har allerede brugt din prøveperiode.Du er 1 klik fra at begynde din %1$s dages gratis prøveperiode af planen %2$s.Det var det!Du benytter allerede %s under en prøveperiode.Du mangler kun ét skridt - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.Du har ikke en gyldig licens til at benytte premium-versionen.Du har en %s licens.Du har købt en licens til %s.Opdatering af %s blev gennemført.You marked this website, %s, as a temporary duplicate of %s.You marked this website, %s, as a temporary duplicate of these sitesDu har måske overset det, men du behøver ikke at dele data og kan blot %s tilmeldingen.You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s.Du er allerede tilmeldt vores brugssporing, hvilket hjælper os med at forbedre %s.Du er allerede tilmeldt vores brugssporing, hvilket hjælper os med at forbedre dem.Your %s Add-on plan was successfully upgraded.Din gratis prøveperiode for %s er blevet annulleret.Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.Your %s license was successfully deactivated.Your WordPress user's: first & last name, and email addressDin konto blev aktiveret med planen %s.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Din e-mailadresse er blevet verificeret - du er FOR SEJ!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Din gratis prøveperiode er udløbet. Du kan stadig benytte alle de gratis features.Din licens er blevet annulleret. Hvis du mener, dette er en fejl, så kontakt venligst support.Din licens er udløbet. %1$sOpgrader nu%2$s for at fortsætte med at benytte %3$s uden forstyrrelser.Din licens er udløbet. Du kan stadig benytte alle funktionerne i %s, men du bliver nødt til at fornye din licens for at få opdateringer og support.Din licens er udløbet. Du kan stadig fortsætte med at benytte den gratis udgave af %s.Din licens er blevet aktiveret.Din licens blev deaktiveret, du er tilbage på planen %s.Dit navn er blevet opdateret.Din plan er blevet aktiveret.Din plan er blevet ændret til %s.Din plan er blevet opgraderet.Your server is blocking the access to Freemius' API, which is crucial for %1$s synchronization. Please contact your host to whitelist %2$sYour subscription was successfully cancelled. Your %s plan license will expire in %s.Din prøveperiode er begyndt.ZIP / PostnummerSådanaktiver en licens herAktiv%s virker ikke uden %s.%s virker ikke uden pluginnet.Se hertillad%s tilbageAktivererårAPIFjernFejlfindingTillykkeBlokeretForbundetDownload senesteDownload seneste gratis versionMånedligtUdløberStiSender e-mailmdÅrligtÅrligtEngangsbeløbPlanIngen privat nøgleSDK-versionerLicensSynkroniserSynkroniser licensForfatterFraTilbaseret på %sStart gratis prøveperiodeFjernFjerncomplete the opt-indatadagedeaktivererdelegérsend %sIKKE%s sikkerheds- og feature-opdateringer, informativt indhold og tilbud.%s PlanFaktureret %sBedsteHeyUpsHey %s,timetimerInstalleretYee-hawlicensWebstedermsny beta-versionny versionikke verificeretPrisPriservalgfritVersionprodukterrevert it nowsekseems like the key you entered doesn't match our records.send mig sikkerheds- og feature-opdateringer, informativt indhold og tilbud.spring overstart prøveperiodenabonnementskifterthe above-mentioned sitesden seneste version af %s herprøveperiodePrøveperiodeSletNedgraderRedigerSkjulTilmeldFrameldKøbVisSpring overOpdaterOpgrader%s sidenPK!o+׺׺$freemius/languages/freemius-nl_NL.monu[T +A+S+%, =, I,U,\,6o,,_[-#-- - . ..".*.3.;.@D.H..O.1/5/T/t/// /////&/-0C0 X0b0q00005000 0 1' 141 M1 Z1d1ou11122"2222!-3aO30333 44"464>4G4O4 T4b4 t44444 K5V5j5 ~5 5 555Y5*696 J6V6_6d6t6(616%6:7I7N7_7g7 w7 777 77 77x7S8 8 889+9t3999999 :*: F:Q:[:fE;; ;;Y;a<<<< < <;<= ==> > !> .>;>jJ>> >>3> ?~?U??@)@)D@-n@@S@A'ADAMGA7AgAp5BBByB@CYC yCCCC CC C1C.&DOUDDA%EygEEaFcFBgF,FF F FFG -G8G?GGG YG eGqGG4GG GGGGGG HH &H 2H>HXH ]H jHwH{H!HH HHHH%H+I;I SI aI/nIImI~JJJJJ JJ J J)JK5K4>KsK5xK(KKK-K$LU8LL1WM~MN[%OOOO OO(O*O"$P1GP+yP*P&PNPFQNQdQ.lQ)QQQQQ R RR(R8RJR SR^RoR~RRWR RSS#S>SESISRSZS jSvS S5SsSl=T&TTT TTU1UEUMUiU oUyU&~ULUfUYV _VkV|V V VV VV VVV{W/XBX)bX X X\XXY'YCFYYYYdFZ-ZZ ZZZ'[M:[G[ [[[[[[E[<\O\a\x\\\*\\*\^\Z]b]h]dn]] ]] ^^"^i*^W^"^B_6R__ __-__ `&`F```d`$m`M``/`"aoBa>aa&bZ/bTbRb.2c.ac9cZc3%d<YdbdPdUJe_efKf(fGg#Xg)|g$gUg)!hKh]hzh;h6h>iAiGibii$iiiij&-j*Tj7jjjj3k9kNkdk|kk*k1k l&l#:l^lzll llll lmN mYmxmmmmm1mnn1n An Mn Zn enrn nCnnQn1o Ao NoXosoyo oo o o o o o o o o p ppFs't$Atft vt tt6ttqu):vdv vvvvv vv vGvM#wqwJwwwwwwx xx)x?xVx6hx/xx xxyy 4y @y:Nyy y y y"yy y y znzzz9{,Q{%~{.{7{# |{/|?||}}2};} O} [}g}o} t}} }&}}}} ~~~~ ~ ~u6  ,5K? ˀЀ߀ , 1; AL` ǂނhƃ$ك! +a^K jÅ~.$׆78@!Gip {*:I>Qf0'͊3?)iY~؋1(Z+jh|Z׍ z|" Ȏݎ' 5 =;G>`#A ]"l,%=#c Ɠ֓ B = KUY`hq  Д Ք*#? cox|/74$Y^fmu ŗ3ї E+qBv&#8 EY[8՚m_# (3G Wdx N!)+1.] Ξ מ  />TZmz  6@D LWf y8Рb3"1I[t âѢ&֢SxQʣУ  %2 R]p'Hӥ*8G "= OZp˧ܧ\( ' /+PP|Aͩ !( /<XAª ٪ -.(6_ d r}}  :I cdp^լ64Fk8 'E_z(bI!X(z5;q#W_ae-DZ14'\\,9k `j`XRS%E̵ '3![V}%Զ  , MZc r| Ϸ   ! +5> C O[d iw~ ĸ иUڸ08IOSX`fou xL  " -; [ h u  ĺ ̺k2;`|4|?*r@tZ"G ^<7A:l]Qcq iS4\bKB9Lm'( asI=Ccn$!RN 21}8#wU:B]xC{J<qiPISHjWW XT73\r[sEg 8?6vfz}+o,%ldFaM.;JpAUQ0$w,>MRjfv VPK(&`3#yYZ0ykbE6LDO~5&_^~e>@9n {F)=Dh5-V1+z)pNOH" T[Xx./_e%'t*!Y/-duog G uhm %s to access version %s security & feature updates, and support.%s - plugin name. As complete "PluginX" activation nowComplete "%s" Activation Now%s Add-on was successfully purchased.%s Installs%s Licenses%s ago%s and its add-ons%s commission when a customer purchases a new license.%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license.%s is a premium only add-on. You have to purchase a license first before activating the plugin.%s is the new owner of the account.%s minimum payout amount.%s or higher%s rating%s ratings%s sec%s star%s stars%s time%s times%s to access version %s security & feature updates, and support.%s tracking cookie after the first visit to maximize earnings potential.%s's paid features%sClick here%s to choose the sites where you'd like to activate the license on.APIASCII arrow left icon←ASCII arrow right icon➤Account DetailsActionsActivateActivate %sActivate %s PlanActivate %s featuresActivate Free VersionActivate LicenseActivate license on all pending sites.Activate license on all sites in the network.Activate this add-onActivatedAdd Ons for %sAdd Ons of module %sAdd another domainAdd-OnAdd-OnsAdd-on must be deployed to WordPress.org or Freemius.AddressAddress Line %dAffiliateAffiliationAfter your free %s, pay as little as %sAgree & Activate LicenseAll RequestsAll TypesAllow & ContinueAlternatively, you can skip it for now and activate the license later, in your %s's network-level Account page.AmountAn automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now.Anonymous feedbackApply on all pending sites.Apply on all sites in the network.Apply to become an affiliateAre you sure you want to delete all Freemius data?Are you sure you want to proceed?As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days.Auto installation only works for opted-in users.Auto renews in %sAutomatic InstallationAverage RatingAwesomeBecome an affiliateBillingBlockingBlog IDBodyBusiness nameBuy a license nowBuy licenseCan't find your license key?CancelCancel %s & ProceedCancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site.Cancel %s?Cancel InstallationCancel SubscriptionCancel TrialCancelledCancelling %sCancelling %s...Cancelling the subscriptionCancelling the trial will immediately block access to all premium features. Are you sure?Change LicenseChange OwnershipChange PlanCheckoutCityClear API CacheClear Updates TransientsClick here to use the plugin anonymouslyClick to see reviews that provided a rating of %sClick to view full-size screenshot %dClone resolution admin notice products list labelProductsCodeCompatible up toContactContact SupportContact UsContributorsCouldn't activate %s.CountryCron TypeDateDeactivateDeactivate LicenseDeactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site.Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?DeactivationDebug LogDelegate to Site AdminsDelete All AccountsDetailsDon't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support.Don't have a license key?Donate to this pluginDowngrading your planDownloadDownload %s VersionDownload the latest %s versionDownload the latest versionDownloadedDue to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support.During the update process we detected %d site(s) that are still pending license activation.During the update process we detected %s site(s) in the network that are still pending your attention.EmailEmail addressEndEnter the domain of your website or other websites from where you plan to promote the %s.Enter the email address you've used for the upgrade below and we will resend you the license key.ErrorError received from the server:ExpiredExpires in %sExtra DomainsExtra domains where you will be marketing the product from.FileFilterFor compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial.FreeFree TrialFree versionFreemius APIFreemius DebugFreemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error.Freemius StateFull nameFunctionGet commission for automated subscription renewals.Have a license key?Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!How do you like %s so far? Test all our %s premium features with a %d-day free trial.How to upload and activate?How will you promote us?I can't pay for it anymoreI couldn't understand how to make it workI don't like to share my information with youI found a better %sI have upgraded my account but when I try to Sync the License, the plan remains %s.I no longer need the %sI only needed the %s for a short periodIDIf you click it, this decision will be delegated to the sites administrators.If you have a moment, please let us know why you are %sIf you would like to give up the ownership of the %s's account to %s click the Change Ownership button.If you'd like to use the %s on those sites, please enter your license key below and click the activation button.Important Upgrade Notice:In %sIn case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?Install Free Version NowInstall Free Version Update NowInstall NowInstall Update NowInstalling plugin: %sInvalid module ID.Invalid site details collection.InvoiceIs ActiveIt looks like the license could not be activated.It looks like the license deactivation failed.It looks like you are not in trial mode anymore so there's nothing to cancel :)It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry.It looks like your site currently doesn't have an active license.It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.It's not what I was looking forJust letting you know that the add-ons information of %s is being pulled from an external server.KeyKindly share what didn't work so we can fix it for future users...Kindly tell us the reason so we can improve.LastLast UpdatedLast licenseLatest Free Version InstalledLatest Version InstalledLearn moreLengthLicenseLicense AgreementLicense KeyLicense keyLicense key is empty.LifetimeLike the %s? Become our ambassador and earn cash ;-)Load DB OptionLocalhostLogLoggerMessageMethodMigrate Options to NetworkMobile appsModuleModule PathModule TypeMore information about %sNameNetwork BlogNetwork UserNewNew Version AvailableNewer Free Version (%s) InstalledNewer Version (%s) InstalledNewsletterNextNoNo IDNo commitment for %s - cancel anytimeNo commitment for %s days - cancel anytime!No credit card requiredNo expirationNon-expiringNone of the %s's plans supports a trial period.O.KOnce your license expires you can still use the Free version but you will NOT have access to the %s features.Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license.Opt InOpt OutOpt in to make "%s" better!OtherOwner EmailOwner IDOwner NamePCI compliantPaid add-on must be deployed to Freemius.PayPal account email addressPaymentsPayouts are in USD and processed monthly via PayPal.PlanPlan %s do not exist, therefore, can't start a trial.Plan %s does not support a trial period.Plan IDPlease contact us herePlease contact us with the following message:Please download %s.Please enter the license key that you received in the email right after the purchase:Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential).Please follow these steps to complete the upgradePlease let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price.Please provide details on how you intend to promote %s (please be as specific as possible).Please provide your full name.PluginPlugin HomepagePlugin IDPlugin InstallPlugin installer section titleChangelogPlugin installer section titleDescriptionPlugin installer section titleFAQPlugin installer section titleFeatures & PricingPlugin installer section titleInstallationPlugin installer section titleOther NotesPlugin installer section titleReviewsPlugin is a "Serviceware" which means it does not have a premium code version.PluginsPlugins & Themes SyncPremiumPremium %s version was successfully activated.Premium add-on version already installed.Premium versionPremium version already active.PricingPrivacy PolicyProceedProcess IDProductsProgram SummaryPromotion methodsProvincePublic KeyPurchase LicenseQuick FeedbackQuotaRe-send activation emailRefer new customers to our %s and earn %s commission on each successful sale you refer!Renew licenseRenew your license nowRequestsRequires WordPress VersionResultSDKSDK PathSave %sScheduled CronsScreenshotsSearch by addressSecret KeySecure HTTPS %s page, running from an external domainSeems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes.Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.Seems like you got the latest release.Select CountrySend License KeySet DB OptionSimulate Network UpgradeSimulate Trial PromotionSingle Site LicenseSite IDSite successfully opted in.SitesSkip & %sSlugSocial media (Facebook, Twitter, etc.)Sorry for the inconvenience and we are here to help if you give us a chance.Sorry, we could not complete the email update. Another user with the same email is already registered.StartStart TrialStart my free %sStateSubmit & %sSubscriptionSupportSupport ForumSync Data From ServerTax / VAT IDTerms of ServiceThank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days.Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information.Thank you so much for using %s and its add-ons!Thank you so much for using %s!Thank you so much for using our products!Thank you!Thanks %s!Thanks for confirming the ownership change. An email was just sent to %s for final approval.The %s broke my siteThe %s didn't workThe %s didn't work as expectedThe %s is great, but I need specific feature that you don't supportThe %s is not workingThe %s suddenly stopped workingThe installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page.The remote plugin package does not contain a folder with the desired slug and renaming did not work.The upgrade of %s was successfully completed.ThemeTheme SwitchThemesThere is a %s of %s available.There is a new version of %s available.This plugin has not been marked as compatible with your version of WordPress.This plugin has not been tested with your current version of WordPress.TimestampTitleTotalTownTrialTypeUnable to connect to the filesystem. Please confirm your credentials.Unlimited LicensesUnlimited UpdatesUnlimited commissions.Up to %s SitesUpdateUpdate LicenseUpdates, announcements, marketing, no spamUpgradeUpload and activate the downloaded versionUsed to express elation, enthusiasm, or triumph (especially in electronic communication).W00tUser IDUsersValueVerification mail was just sent to %s. If you can't find it after 5 min, please check your spam box.VerifiedVerify EmailVersion %s was released.View detailsView paid featuresWarningWe can't see any active licenses associated with that email address, are you sure it's the right address?We couldn't find your email address in the system, are you sure it's the right address?We made a few tweaks to the %s, %sWe're excited to introduce the Freemius network-level integration.Website, email, and social media statistics (optional)What did you expect?What feature?What is your %s?What price would you feel comfortable paying?What you've been looking for?What's the %s's name?Where are you going to promote the %s?WordPress.org Plugin PageYesYes - %sYou already utilized a trial before.You are 1-click away from starting your %1$s-day free trial of the %2$s plan.You are all good!You are already running the %s in a trial mode.You are just one step away - %sYou can still enjoy all %s features but you will not have access to %s security & feature updates, nor support.You do not have a valid license to access the premium version.You have a %s license.You have successfully updated your %s.You might have missed it, but you don't have to share any data and can just %s the opt-in.You've already opted-in to our usage-tracking, which helps us keep improving the %s.You've already opted-in to our usage-tracking, which helps us keep improving them.Your %s Add-on plan was successfully upgraded.Your %s free trial was successfully cancelled.Your account was successfully activated with the %s plan.Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s.Your affiliation account was temporarily suspended.Your email has been successfully verified - you are AWESOME!Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your free trial has expired. You can still continue using all our free features.Your license has been cancelled. If you think it's a mistake, please contact support.Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions.Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support.Your license has expired. You can still continue using the free %s forever.Your license was successfully activated.Your license was successfully deactivated, you are back to the %s plan.Your name was successfully updated.Your plan was successfully changed to %s.Your plan was successfully upgraded.Your subscription was successfully cancelled. Your %s plan license will expire in %s.Your trial has been successfully started.ZIP / Postal Codea positive responseRight onactive add-onActiveaddonX cannot run without pluginY%s cannot run without %s.addonX cannot run...%s cannot run without the plugin.advance notice of something that will need attention.Heads upallowas 5 licenses left%s leftas activating pluginActivatingas annual periodyearas application program interfaceAPIas close a windowDismissas code debuggingDebuggingas congratulationsCongratsas connection blockedBlockedas connection was successfulConnectedas download latest versionDownload Latestas download latest versionDownload Latest Free Versionas every monthMonthlyas expiration dateExpirationas file/folder pathPathas in the process of sending an emailSending emailas monthly periodmoas once a yearAnnualas once a yearAnnuallyas once a yearOnceas product pricing planPlanas secret encryption key missingNo Secretas software development kit versionsSDK Versionsas software licenseLicenseas synchronizeSyncas synchronize licenseSync Licenseas the plugin authorAuthoras turned offOffas turned onOnbased on %scall to actionStart free trialclose a windowDismissclose windowDismissdeactivatingdelegatedo %sNOT%s send me security & feature updates, educational content and offers.e.g. Professional Plan%s Plane.g. billed monthlyBilled %se.g. the best productBestexclamationHeyexclamationOopsgreetingHey %s,interjection expressing joy or exuberanceYee-hawlicenselike websitesSitesmillisecondsmsnew versionnot verifiednounPricenounPricingproduct versionVersionsecondssecsend me security & feature updates, educational content and offers.skipsomething somebody says when they are thinking about what you have just said.Hmmstart the trialsubscriptionswitchingthe latest %s version heretrialtrial periodTrialverbDeleteverbDowngradeverbEditverbHideverbOpt InverbOpt OutverbPurchaseverbShowverbSkipverbUpdateverbUpgradex-ago%s agoProject-Id-Version: WordPress SDK Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues PO-Revision-Date: 2023-04-19 18:31+0530 Last-Translator: Leo Fajardo , 2022 Language-Team: Dutch (Netherlands) (http://www.transifex.com/freemius/wordpress-sdk/language/nl_NL/) Language: nl_NL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Poedit-Basepath: .. X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c X-Poedit-SourceCharset: UTF-8 X-Generator: Poedit 3.2.2 X-Poedit-SearchPath-0: . X-Poedit-SearchPathExcluded-0: *.js %svoor toegang to versie %s beveiliging & features updates en support.Voltooi "%s" Activatie Nu%s Add-on werd succesvol aangekocht.%s Installaties%s Licenties%s geleden%sen bijbehorende uitbreidingen%s commissie als een klant een nieuwe licentie koopt. %s gratis proefperiode werd succesvol stop gezet. Daar de add-on alleen als premium versie beschikbaar is werd deze automatisch gedeactiveerd. Als u de add-on in de toekomst wilt gebruiken dient u een licentie aan te schaffen.%s is uitsluitend beschikbaar als een premium add-on. Je moet een licentie kopen voordat je de plug-in activeert.%s is de nieuwe eigenaar van het account.%s minimum uitbetalingsbedrag.%s of hoger%s beoordeling%s beoordelingen%s sec%s ster%s sterren%s tijd%s tijden%svoor toegang tot versie %s beveiliging en feature updates en support.%s tracking cookie na eerste bezoek om je verdienpotentieel te maximaliseren.%s betaalde mogelijkheden%sKlik hier%s om de sites te kiezen waar op je de licentie wilt activeren.API←➤AccountgegevensActiesActiveerActiveer %sActiveer %s PlanActiveer %s features.Activeer Gratis VersieActiveer LicentieActiveer licentie op alle in behandeling zijnde sites.Activeer licentie op alle sites in het netwerk.Activeer deze add-onGeactiveerdAdd-ons voor %sUitbreidingen van module %sVoeg nog een domein toeUitbreidingUitbreidingenAdd-on moet op WordPress.org of Freemius geplaatst worden.AdresAdresregel %dAffiliateAffiliatieNa uw gratis %s, betaal slechts %sAkkoord & Activeer LicentieAlle RequestsAlle TypesToestaan & Ga VerderJe kunt dat eventueel ook nu overslaan en de licentie later in je %s netwerk-niveau Account pagina activeren. BedragEen geautomatiseerde download en installatie van %s (betaalde versie) van %s zal starten binnen %s. Als je dit handmatig wil doen, klik dan nu op de annuleer knop.Anonieme terugkoppelingPas toe op alle in behandeling zijnde sites.Pas toe op alle sites in het netwerk.Meld je aan om een affiliate partner te wordenWeet u zeker dat u alle Freemius data wilt verwijderen?Weet je zeker dat je wilt doorgaan?Omdat wij 30 dagen reserveren voor eventuele terugstortingen, betalen we alleen commissies uit die ouder dan 30 dagen zijn.Automatische installatie werkt alleen voor opted-in gebruikers.Auto hernieuwd over %sAutomatische InstallatieGemiddelde BeoordelingGeweldigWordt een affiliateFactureringGeblokkeerdBlog IDBodyBedrijfsnaamKoop nu een licentieKoop licentieKan je je licentiesleutel niet vinden?AnnuleerAnnuleer %s & Ga DoorAnnuleer %s - Ik heb niet meer enige beveiligings- en uitbreidingsupdates of ondersteuning voor %s nodig, omdat ik niet van plan ben de %sop deze of enige andere site te gebruiken.%s annuleren?Annuleer InstallatieAbonnement OpzeggenProefperiode OpzeggenGeannuleerdAnnuleren %s%s wordt geannuleerd...Het abonnement annulerenHet stopzetten van de proefperiode zal de toegang tot de premium features onmiddellijk blokkeren. Weet je dat zeker?Verander LicentieEigendom OverdragenWijzig PlanAfrekenenStadAPI-Cache LeegmakenUpdates Transients OpschonenKlik hier om de plug-in anoniem te gebruikenKlik om reviews te bekijken met een beoordeling van%sKlik voor het op volle-grootte bekijken van schermafbeelding %dProductenCodeCompatible totContactContacteer SupportContacteer OnsMedewerkersKon %s niet activeren.LandCron TypeDatumDeactiveerDeactiveer LicentieHet deactiveren en deïnstalleren van de %s zal de licentie automatisch uitschakelen, die je dan kan gebruiken op een andere site.Deactiveren van je licentie zal alle premium features blokkeren, maar geeft je de mogelijkheid de licentie op een andere site te activeren. Weet je zeker dat je wilt doorgaan?DeactivatieDebug LogDelegeren aan Site BeheerdersVerwijder All AccountsDetailsAnnuleer %s niet - Ik wil nog steeds zowel beveiligings- en uitbreidingsupdates ontvangen als contact kunnen opnemen met Support.Heb je geen licentiesleutel?Doneer aan deze plug-inJe plan naar beneden bijstellenDownloadDownload %s VersieDownload de meeste recente %s versieDownload de meeste recente versieGedownloadAls gevolg van het overtreden van onze affiliate voorwaarden, hebben we besloten je affiliate account tijdelijk te blokkeren. Neem voor eventuele vragen alsjeblieft contact op met support.Tijdens het update proces detecteerden we %dsite(s) waarvoor de licentie nog niet geactiveerd is.Tijdens het update proces detecteerden we %dsite(s) in het netwerk die jouw aandacht vereisen.E-mailE-mailadresEindeVoer de domeinnaam in van je website of andere websites waar vanaf je van plan bent de %ste gaan promoten.Voer hieronder het e-mailadres in dat je gebruikt hebt voor de upgrade en we zullen je jouw licentiesleutel opnieuw toesturen.FoutFoutmelding ontvangen van de server:VerlopenVerloopt over %sExtra DomeinenExtra domeinen vanaf waar je het product gaat promoten.BestandFilterVoordat we de proefperiode kunnen starten, vragen we je, in overeenstemming met de Wordpress.org-richtlijnen, in te stemmen je gebruikers- en niet-sensitieve site informatie door de %s periodiek te laten verzenden naar %s om te controleren op nieuwe versies en je proefversie te valideren.GratisGratis ProefperiodeGratis versieFreemius APIFreemius DebugFreemius SDK kon het hoofdbestand van de plug-in niet vinden. Neem a.j.b. contact op met sdk@freemius.com m.b.t. deze fout.Freemius StatusVolledige naamFunctieKrijg een commissie voor automatische abonnementsverlengingen.Heb je een licentiesleutel?Hey, wist je dat %s een samenwerkingsprogramma heeft? Als je de %s goedvindt, kun je onze ambassadeur worden en wat geld verdienen!Hoe bevalt %s tot dusver? Test al onze %s premium features gedurende een%d-daagse gratis proefperiode.Hoe te uploaden en activeren?Hoe ga je ons promoten?Ik kan er niet langer meer voor betalenIk snapte niet hoe ik het aan het werk kon krijgen.Ik vind het niet prettig om mijn informatie met jullie te delenIk vond een beter %sIk heb mijn account geüpgraded maar als ik probeer te Synchroniseren blijft het plan %s.Ik heb de %s niet meer nodig Ik had de %s alleen nodig voor een korte periode.IDAl je er op klikt, zal deze beslissing gedelegeerd worden aan de beheerders van de sites. We zouden het zeer op prijs stellen, als je even hebt, om ons alsjeblieft te laten weten waarom je gaat %sAls je het eigendom van het %s account wilt overdragen aan %s, klik dan op de Eigendom Overdragen knop. Als je de %s op deze sites wil gebruiken, voer dan alsjeblieft de licentiesleutel hieronder in en klik op de activatie-knop.Belangrijke Upgrade Mededeling:Binnen %sMocht je NIET van plan zijn om deze %s te gebruiken op deze site (of op een andere site) - wil je dan het %s ook opzeggen?Installer Gratis Versie NuInstalleer Gratis Versie Update NuInstalleer NuInstalleer Update NuInstalleren van plug-in: %sOngeldige Module-IDOngeldige verzameling van Site Details.FactuurIs ActiefHet lijkt erop dat de licentie niet geactiveerd kon worden.Het lijkt erop dat het deactiveren van je licentie mislukt is.Het lijkt er op dat u niet langer meer in de proefperiode zit, dus er valt niets stop te zetten.Het lijkt erop dat u nog steeds op het %s plan zit. Als u uw plan geüpgraded of veranderd heeft, dan is het waarschijnlijk een fout aan onze kant - sorry.Het lijkt erop dat je site momenteel geen actieve licentie heeft.Het lijkt erop dat een van de authenticatie parameters niet klopt. Update je Publieke Sleutel, Geheime Sleutel & Gebruikers ID en probeer het nogmaals. Het is niet waarna ik opzoek wasVoor alle duidelijkheid, de add-ons informatie van %s wordt opgehaald van een externe server.SleutelWil je alsjeblieft zo vriendelijk zijn om te delen wat niet werkte, zodat we dat kunnen verbeteren voor toekomstige gebruikers ...Wilt je alsjeblieft zo vriendelijk zijn om de reden te vermelden, zodat wij verbeteringen kunnen doorvoeren.LaatsteLaatst GeüpdatetLaatste licentieNieuwste Gratis Versie GeïnstalleerdMeest Recente Versie GeïnstalleerdLees meerLengteLicentieLicentieovereenkomstLicentiesleutelLicentiesleutelLicentiesleutel is leeg.LevenslangVind je de %s goed? Word dan onze ambassadeur en verdien cash ;-)Laad DB-optieLocalhostLogLoggerBerichtMethodesZet Opties over naar NetwerkMobiele appsModuleModule PadModuletypeMeer informatie over %sNaamNetwerk BlogNetwerk GebruikerNieuwNieuwe Versie BeschikbaarNieuwere Gratis Versie (%s) GeïnstalleerdNieuwere Versie (%s) GeïnstalleerdNieuwsbriefVolgendeNeeGeen IDGeen verplichting voor %s - opzeggen kan altijdGeen verplichting voor %s dagen - elk moment opzeggen!Geen creditcard nodigGeen verloopdatumNiet-verlopendeGeen van de %s plannen ondersteunt een proefperiode.OkéAls je licentie verloopt kan je nog steeds gebruik maken van de Gratis versie, maar je zal GEEN toegang meer hebben tot de %sfeatures.Als je licentie afloopt, zul je %s niet meer kunnen gebruiken, tenzij je het opnieuw activeert met een geldige Premium-licentie.Opt InOpt OutOpt-in om "%s" te verbeteren!OverigeE-mail EigenaarID EigenaarNaam EigenaarPCI-comformBetaalde add-on moet op Freemius geplaatst worden.PayPal account e-mailadresBetalingenUitbetalingen zijn in USD en worden maandelijks uitgevoerd via PayPalPlanPlan %s bestaat niet, daarom kan proefperiode niet gestart worden.Plan %s ondersteunt geen proefperiode.Plan IDNeem hier a.u.b. contact met ons opNeem a.u.b. contact met ons op met het volgende bericht:A.u.b. %s downloaden.Voer aalsjeblieft de licentiesleutel in die je ontving in de e-mail direct na de aankoop:Voel je alsjeblieft vrij om elke relevante website of social media statistieken met ons te delen, bijvoorbeeld maandelijkse unieke bezoekers, aantal e-mail abonnees , volgers, etc. (we zullen deze informatie vertrouwelijk houden).Volg alsjeblieft deze stappen om de upgrade te voltooienLaat ons alsjeblieft weten als je op de hoogte gehouden wilt worden van beveiliging & feature updates, educatieve content en zo nu en dan aanbiedingen:Onthou alsjeblieft dat we geen oude prijzen voor verlengingen/nieuwe abonnementen na een annulering kunnen aanhouden. Als je in de toekomst besluit om een abonnement handmatig te vernieuwen, zal de nieuwe prijs (na een prijsverhoging die meestal jaarlijks plaatsvindt) worden berekend.Geef alsjeblieft zo gedetailleerd als mogelijk aan hoe je van plan bent om %s te gaan promoten.Geef alsjeblieft je volledige naam.Plug-inPlug-in HomepagePlug-in IDPlug-in InstallatieWijzigingen LogBeschrijvingVeelgestelde VragenFeatures & PrijzenInstallatieAndere NotitiesReviewsPlug-in is 'Serviceware' wat betekent dat het geen premium code versie bevat. Plug-insSynchronisatie Plug-ins & Thema'sPremiumPremium %s versie is succesvol geactiveerd.Premium add-on versie is reeds geïnstalleerd.Premium versiePremium versie reeds actief.PrijzenPrivacybeleidDoorgaanProces-IDProductenProgramma SamenvattingPromotie methodesProvinciePublieke SleutelLicentie KopenSnelle terugkoppelingQuotaActivatiemail opnieuw versturenVerwijs nieuwe klanten naar onze %s en krijg %s commissie op iedere door jou doorverwezen, geslaagde verkoop!Vernieuw licentieVernieuw je licentie nuAanvragenVereiste WordPress-versieResultaatSDKSDK PadBespaar %sGeplande CronsSchermafbeeldingenZoek op adresGeheime SleutelBeveiligde HTTPS %s pagina, loopt via een extern domeinHet lijkt erop, dat we een tijdelijk probleem hebben met het annuleren van je abonnement. Probeer het alsjeblieft over een paar minuten nog eens.Het lijkt er op dat we een tijdelijk probleem hebben met het opzeggen van uw proefperiode. Probeer het a.u.b. over enkele minuten nog eens.Het lijkt erop dat je de meest recente versie hebt.Selecteer LandVerzend LicentiesleutelActiveer DB-OptieSimuleer Netwerk UpgradeSimuleer Trial ActieEnkele Site LicentieSite IDSite opt-in geslaagd. SitesSla over & %sSlugSocial media (Facebook, Twitter, etc.)Sorry voor het ongemak en we zijn er om je te helpen als je daartoe de kans geeft..Sorry, we konden de e-mail update niet voltooien. Een andere gebruiker met hetzelfde e-mailadres is reeds geregistreerd.StartStart ProefperiodeStart mijn gratis %sStaatVerstuur & %sAbonnementOndersteuningSupportforumSynchroniseer Data Vanaf ServerBtw-nummerServicevoorwaardenBedankt voor je aanvraag voor deelname aan ons affiliate programma, helaas, op dit moment hebben we besloten je aanvraag af te wijzen. Probeer het alsjeblieft over 30 dagen nog eens.Bedankt voor je aanvraag voor deelname aan ons samenwerkingsprogramma. We zullen binnen 14 dagen je gegevens doornemen, waarna we je aanvullende informatie zullen sturen.Hartelijk bedankt voor het gebruik van %s en bijbehorende uitbreidingen!Hartelijk bedankt voor het gebruik van %s!Hartelijk bedankt voor het gebruiken van onze producten!Bedankt!Bedankt %s!Bedankt voor het bevestigen van de eigendomsoverdracht. Zojuist is er een e-mail verstuurd naar %s voor de definitieve goedkeuring. De %s maakte mijn site onbruikbaarDe %s werkte nietDe %s werkte niet zoals verwachtDe %s is uitstekend, maar ik heb een specifieke feature nodig die jullie niet ondersteunenDe %s werkt nietDe %s werkte opeens niet meerHet installatieproces is gestart en kan enkele minuten duren om te voltooien. Wacht alsjeblieft totdat dat gebeurt is - deze pagina niet verversen.Het remote plug-in pakket bevat geen folder met de verwachte slug en hernoemen werkte niet. De upgrade van %s is succesvol voltooid.ThemaThema WisselThema'sEr is een %s van %s beschikbaar.Er is een nieuwe versie van %s beschikbaar.Deze plug-in is niet als compatibel aangemerkt voor je huidige WordPress versie.Deze plug-in is nog niet getest met je huidige WordPress versie. TijdstempelTitelTotaalPlaatsProefperiodeTypeToegang tot het bestandssysteem is niet mogelijk. Bevestig alsjeblieft je inloggegevens.Onbeperkte LicentiesOnbeperkte UpdatesOnbeperkte commissies.Tot %s SitesBijwerkenUpdate LicentieUpdates, aankondigingen, marketing, geen spamUpgradeUpload en activeer de gedownloade versieW00tGebruikers IDGebruikersWaardeVerificatiemail zojuist verstuurd naar %s. Als je deze niet binnen 5 min. hebt ontvangen, kijk dan alsjeblieft in je spambox.GeverifieerdVerifieer E-mailVersie %s is vrijgegeven.Bekijk detailsBekijk betaalde kenmerkenWaarschuwingEr is geen actieve licentie gekoppeld aan dat e-mailadres, ben je zeker dat dat het juiste adres is?We konden je e-mailadres niet vinden in het systeem, ben je zeker dat dat het juiste adres is?We hebben een aantal aanpassingen gedaan op de %s, %s We zijn verheugd om Freemius network-level integratie te introduceren.Website, mail, and social media statistieken (optioneel)Wat had je verwacht?Welke feature?Wat is je %s?Welke bedrag zou je ervoor over hebben?Waar was je naar op zoek?Wat is de naam van het %s?Waar ga je de %s promoten?WordPress.org Plug-in PaginaJaJa - %sU heeft reeds een proefperiode gebruikt.U bent 1-klik verwijderd van het starten van uw %1$s-daagse gratis proefperiode van het %2$s plan.Alles is goed!Je draait de %s al in proefmodus.Je bent slechts een stap verwijderd - %sJe kunt nog steeds van alle %s-mogelijkheden genieten, maar je zult geen toegang hebben tot %s veiligheids- en uitbreidingsupdates, noch ondersteuning.Je hebt geen geldige licentie voor de premium versie.Je hebt een %s licentieJe hebt je %s succesvol geüpdatet.Misschien heb je het gemist, maar je hoeft geen gegevens te delen en kunt de opt-in %s.Je hebt reeds ingestemd met onze gebruiks-tracking, wat ons helpt om %s te blijven verbeteren.Je hebt reeds ingestemd met onze gebruiks-tracking, wat ons helpt om deze te blijven verbeteren.Uw %sAdd-on plan werd succesvol geüpgraded. Uw gratis %s proefperiode is succesvol opgezegd. Je account is succesvol geactiveerd met het %s plan.Je samenwerkingsaanvraag voor %s is geaccepteerd! Log in op je samenwerkingsomgeving op: %s.Je affiliate account is tijdelijk geschorst.Je e-mail werd succesvol geverifieerd - je bent GEWELDIG!Je gratis proefperiode is verlopen. %1$sUpgrade nu%2$som de %3$s zonder interrupties te blijven gebruiken. Je gratis proefperiode is verlopen. Je kan nog steeds al onze gratis features blijven gebruiken.Je licentie is geannuleerd. Als je denkt dat dat een fout is, neem dan alsjeblieft contact op met support.Je licentie is verlopen. %1$sUpgrade nu%2$s om de %3$s zonder interrupties te blijven gebruiken.Je licentie is verlopen. Je kan nog steeds alle %s features gebruiken, maar je zal je licentie moeten vernieuwen om weer updates en support te ontvangen.Je licentie is verlopen. Je kan echter de gratis %s voor altijd blijven gebruiken.Je licentie is succesvol geactiveerd.Je licentie is succesvol gedeactiveerd, je bent terug op het %s plan.Je naam is succesvol bijgewerkt.Je plan is succesvol veranderd naar %s.Je plan is succesvol geüpgraded.Je abonnement is succesvol geannuleerd. De licentie van je %s-plan al over %s aflopen.U proefperiode is met succes gestart.PostcodeToppieActiveer%s werkt niet zonder %s.%s werkt niet zonder de plug-in.Aankondigingtoestaan%s beschikbaarActiverenjaarAPIAfsluitenDebuggingGefeliciteerdGeblokkeerdVerbondenDownload NieuwsteDownload Nieuwste Gratis VersieMaandelijksVerloopdatumPadE-mail versturenmndJaarlijksJaarlijksEenmaligPlanGeen GeheimSDK VersiesLicentieSyncSync LicentieAuteurUitAangebaseerd op %sStart gratis proefperidoeAfsluitenAfsluitendeactiverendeligerenstuur mij %sGEEN%s beveiliging & feature updates, educatieve content of aanbiedingen.%s Plan%s gefactureerd BesteHoiOepsHoi %s,HoeralicentieSitesmsnieuwe versieniet geverifieerdPrijsPrijzenVersiesecstuur mij beveiliging & feature updates, educatieve content en aanbiedingen.overslaanHmmstart de proefperiodeabonnementoverschakelende meest recente %s versie hierproefperiodeProefperiodeVerwijderDowngradeBewerkVerbergOpt InOpt OutKoopToonSla OverBijwerkenUpgrade%s geledenPK! e044freemius/languages/freemius.potnu[# Copyright (C) 2023 freemius # This file is distributed under the same license as the freemius package. msgid "" msgstr "" "Project-Id-Version: freemius\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language-Team: Freemius Team \n" "Last-Translator: Vova Feldman \n" "Report-Msgid-Bugs-To: https://github.com/Freemius/wordpress-sdk/issues\n" "X-Poedit-Basepath: ..\n" "X-Poedit-KeywordsList: get_text_inline;fs_text_inline;fs_echo_inline;fs_esc_js_inline;fs_esc_attr_inline;fs_esc_attr_echo_inline;fs_esc_html_inline;fs_esc_html_echo_inline;get_text_x_inline:1,2c;fs_text_x_inline:1,2c;fs_echo_x_inline:1,2c;fs_esc_attr_x_inline:1,2c;fs_esc_js_x_inline:1,2c;fs_esc_js_echo_x_inline:1,2c;fs_esc_html_x_inline:1,2c;fs_esc_html_echo_x_inline:1,2c\n" "X-Poedit-SearchPath-0: .\n" "X-Poedit-SearchPathExcluded-0: *.js\n" "X-Poedit-SourceCharset: UTF-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: includes/class-freemius.php:1748, templates/account.php:947 msgid "An update to a Beta version will replace your installed version of %s with the latest Beta release - use with caution, and not on production sites. You have been warned." msgstr "" #: includes/class-freemius.php:1755 msgid "Would you like to proceed with the update?" msgstr "" #: includes/class-freemius.php:1980 msgid "Freemius SDK couldn't find the plugin's main file. Please contact sdk@freemius.com with the current error." msgstr "" #: includes/class-freemius.php:1982, includes/fs-plugin-info-dialog.php:1517 msgid "Error" msgstr "" #: includes/class-freemius.php:2428 msgid "I found a better %s" msgstr "" #: includes/class-freemius.php:2430 msgid "What's the %s's name?" msgstr "" #: includes/class-freemius.php:2436 msgid "It's a temporary %s - I'm troubleshooting an issue" msgstr "" #: includes/class-freemius.php:2438 msgid "Deactivation" msgstr "" #: includes/class-freemius.php:2439 msgid "Theme Switch" msgstr "" #: includes/class-freemius.php:2448, templates/forms/resend-key.php:24, templates/forms/user-change.php:29 msgid "Other" msgstr "" #: includes/class-freemius.php:2456 msgid "I no longer need the %s" msgstr "" #: includes/class-freemius.php:2463 msgid "I only needed the %s for a short period" msgstr "" #: includes/class-freemius.php:2469 msgid "The %s broke my site" msgstr "" #: includes/class-freemius.php:2476 msgid "The %s suddenly stopped working" msgstr "" #: includes/class-freemius.php:2486 msgid "I can't pay for it anymore" msgstr "" #: includes/class-freemius.php:2488 msgid "What price would you feel comfortable paying?" msgstr "" #: includes/class-freemius.php:2494 msgid "I don't like to share my information with you" msgstr "" #: includes/class-freemius.php:2515 msgid "The %s didn't work" msgstr "" #: includes/class-freemius.php:2525 msgid "I couldn't understand how to make it work" msgstr "" #: includes/class-freemius.php:2533 msgid "The %s is great, but I need specific feature that you don't support" msgstr "" #: includes/class-freemius.php:2535 msgid "What feature?" msgstr "" #: includes/class-freemius.php:2539 msgid "The %s is not working" msgstr "" #: includes/class-freemius.php:2541 msgid "Kindly share what didn't work so we can fix it for future users..." msgstr "" #: includes/class-freemius.php:2545 msgid "It's not what I was looking for" msgstr "" #: includes/class-freemius.php:2547 msgid "What you've been looking for?" msgstr "" #: includes/class-freemius.php:2551 msgid "The %s didn't work as expected" msgstr "" #: includes/class-freemius.php:2553 msgid "What did you expect?" msgstr "" #: includes/class-freemius.php:3641, templates/debug.php:24 msgid "Freemius Debug" msgstr "" #: includes/class-freemius.php:4755 msgid "You have purchased a %s license." msgstr "" #: includes/class-freemius.php:4759 msgid " 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." msgstr "" #: includes/class-freemius.php:4769, includes/class-freemius.php:21125, includes/class-freemius.php:24783 msgctxt "interjection expressing joy or exuberance" msgid "Yee-haw" msgstr "" #: includes/class-freemius.php:4783 msgctxt "addonX cannot run without pluginY" msgid "%s cannot run without %s." msgstr "" #: includes/class-freemius.php:4784 msgctxt "addonX cannot run..." msgid "%s cannot run without the plugin." msgstr "" #: includes/class-freemius.php:4786, includes/class-freemius.php:5978, includes/class-freemius.php:13730, includes/class-freemius.php:14469, includes/class-freemius.php:18281, includes/class-freemius.php:18394, includes/class-freemius.php:18571, includes/class-freemius.php:20856, includes/class-freemius.php:21955, includes/class-freemius.php:22971, includes/class-freemius.php:23101, includes/class-freemius.php:23231, templates/add-ons.php:57 msgctxt "exclamation" msgid "Oops" msgstr "" #: includes/class-freemius.php:5065 msgid "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:" msgstr "" #: includes/class-freemius.php:5645 msgid "Premium %s version was successfully activated." msgstr "" #: includes/class-freemius.php:5657, includes/class-freemius.php:7692 msgctxt "Used to express elation, enthusiasm, or triumph (especially in electronic communication)." msgid "W00t" msgstr "" #: includes/class-freemius.php:5672 msgid "You have a %s license." msgstr "" #: includes/class-freemius.php:5961 msgid "%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you'll have to purchase a license." msgstr "" #: includes/class-freemius.php:5965 msgid "%s is a premium only add-on. You have to purchase a license first before activating the plugin." msgstr "" #: includes/class-freemius.php:5974, templates/add-ons.php:186, templates/account/partials/addon.php:386 msgid "More information about %s" msgstr "" #: includes/class-freemius.php:5975 msgid "Purchase License" msgstr "" #. translators: %3$s: action (e.g.: "start the trial" or "complete the opt-in") #: includes/class-freemius.php:6971 msgid "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." msgstr "" #: includes/class-freemius.php:6974 msgid "start the trial" msgstr "" #: includes/class-freemius.php:6975, templates/connect.php:218 msgid "complete the opt-in" msgstr "" #: includes/class-freemius.php:6977 msgid "Thanks!" msgstr "" #. translators: %3$s: What the user is expected to receive via email (e.g.: "the installation instructions" or "a license key") #: includes/class-freemius.php:6980 msgid "You should receive %3$s for %1$s to your mailbox at %2$s in the next 5 minutes." msgstr "" #: includes/class-freemius.php:6983 msgctxt "Part of the message telling the user what they should receive via email." msgid "the installation instructions" msgstr "" #: includes/class-freemius.php:6989 msgctxt "Part of the message telling the user what they should receive via email." msgid "a license key" msgstr "" #: includes/class-freemius.php:6997 msgid "%s to activate the license once you get it." msgstr "" #: includes/class-freemius.php:7005 msgctxt "Part of an activation link message." msgid "Click here" msgstr "" #: includes/class-freemius.php:7012 msgctxt "Part of the message that tells the user to check their spam folder for a specific email." msgid "the product's support email address" msgstr "" #: includes/class-freemius.php:7018 msgid "If you didn't get the email, try checking your spam folder or search for emails from %4$s." msgstr "" #: includes/class-freemius.php:7020 msgid "Thanks for upgrading." msgstr "" #: includes/class-freemius.php:7156 msgid "You are just one step away - %s" msgstr "" #: includes/class-freemius.php:7159 msgctxt "%s - plugin name. As complete \"PluginX\" activation now" msgid "Complete \"%s\" Activation Now" msgstr "" #: includes/class-freemius.php:7241 msgid "We made a few tweaks to the %s, %s" msgstr "" #: includes/class-freemius.php:7245 msgid "Opt in to make \"%s\" better!" msgstr "" #: includes/class-freemius.php:7691 msgid "The upgrade of %s was successfully completed." msgstr "" #: includes/class-freemius.php:10441, includes/class-fs-plugin-updater.php:1100, includes/class-fs-plugin-updater.php:1315, includes/class-fs-plugin-updater.php:1322, templates/auto-installation.php:32 msgid "Add-On" msgstr "" #: includes/class-freemius.php:10443, templates/account.php:411, templates/account.php:419, templates/debug.php:399, templates/debug.php:619 msgid "Plugin" msgstr "" #: includes/class-freemius.php:10444, templates/account.php:412, templates/account.php:420, templates/debug.php:399, templates/debug.php:619, templates/forms/deactivation/form.php:107 msgid "Theme" msgstr "" #: includes/class-freemius.php:13549 msgid "An unknown error has occurred while trying to toggle the license's white-label mode." msgstr "" #: includes/class-freemius.php:13563 msgid "Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s." msgstr "" #: includes/class-freemius.php:13568, templates/account/partials/disconnect-button.php:84 msgid "User Dashboard" msgstr "" #: includes/class-freemius.php:13569 msgid "revert it now" msgstr "" #: includes/class-freemius.php:13627 msgid "An unknown error has occurred while trying to set the user's beta mode." msgstr "" #: includes/class-freemius.php:13701 msgid "Invalid new user ID or email address." msgstr "" #: includes/class-freemius.php:13731 msgid "Sorry, we could not complete the email update. Another user with the same email is already registered." msgstr "" #: includes/class-freemius.php:13732 msgid "If you would like to give up the ownership of the %s's account to %s click the Change Ownership button." msgstr "" #: includes/class-freemius.php:13739 msgid "Change Ownership" msgstr "" #: includes/class-freemius.php:14336 msgid "Invalid site details collection." msgstr "" #: includes/class-freemius.php:14456 msgid "We couldn't find your email address in the system, are you sure it's the right address?" msgstr "" #: includes/class-freemius.php:14458 msgid "We can't see any active licenses associated with that email address, are you sure it's the right address?" msgstr "" #: includes/class-freemius.php:14756 msgid "Account is pending activation. Please check your email and click the link to activate your account and then submit the affiliate form again." msgstr "" #: includes/class-freemius.php:14870, templates/forms/premium-versions-upgrade-handler.php:47 msgid "Buy a license now" msgstr "" #: includes/class-freemius.php:14882, templates/forms/premium-versions-upgrade-handler.php:46 msgid "Renew your license now" msgstr "" #: includes/class-freemius.php:14886 msgid "%s to access version %s security & feature updates, and support." msgstr "" #: includes/class-freemius.php:17621 msgid "%s opt-in was successfully completed." msgstr "" #: includes/class-freemius.php:17635 msgid "Your account was successfully activated with the %s plan." msgstr "" #: includes/class-freemius.php:17645, includes/class-freemius.php:21566 msgid "Your trial has been successfully started." msgstr "" #: includes/class-freemius.php:18279, includes/class-freemius.php:18392, includes/class-freemius.php:18569 msgid "Couldn't activate %s." msgstr "" #: includes/class-freemius.php:18280, includes/class-freemius.php:18393, includes/class-freemius.php:18570 msgid "Please contact us with the following message:" msgstr "" #: includes/class-freemius.php:18389, templates/forms/data-debug-mode.php:162 msgid "An unknown error has occurred." msgstr "" #: includes/class-freemius.php:18931, includes/class-freemius.php:24339 msgid "Upgrade" msgstr "" #: includes/class-freemius.php:18937 msgid "Start Trial" msgstr "" #: includes/class-freemius.php:18939 msgid "Pricing" msgstr "" #: includes/class-freemius.php:19019, includes/class-freemius.php:19021 msgid "Affiliation" msgstr "" #: includes/class-freemius.php:19049, includes/class-freemius.php:19051, templates/account.php:264, templates/debug.php:366 msgid "Account" msgstr "" #: includes/class-freemius.php:19065, includes/class-freemius.php:19067, includes/customizer/class-fs-customizer-support-section.php:60 msgid "Contact Us" msgstr "" #: includes/class-freemius.php:19078, includes/class-freemius.php:19080, includes/class-freemius.php:24353, templates/account.php:134, templates/account/partials/addon.php:49 msgid "Add-Ons" msgstr "" #: includes/class-freemius.php:19114 msgctxt "ASCII arrow left icon" msgid "←" msgstr "" #: includes/class-freemius.php:19114 msgctxt "ASCII arrow right icon" msgid "➤" msgstr "" #: includes/class-freemius.php:19116, templates/pricing.php:110 msgctxt "noun" msgid "Pricing" msgstr "" #: includes/class-freemius.php:19329, includes/customizer/class-fs-customizer-support-section.php:67 msgid "Support Forum" msgstr "" #: includes/class-freemius.php:20350 msgid "Your email has been successfully verified - you are AWESOME!" msgstr "" #: includes/class-freemius.php:20351 msgctxt "a positive response" msgid "Right on" msgstr "" #: includes/class-freemius.php:20857 msgid "seems like the key you entered doesn't match our records." msgstr "" #: includes/class-freemius.php:20881 msgid "Debug mode was successfully enabled and will be automatically disabled in 60 min. You can also disable it earlier by clicking the \"Stop Debug\" link." msgstr "" #: includes/class-freemius.php:21116 msgid "Your %s Add-on plan was successfully upgraded." msgstr "" #: includes/class-freemius.php:21118 msgid "%s Add-on was successfully purchased." msgstr "" #: includes/class-freemius.php:21121 msgid "Download the latest version" msgstr "" #: includes/class-freemius.php:21239 msgid "It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again." msgstr "" #: includes/class-freemius.php:21239, includes/class-freemius.php:21636, includes/class-freemius.php:21737, includes/class-freemius.php:21824 msgid "Error received from the server:" msgstr "" #: includes/class-freemius.php:21470, includes/class-freemius.php:21742, includes/class-freemius.php:21795, includes/class-freemius.php:21902 msgctxt "something somebody says when they are thinking about what you have just said." msgid "Hmm" msgstr "" #: includes/class-freemius.php:21483 msgid "It looks like you are still on the %s plan. If you did upgrade or change your plan, it's probably an issue on our side - sorry." msgstr "" #: includes/class-freemius.php:21484, templates/account.php:136, templates/add-ons.php:250, templates/account/partials/addon.php:51 msgctxt "trial period" msgid "Trial" msgstr "" #: includes/class-freemius.php:21489 msgid "I have upgraded my account but when I try to Sync the License, the plan remains %s." msgstr "" #: includes/class-freemius.php:21493, includes/class-freemius.php:21545 msgid "Please contact us here" msgstr "" #: includes/class-freemius.php:21515 msgid "Your plan was successfully changed to %s." msgstr "" #: includes/class-freemius.php:21531 msgid "Your license has expired. You can still continue using the free %s forever." msgstr "" #: includes/class-freemius.php:21533 msgid "Your license has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." msgstr "" #: includes/class-freemius.php:21541 msgid "Your license has been cancelled. If you think it's a mistake, please contact support." msgstr "" #: includes/class-freemius.php:21554 msgid "Your license has expired. You can still continue using all the %s features, but you'll need to renew your license to continue getting updates and support." msgstr "" #: includes/class-freemius.php:21580 msgid "Your free trial has expired. You can still continue using all our free features." msgstr "" #: includes/class-freemius.php:21582 msgid "Your free trial has expired. %1$sUpgrade now%2$s to continue using the %3$s without interruptions." msgstr "" #: includes/class-freemius.php:21628 msgid "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" msgstr "" #: includes/class-freemius.php:21630 msgid "Show error details" msgstr "" #: includes/class-freemius.php:21733 msgid "It looks like the license could not be activated." msgstr "" #: includes/class-freemius.php:21775 msgid "Your license was successfully activated." msgstr "" #: includes/class-freemius.php:21799 msgid "It looks like your site currently doesn't have an active license." msgstr "" #: includes/class-freemius.php:21823 msgid "It looks like the license deactivation failed." msgstr "" #: includes/class-freemius.php:21852 msgid "Your %s license was successfully deactivated." msgstr "" #: includes/class-freemius.php:21853 msgid "Your license was successfully deactivated, you are back to the %s plan." msgstr "" #: includes/class-freemius.php:21856 msgid "O.K" msgstr "" #: includes/class-freemius.php:21909 msgid "Seems like we are having some temporary issue with your subscription cancellation. Please try again in few minutes." msgstr "" #: includes/class-freemius.php:21918 msgid "Your subscription was successfully cancelled. Your %s plan license will expire in %s." msgstr "" #: includes/class-freemius.php:21960 msgid "You are already running the %s in a trial mode." msgstr "" #: includes/class-freemius.php:21971 msgid "You already utilized a trial before." msgstr "" #: includes/class-freemius.php:21985 msgid "Plan %s do not exist, therefore, can't start a trial." msgstr "" #: includes/class-freemius.php:21996 msgid "Plan %s does not support a trial period." msgstr "" #: includes/class-freemius.php:22007 msgid "None of the %s's plans supports a trial period." msgstr "" #: includes/class-freemius.php:22056 msgid "It looks like you are not in trial mode anymore so there's nothing to cancel :)" msgstr "" #: includes/class-freemius.php:22092 msgid "Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes." msgstr "" #: includes/class-freemius.php:22111 msgid "Your %s free trial was successfully cancelled." msgstr "" #: includes/class-freemius.php:22438 msgid "Version %s was released." msgstr "" #: includes/class-freemius.php:22438 msgid "Please download %s." msgstr "" #: includes/class-freemius.php:22445 msgid "the latest %s version here" msgstr "" #: includes/class-freemius.php:22450 msgid "New" msgstr "" #: includes/class-freemius.php:22455 msgid "Seems like you got the latest release." msgstr "" #: includes/class-freemius.php:22456 msgid "You are all good!" msgstr "" #: includes/class-freemius.php:22859 msgid "Verification mail was just sent to %s. If you can't find it after 5 min, please check your spam box." msgstr "" #: includes/class-freemius.php:22999 msgid "Site successfully opted in." msgstr "" #: includes/class-freemius.php:23000, includes/class-freemius.php:24049 msgid "Awesome" msgstr "" #: includes/class-freemius.php:23016 msgid "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." msgstr "" #: includes/class-freemius.php:23017 msgid "Thank you!" msgstr "" #: includes/class-freemius.php:23026 msgid "Diagnostic data will no longer be sent from %s to %s." msgstr "" #: includes/class-freemius.php:23181 msgid "A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours." msgstr "" #: includes/class-freemius.php:23183 msgid "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." msgstr "" #: includes/class-freemius.php:23190 msgid "Thanks for confirming the ownership change. An email was just sent to %s for final approval." msgstr "" #: includes/class-freemius.php:23195 msgid "%s is the new owner of the account." msgstr "" #: includes/class-freemius.php:23197 msgctxt "as congratulations" msgid "Congrats" msgstr "" #: includes/class-freemius.php:23214 msgid "Please provide your full name." msgstr "" #: includes/class-freemius.php:23219 msgid "Your name was successfully updated." msgstr "" #: includes/class-freemius.php:23280 msgid "You have successfully updated your %s." msgstr "" #: includes/class-freemius.php:23339 msgid "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin." msgstr "" #: includes/class-freemius.php:23342 msgid "Click here" msgstr "" #: includes/class-freemius.php:23379, includes/class-freemius.php:23376 msgid "Bundle" msgstr "" #: includes/class-freemius.php:23459 msgid "Just letting you know that the add-ons information of %s is being pulled from an external server." msgstr "" #: includes/class-freemius.php:23460 msgctxt "advance notice of something that will need attention." msgid "Heads up" msgstr "" #: includes/class-freemius.php:24089 msgctxt "exclamation" msgid "Hey" msgstr "" #: includes/class-freemius.php:24089 msgid "How do you like %s so far? Test all our %s premium features with a %d-day free trial." msgstr "" #: includes/class-freemius.php:24097 msgid "No commitment for %s days - cancel anytime!" msgstr "" #: includes/class-freemius.php:24098 msgid "No credit card required" msgstr "" #: includes/class-freemius.php:24105, templates/forms/trial-start.php:53 msgctxt "call to action" msgid "Start free trial" msgstr "" #: includes/class-freemius.php:24182 msgid "Hey there, did you know that %s has an affiliate program? If you like the %s you can become our ambassador and earn some cash!" msgstr "" #: includes/class-freemius.php:24191 msgid "Learn more" msgstr "" #: includes/class-freemius.php:24377, templates/account.php:573, templates/account.php:725, templates/connect.php:221, templates/connect.php:447, includes/managers/class-fs-clone-manager.php:1295, templates/forms/license-activation.php:27, templates/account/partials/addon.php:326 msgid "Activate License" msgstr "" #: includes/class-freemius.php:24378, templates/account.php:667, templates/account.php:724, templates/account/partials/addon.php:327, templates/account/partials/site.php:273 msgid "Change License" msgstr "" #: includes/class-freemius.php:24485, templates/account/partials/site.php:170 msgid "Opt Out" msgstr "" #: includes/class-freemius.php:24487, includes/class-freemius.php:24493, templates/account/partials/site.php:49, templates/account/partials/site.php:170 msgid "Opt In" msgstr "" #: includes/class-freemius.php:24728 msgid " The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s" msgstr "" #: includes/class-freemius.php:24738 msgid "Activate %s features" msgstr "" #: includes/class-freemius.php:24751 msgid "Please follow these steps to complete the upgrade" msgstr "" #: includes/class-freemius.php:24755 msgid "Download the latest %s version" msgstr "" #: includes/class-freemius.php:24759 msgid "Upload and activate the downloaded version" msgstr "" #: includes/class-freemius.php:24761 msgid "How to upload and activate?" msgstr "" #: includes/class-freemius.php:24796 msgid "Your plan was successfully upgraded." msgstr "" #: includes/class-freemius.php:24797 msgid "Your plan was successfully activated." msgstr "" #: includes/class-freemius.php:24927 msgid "%sClick here%s to choose the sites where you'd like to activate the license on." msgstr "" #: includes/class-freemius.php:25096 msgid "Auto installation only works for opted-in users." msgstr "" #: includes/class-freemius.php:25106, includes/class-freemius.php:25139, includes/class-fs-plugin-updater.php:1294, includes/class-fs-plugin-updater.php:1308 msgid "Invalid module ID." msgstr "" #: includes/class-freemius.php:25115, includes/class-fs-plugin-updater.php:1330 msgid "Premium version already active." msgstr "" #: includes/class-freemius.php:25122 msgid "You do not have a valid license to access the premium version." msgstr "" #: includes/class-freemius.php:25129 msgid "Plugin is a \"Serviceware\" which means it does not have a premium code version." msgstr "" #: includes/class-freemius.php:25147, includes/class-fs-plugin-updater.php:1329 msgid "Premium add-on version already installed." msgstr "" #: includes/class-freemius.php:25501 msgid "View paid features" msgstr "" #: includes/class-freemius.php:25805 msgid "Thank you so much for using %s and its add-ons!" msgstr "" #: includes/class-freemius.php:25806 msgid "Thank you so much for using %s!" msgstr "" #: includes/class-freemius.php:25812 msgid "You've already opted-in to our usage-tracking, which helps us keep improving the %s." msgstr "" #: includes/class-freemius.php:25816 msgid "Thank you so much for using our products!" msgstr "" #: includes/class-freemius.php:25817 msgid "You've already opted-in to our usage-tracking, which helps us keep improving them." msgstr "" #: includes/class-freemius.php:25836 msgid "%s and its add-ons" msgstr "" #: includes/class-freemius.php:25845 msgid "Products" msgstr "" #: includes/class-freemius.php:25852, templates/connect.php:322 msgid "Yes" msgstr "" #: includes/class-freemius.php:25853, templates/connect.php:323 msgid "send me security & feature updates, educational content and offers." msgstr "" #: includes/class-freemius.php:25854, templates/connect.php:328 msgid "No" msgstr "" #: includes/class-freemius.php:25856, templates/connect.php:330 msgid "do %sNOT%s send me security & feature updates, educational content and offers." msgstr "" #: includes/class-freemius.php:25866 msgid "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 :-)" msgstr "" #: includes/class-freemius.php:25868, templates/connect.php:337 msgid "Please let us know if you'd like us to contact you for security & feature updates, educational content, and occasional offers:" msgstr "" #: includes/class-freemius.php:26158 msgid "License key is empty." msgstr "" #: includes/class-fs-plugin-updater.php:210, templates/forms/premium-versions-upgrade-handler.php:57 msgid "Renew license" msgstr "" #: includes/class-fs-plugin-updater.php:215, templates/forms/premium-versions-upgrade-handler.php:58 msgid "Buy license" msgstr "" #: includes/class-fs-plugin-updater.php:335, includes/class-fs-plugin-updater.php:368 msgid "There is a %s of %s available." msgstr "" #: includes/class-fs-plugin-updater.php:337, includes/class-fs-plugin-updater.php:373 msgid "new Beta version" msgstr "" #: includes/class-fs-plugin-updater.php:338, includes/class-fs-plugin-updater.php:374 msgid "new version" msgstr "" #: includes/class-fs-plugin-updater.php:397 msgid "Important Upgrade Notice:" msgstr "" #: includes/class-fs-plugin-updater.php:1359 msgid "Installing plugin: %s" msgstr "" #: includes/class-fs-plugin-updater.php:1400 msgid "Unable to connect to the filesystem. Please confirm your credentials." msgstr "" #: includes/class-fs-plugin-updater.php:1582 msgid "The remote plugin package does not contain a folder with the desired slug and renaming did not work." msgstr "" #: includes/fs-plugin-info-dialog.php:542 msgid "Purchase More" msgstr "" #: includes/fs-plugin-info-dialog.php:543, templates/account/partials/addon.php:390 msgctxt "verb" msgid "Purchase" msgstr "" #: includes/fs-plugin-info-dialog.php:547 msgid "Start my free %s" msgstr "" #: includes/fs-plugin-info-dialog.php:745 msgid "Install Free Version Update Now" msgstr "" #: includes/fs-plugin-info-dialog.php:746, templates/account.php:656 msgid "Install Update Now" msgstr "" #: includes/fs-plugin-info-dialog.php:755 msgid "Install Free Version Now" msgstr "" #: includes/fs-plugin-info-dialog.php:756, templates/add-ons.php:323, templates/auto-installation.php:111, templates/account/partials/addon.php:370, templates/account/partials/addon.php:423 msgid "Install Now" msgstr "" #: includes/fs-plugin-info-dialog.php:772 msgctxt "as download latest version" msgid "Download Latest Free Version" msgstr "" #: includes/fs-plugin-info-dialog.php:773, templates/account.php:114, templates/add-ons.php:37, templates/account/partials/addon.php:30 msgctxt "as download latest version" msgid "Download Latest" msgstr "" #: includes/fs-plugin-info-dialog.php:788, templates/add-ons.php:329, templates/account/partials/addon.php:361, templates/account/partials/addon.php:417 msgid "Activate this add-on" msgstr "" #: includes/fs-plugin-info-dialog.php:790, templates/connect.php:444 msgid "Activate Free Version" msgstr "" #: includes/fs-plugin-info-dialog.php:791, templates/account.php:138, templates/add-ons.php:330, templates/account/partials/addon.php:53 msgid "Activate" msgstr "" #: includes/fs-plugin-info-dialog.php:1003 msgctxt "Plugin installer section title" msgid "Description" msgstr "" #: includes/fs-plugin-info-dialog.php:1004 msgctxt "Plugin installer section title" msgid "Installation" msgstr "" #: includes/fs-plugin-info-dialog.php:1005 msgctxt "Plugin installer section title" msgid "FAQ" msgstr "" #: includes/fs-plugin-info-dialog.php:1006, templates/plugin-info/description.php:55 msgid "Screenshots" msgstr "" #: includes/fs-plugin-info-dialog.php:1007 msgctxt "Plugin installer section title" msgid "Changelog" msgstr "" #: includes/fs-plugin-info-dialog.php:1008 msgctxt "Plugin installer section title" msgid "Reviews" msgstr "" #: includes/fs-plugin-info-dialog.php:1009 msgctxt "Plugin installer section title" msgid "Other Notes" msgstr "" #: includes/fs-plugin-info-dialog.php:1024 msgctxt "Plugin installer section title" msgid "Features & Pricing" msgstr "" #: includes/fs-plugin-info-dialog.php:1034 msgid "Plugin Install" msgstr "" #: includes/fs-plugin-info-dialog.php:1106 msgctxt "e.g. Professional Plan" msgid "%s Plan" msgstr "" #: includes/fs-plugin-info-dialog.php:1132 msgctxt "e.g. the best product" msgid "Best" msgstr "" #: includes/fs-plugin-info-dialog.php:1138, includes/fs-plugin-info-dialog.php:1158 msgctxt "as every month" msgid "Monthly" msgstr "" #: includes/fs-plugin-info-dialog.php:1141 msgctxt "as once a year" msgid "Annual" msgstr "" #: includes/fs-plugin-info-dialog.php:1144 msgid "Lifetime" msgstr "" #: includes/fs-plugin-info-dialog.php:1158, includes/fs-plugin-info-dialog.php:1160, includes/fs-plugin-info-dialog.php:1162 msgctxt "e.g. billed monthly" msgid "Billed %s" msgstr "" #: includes/fs-plugin-info-dialog.php:1160 msgctxt "as once a year" msgid "Annually" msgstr "" #: includes/fs-plugin-info-dialog.php:1162 msgctxt "as once a year" msgid "Once" msgstr "" #: includes/fs-plugin-info-dialog.php:1168 msgid "Single Site License" msgstr "" #: includes/fs-plugin-info-dialog.php:1170 msgid "Unlimited Licenses" msgstr "" #: includes/fs-plugin-info-dialog.php:1172 msgid "Up to %s Sites" msgstr "" #: includes/fs-plugin-info-dialog.php:1182, templates/plugin-info/features.php:82 msgctxt "as monthly period" msgid "mo" msgstr "" #: includes/fs-plugin-info-dialog.php:1189, templates/plugin-info/features.php:80 msgctxt "as annual period" msgid "year" msgstr "" #: includes/fs-plugin-info-dialog.php:1243 msgctxt "noun" msgid "Price" msgstr "" #: includes/fs-plugin-info-dialog.php:1291 msgid "Save %s" msgstr "" #: includes/fs-plugin-info-dialog.php:1301 msgid "No commitment for %s - cancel anytime" msgstr "" #: includes/fs-plugin-info-dialog.php:1304 msgid "After your free %s, pay as little as %s" msgstr "" #: includes/fs-plugin-info-dialog.php:1315 msgid "Details" msgstr "" #: includes/fs-plugin-info-dialog.php:1319, templates/account.php:125, templates/debug.php:232, templates/debug.php:269, templates/debug.php:518, templates/account/partials/addon.php:41 msgctxt "product version" msgid "Version" msgstr "" #: includes/fs-plugin-info-dialog.php:1326 msgctxt "as the plugin author" msgid "Author" msgstr "" #: includes/fs-plugin-info-dialog.php:1333 msgid "Last Updated" msgstr "" #: includes/fs-plugin-info-dialog.php:1338, templates/account.php:544 msgctxt "x-ago" msgid "%s ago" msgstr "" #: includes/fs-plugin-info-dialog.php:1347 msgid "Requires WordPress Version" msgstr "" #: includes/fs-plugin-info-dialog.php:1350, includes/fs-plugin-info-dialog.php:1370 msgid "%s or higher" msgstr "" #: includes/fs-plugin-info-dialog.php:1358 msgid "Compatible up to" msgstr "" #: includes/fs-plugin-info-dialog.php:1366 msgid "Requires PHP Version" msgstr "" #: includes/fs-plugin-info-dialog.php:1379 msgid "Downloaded" msgstr "" #: includes/fs-plugin-info-dialog.php:1383 msgid "%s time" msgstr "" #: includes/fs-plugin-info-dialog.php:1385 msgid "%s times" msgstr "" #: includes/fs-plugin-info-dialog.php:1396 msgid "WordPress.org Plugin Page" msgstr "" #: includes/fs-plugin-info-dialog.php:1405 msgid "Plugin Homepage" msgstr "" #: includes/fs-plugin-info-dialog.php:1414, includes/fs-plugin-info-dialog.php:1498 msgid "Donate to this plugin" msgstr "" #: includes/fs-plugin-info-dialog.php:1421 msgid "Average Rating" msgstr "" #: includes/fs-plugin-info-dialog.php:1428 msgid "based on %s" msgstr "" #: includes/fs-plugin-info-dialog.php:1432 msgid "%s rating" msgstr "" #: includes/fs-plugin-info-dialog.php:1434 msgid "%s ratings" msgstr "" #: includes/fs-plugin-info-dialog.php:1449 msgid "%s star" msgstr "" #: includes/fs-plugin-info-dialog.php:1451 msgid "%s stars" msgstr "" #: includes/fs-plugin-info-dialog.php:1463 msgid "Click to see reviews that provided a rating of %s" msgstr "" #: includes/fs-plugin-info-dialog.php:1476 msgid "Contributors" msgstr "" #: includes/fs-plugin-info-dialog.php:1517 msgid "This plugin requires a newer version of PHP." msgstr "" #: includes/fs-plugin-info-dialog.php:1526 msgid "Click here to learn more about updating PHP." msgstr "" #: includes/fs-plugin-info-dialog.php:1540, includes/fs-plugin-info-dialog.php:1542 msgid "Warning" msgstr "" #: includes/fs-plugin-info-dialog.php:1540 msgid "This plugin has not been tested with your current version of WordPress." msgstr "" #: includes/fs-plugin-info-dialog.php:1542 msgid "This plugin has not been marked as compatible with your version of WordPress." msgstr "" #: includes/fs-plugin-info-dialog.php:1561 msgid "Paid add-on must be deployed to Freemius." msgstr "" #: includes/fs-plugin-info-dialog.php:1562 msgid "Add-on must be deployed to WordPress.org or Freemius." msgstr "" #: includes/fs-plugin-info-dialog.php:1583 msgid "Newer Version (%s) Installed" msgstr "" #: includes/fs-plugin-info-dialog.php:1584 msgid "Newer Free Version (%s) Installed" msgstr "" #: includes/fs-plugin-info-dialog.php:1591 msgid "Latest Version Installed" msgstr "" #: includes/fs-plugin-info-dialog.php:1592 msgid "Latest Free Version Installed" msgstr "" #: templates/account.php:115, templates/forms/subscription-cancellation.php:96, templates/account/partials/addon.php:31, templates/account/partials/site.php:313 msgid "Downgrading your plan" msgstr "" #: templates/account.php:116, templates/forms/subscription-cancellation.php:97, templates/account/partials/addon.php:32, templates/account/partials/site.php:314 msgid "Cancelling the subscription" msgstr "" #. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' #: templates/account.php:118, templates/forms/subscription-cancellation.php:99, templates/account/partials/site.php:316 msgid "%1$s will immediately stop all future recurring payments and your %2$s plan license will expire in %3$s." msgstr "" #: templates/account.php:119, templates/forms/subscription-cancellation.php:100, templates/account/partials/addon.php:35, templates/account/partials/site.php:317 msgid "Please note that we will not be able to grandfather outdated pricing for renewals/new subscriptions after a cancellation. If you choose to renew the subscription manually in the future, after a price increase, which typically occurs once a year, you will be charged the updated price." msgstr "" #: templates/account.php:120, templates/forms/subscription-cancellation.php:106, templates/account/partials/addon.php:36 msgid "Cancelling the trial will immediately block access to all premium features. Are you sure?" msgstr "" #: templates/account.php:121, templates/forms/subscription-cancellation.php:101, templates/account/partials/addon.php:37, templates/account/partials/site.php:318 msgid "You can still enjoy all %s features but you will not have access to %s security & feature updates, nor support." msgstr "" #: templates/account.php:122, templates/forms/subscription-cancellation.php:102, templates/account/partials/addon.php:38, templates/account/partials/site.php:319 msgid "Once your license expires you can still use the Free version but you will NOT have access to the %s features." msgstr "" #. translators: %s: Plan title (e.g. "Professional") #: templates/account.php:124, templates/account/partials/activate-license-button.php:31, templates/account/partials/addon.php:40 msgid "Activate %s Plan" msgstr "" #. translators: %s: Time period (e.g. Auto renews in "2 months") #: templates/account.php:127, templates/account/partials/addon.php:43, templates/account/partials/site.php:293 msgid "Auto renews in %s" msgstr "" #. translators: %s: Time period (e.g. Expires in "2 months") #: templates/account.php:129, templates/account/partials/addon.php:45, templates/account/partials/site.php:295 msgid "Expires in %s" msgstr "" #: templates/account.php:130 msgctxt "as synchronize license" msgid "Sync License" msgstr "" #: templates/account.php:131, templates/account/partials/addon.php:46 msgid "Cancel Trial" msgstr "" #: templates/account.php:132, templates/account/partials/addon.php:47 msgid "Change Plan" msgstr "" #: templates/account.php:133, templates/account/partials/addon.php:48 msgctxt "verb" msgid "Upgrade" msgstr "" #: templates/account.php:135, templates/account/partials/addon.php:50, templates/account/partials/site.php:320 msgctxt "verb" msgid "Downgrade" msgstr "" #: templates/account.php:137, templates/add-ons.php:246, templates/plugin-info/features.php:72, templates/account/partials/addon.php:52, templates/account/partials/site.php:33 msgid "Free" msgstr "" #: templates/account.php:139, templates/debug.php:412, includes/customizer/class-fs-customizer-upsell-control.php:110, templates/account/partials/addon.php:54 msgctxt "as product pricing plan" msgid "Plan" msgstr "" #: templates/account.php:140 msgid "Bundle Plan" msgstr "" #: templates/account.php:272 msgid "Free Trial" msgstr "" #: templates/account.php:283 msgid "Account Details" msgstr "" #: templates/account.php:290, templates/forms/data-debug-mode.php:33 msgid "Start Debug" msgstr "" #: templates/account.php:292 msgid "Stop Debug" msgstr "" #: templates/account.php:299 msgid "Billing & Invoices" msgstr "" #: templates/account.php:322, templates/account/partials/addon.php:236, templates/account/partials/deactivate-license-button.php:35 msgid "Deactivate License" msgstr "" #: templates/account.php:345, templates/forms/subscription-cancellation.php:125 msgid "Are you sure you want to proceed?" msgstr "" #: templates/account.php:345, templates/account/partials/addon.php:260 msgid "Cancel Subscription" msgstr "" #: templates/account.php:374, templates/account/partials/addon.php:345 msgctxt "as synchronize" msgid "Sync" msgstr "" #: templates/account.php:389, templates/debug.php:575 msgid "Name" msgstr "" #: templates/account.php:395, templates/debug.php:576 msgid "Email" msgstr "" #: templates/account.php:402, templates/debug.php:410, templates/debug.php:625 msgid "User ID" msgstr "" #: templates/account.php:420, templates/account.php:738, templates/account.php:789, templates/debug.php:267, templates/debug.php:404, templates/debug.php:515, templates/debug.php:574, templates/debug.php:623, templates/debug.php:702, templates/account/payments.php:35, templates/debug/logger.php:21 msgid "ID" msgstr "" #: templates/account.php:427 msgid "Site ID" msgstr "" #: templates/account.php:430 msgid "No ID" msgstr "" #: templates/account.php:435, templates/debug.php:274, templates/debug.php:413, templates/debug.php:519, templates/debug.php:578, templates/account/partials/site.php:228 msgid "Public Key" msgstr "" #: templates/account.php:441, templates/debug.php:414, templates/debug.php:520, templates/debug.php:579, templates/account/partials/site.php:241 msgid "Secret Key" msgstr "" #: templates/account.php:444 msgctxt "as secret encryption key missing" msgid "No Secret" msgstr "" #: templates/account.php:471, templates/account/partials/site.php:120, templates/account/partials/site.php:122 msgid "Trial" msgstr "" #: templates/account.php:498, templates/debug.php:631, templates/account/partials/site.php:262 msgid "License Key" msgstr "" #: templates/account.php:529 msgid "Join the Beta program" msgstr "" #: templates/account.php:535 msgid "not verified" msgstr "" #: templates/account.php:544, templates/account/partials/addon.php:195 msgid "Expired" msgstr "" #: templates/account.php:602 msgid "Premium version" msgstr "" #: templates/account.php:604 msgid "Free version" msgstr "" #: templates/account.php:616 msgid "Verify Email" msgstr "" #: templates/account.php:630 msgid "Download %s Version" msgstr "" #: templates/account.php:646 msgid "Download Paid Version" msgstr "" #: templates/account.php:664, templates/account.php:927, templates/account/partials/site.php:250, templates/account/partials/site.php:272 msgctxt "verb" msgid "Show" msgstr "" #: templates/account.php:680 msgid "What is your %s?" msgstr "" #: templates/account.php:688, templates/account/billing.php:21 msgctxt "verb" msgid "Edit" msgstr "" #: templates/account.php:693, templates/forms/user-change.php:27 msgid "Change User" msgstr "" #: templates/account.php:717 msgid "Sites" msgstr "" #: templates/account.php:730 msgid "Search by address" msgstr "" #: templates/account.php:739, templates/debug.php:407 msgid "Address" msgstr "" #: templates/account.php:740 msgid "License" msgstr "" #: templates/account.php:741 msgid "Plan" msgstr "" #: templates/account.php:792 msgctxt "as software license" msgid "License" msgstr "" #: templates/account.php:921 msgctxt "verb" msgid "Hide" msgstr "" #: templates/account.php:943, templates/forms/data-debug-mode.php:31, templates/forms/deactivation/form.php:358, templates/forms/deactivation/form.php:389 msgid "Processing" msgstr "" #: templates/account.php:946 msgid "Get updates for bleeding edge Beta versions of %s." msgstr "" #: templates/account.php:1004 msgid "Cancelling %s" msgstr "" #: templates/account.php:1004, templates/account.php:1021, templates/forms/subscription-cancellation.php:27, templates/forms/deactivation/form.php:178 msgid "trial" msgstr "" #: templates/account.php:1019, templates/forms/deactivation/form.php:195 msgid "Cancelling %s..." msgstr "" #: templates/account.php:1022, templates/forms/subscription-cancellation.php:28, templates/forms/deactivation/form.php:179 msgid "subscription" msgstr "" #: templates/account.php:1036 msgid "Deactivating your license will block all premium features, but will enable activating the license on another site. Are you sure you want to proceed?" msgstr "" #: templates/account.php:1110 msgid "Disabling white-label mode" msgstr "" #: templates/account.php:1111 msgid "Enabling white-label mode" msgstr "" #: templates/add-ons.php:38 msgid "View details" msgstr "" #: templates/add-ons.php:48 msgid "Add Ons for %s" msgstr "" #: templates/add-ons.php:58 msgid "We couldn't load the add-ons list. It's probably an issue on our side, please try to come back in few minutes." msgstr "" #: templates/add-ons.php:229 msgctxt "active add-on" msgid "Active" msgstr "" #: templates/add-ons.php:230 msgctxt "installed add-on" msgid "Installed" msgstr "" #: templates/admin-notice.php:13, templates/forms/license-activation.php:243, templates/forms/resend-key.php:80 msgctxt "as close a window" msgid "Dismiss" msgstr "" #: templates/auto-installation.php:45 msgid "%s sec" msgstr "" #: templates/auto-installation.php:83 msgid "Automatic Installation" msgstr "" #: templates/auto-installation.php:93 msgid "An automated download and installation of %s (paid version) from %s will start in %s. If you would like to do it manually - click the cancellation button now." msgstr "" #: templates/auto-installation.php:104 msgid "The installation process has started and may take a few minutes to complete. Please wait until it is done - do not refresh this page." msgstr "" #: templates/auto-installation.php:109 msgid "Cancel Installation" msgstr "" #: templates/checkout.php:181 msgid "Checkout" msgstr "" #: templates/checkout.php:181 msgid "PCI compliant" msgstr "" #. translators: %s: name (e.g. Hey John,) #: templates/connect.php:127 msgctxt "greeting" msgid "Hey %s," msgstr "" #: templates/connect.php:187 msgid "Never miss an important update" msgstr "" #: templates/connect.php:195 msgid "Thank you for updating to %1$s v%2$s!" msgstr "" #: templates/connect.php:205 msgid "Allow & Continue" msgstr "" #: templates/connect.php:209 msgid "Re-send activation email" msgstr "" #: templates/connect.php:213 msgid "Thanks %s!" msgstr "" #: templates/connect.php:214 msgid "You should receive a confirmation email for %s to your mailbox at %s. Please make sure you click the button in that email to %s." msgstr "" #: templates/connect.php:225 msgid "Welcome to %s! To get started, please enter your license key:" msgstr "" #: templates/connect.php:236 msgid "Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info. This will help us make the %s more compatible with your site and better at doing what you need it to." msgstr "" #. translators: %s: module type (plugin, theme, or add-on) #: templates/connect.php:245 msgid "We have introduced this opt-in so you never miss an important update and help us make the %s more compatible with your site and better at doing what you need it to." msgstr "" #: templates/connect.php:247 msgid "Opt in to get email notifications for security & feature updates, educational content, and occasional offers, and to share some basic WordPress environment info." msgstr "" #: templates/connect.php:250 msgid "If you skip this, that's okay! %1$s will still work just fine." msgstr "" #: templates/connect.php:280 msgid "We're excited to introduce the Freemius network-level integration." msgstr "" #: templates/connect.php:283 msgid "During the update process we detected %d site(s) that are still pending license activation." msgstr "" #: templates/connect.php:285 msgid "If you'd like to use the %s on those sites, please enter your license key below and click the activation button." msgstr "" #: templates/connect.php:287 msgid "%s's paid features" msgstr "" #: templates/connect.php:292 msgid "Alternatively, you can skip it for now and activate the license later, in your %s's network-level Account page." msgstr "" #: templates/connect.php:294 msgid "During the update process we detected %s site(s) in the network that are still pending your attention." msgstr "" #: templates/connect.php:303, templates/forms/data-debug-mode.php:35, templates/forms/license-activation.php:42 msgid "License key" msgstr "" #: templates/connect.php:306, templates/forms/license-activation.php:22 msgid "Can't find your license key?" msgstr "" #: templates/connect.php:369, templates/connect.php:693, templates/forms/deactivation/retry-skip.php:20 msgctxt "verb" msgid "Skip" msgstr "" #: templates/connect.php:372 msgid "Delegate to Site Admins" msgstr "" #: templates/connect.php:372 msgid "If you click it, this decision will be delegated to the sites administrators." msgstr "" #: templates/connect.php:399 msgid "License issues?" msgstr "" #: templates/connect.php:423 msgid "For delivery of security & feature updates, and license management, %s needs to" msgstr "" #: templates/connect.php:428 msgid "This will allow %s to" msgstr "" #: templates/connect.php:443 msgid "Don't have a license key?" msgstr "" #: templates/connect.php:446 msgid "Have a license key?" msgstr "" #: templates/connect.php:454 msgid "Freemius is our licensing and software updates engine" msgstr "" #: templates/connect.php:457 msgid "Privacy Policy" msgstr "" #: templates/connect.php:459 msgid "License Agreement" msgstr "" #: templates/connect.php:459 msgid "Terms of Service" msgstr "" #: templates/connect.php:879 msgctxt "as in the process of sending an email" msgid "Sending email" msgstr "" #: templates/connect.php:880 msgctxt "as activating plugin" msgid "Activating" msgstr "" #: templates/contact.php:78 msgid "Contact" msgstr "" #: templates/debug.php:17 msgctxt "as turned off" msgid "Off" msgstr "" #: templates/debug.php:18 msgctxt "as turned on" msgid "On" msgstr "" #: templates/debug.php:24 msgid "SDK" msgstr "" #: templates/debug.php:28 msgctxt "as code debugging" msgid "Debugging" msgstr "" #: templates/debug.php:58, templates/debug.php:279, templates/debug.php:415, templates/debug.php:580 msgid "Actions" msgstr "" #: templates/debug.php:68 msgid "Are you sure you want to delete all Freemius data?" msgstr "" #: templates/debug.php:68 msgid "Delete All Accounts" msgstr "" #: templates/debug.php:75 msgid "Clear API Cache" msgstr "" #: templates/debug.php:83 msgid "Clear Updates Transients" msgstr "" #: templates/debug.php:92 msgid "Reset Deactivation Snoozing" msgstr "" #: templates/debug.php:100 msgid "Sync Data From Server" msgstr "" #: templates/debug.php:109 msgid "Migrate Options to Network" msgstr "" #: templates/debug.php:114 msgid "Load DB Option" msgstr "" #: templates/debug.php:117 msgid "Set DB Option" msgstr "" #: templates/debug.php:211 msgid "Key" msgstr "" #: templates/debug.php:212 msgid "Value" msgstr "" #: templates/debug.php:228 msgctxt "as software development kit versions" msgid "SDK Versions" msgstr "" #: templates/debug.php:233 msgid "SDK Path" msgstr "" #: templates/debug.php:234, templates/debug.php:273 msgid "Module Path" msgstr "" #: templates/debug.php:235 msgid "Is Active" msgstr "" #: templates/debug.php:263, templates/debug/plugins-themes-sync.php:35 msgid "Plugins" msgstr "" #: templates/debug.php:263, templates/debug/plugins-themes-sync.php:56 msgid "Themes" msgstr "" #: templates/debug.php:268, templates/debug.php:409, templates/debug.php:517, templates/debug/scheduled-crons.php:80 msgid "Slug" msgstr "" #: templates/debug.php:270, templates/debug.php:516 msgid "Title" msgstr "" #: templates/debug.php:271 msgctxt "as application program interface" msgid "API" msgstr "" #: templates/debug.php:272 msgid "Freemius State" msgstr "" #: templates/debug.php:276 msgid "Network Blog" msgstr "" #: templates/debug.php:277 msgid "Network User" msgstr "" #: templates/debug.php:323 msgctxt "as connection was successful" msgid "Connected" msgstr "" #: templates/debug.php:325 msgctxt "as connection blocked" msgid "Blocked" msgstr "" #: templates/debug.php:326 msgctxt "API connectivity state is unknown" msgid "Unknown" msgstr "" #: templates/debug.php:362 msgid "Simulate Trial Promotion" msgstr "" #: templates/debug.php:374 msgid "Simulate Network Upgrade" msgstr "" #: templates/debug.php:398 msgid "%s Installs" msgstr "" #: templates/debug.php:400 msgctxt "like websites" msgid "Sites" msgstr "" #: templates/debug.php:406, templates/account/partials/site.php:156 msgid "Blog ID" msgstr "" #: templates/debug.php:411 msgid "License ID" msgstr "" #: templates/debug.php:497, templates/debug.php:603, templates/account/partials/addon.php:440 msgctxt "verb" msgid "Delete" msgstr "" #: templates/debug.php:511 msgid "Add Ons of module %s" msgstr "" #: templates/debug.php:570 msgid "Users" msgstr "" #: templates/debug.php:577 msgid "Verified" msgstr "" #: templates/debug.php:619 msgid "%s Licenses" msgstr "" #: templates/debug.php:624 msgid "Plugin ID" msgstr "" #: templates/debug.php:626 msgid "Plan ID" msgstr "" #: templates/debug.php:627 msgid "Quota" msgstr "" #: templates/debug.php:628 msgid "Activated" msgstr "" #: templates/debug.php:629 msgid "Blocking" msgstr "" #: templates/debug.php:630, templates/debug.php:701, templates/debug/logger.php:22 msgid "Type" msgstr "" #: templates/debug.php:632 msgctxt "as expiration date" msgid "Expiration" msgstr "" #: templates/debug.php:660 msgid "Debug Log" msgstr "" #: templates/debug.php:664 msgid "All Types" msgstr "" #: templates/debug.php:671 msgid "All Requests" msgstr "" #: templates/debug.php:676, templates/debug.php:705, templates/debug/logger.php:25 msgid "File" msgstr "" #: templates/debug.php:677, templates/debug.php:703, templates/debug/logger.php:23 msgid "Function" msgstr "" #: templates/debug.php:678 msgid "Process ID" msgstr "" #: templates/debug.php:679 msgid "Logger" msgstr "" #: templates/debug.php:680, templates/debug.php:704, templates/debug/logger.php:24 msgid "Message" msgstr "" #: templates/debug.php:682 msgid "Filter" msgstr "" #: templates/debug.php:690 msgid "Download" msgstr "" #: templates/debug.php:706, templates/debug/logger.php:26 msgid "Timestamp" msgstr "" #: templates/secure-https-header.php:28 msgid "Secure HTTPS %s page, running from an external domain" msgstr "" #: includes/customizer/class-fs-customizer-support-section.php:55, templates/plugin-info/features.php:43 msgid "Support" msgstr "" #: includes/debug/class-fs-debug-bar-panel.php:48, templates/debug/api-calls.php:54, templates/debug/logger.php:62 msgctxt "milliseconds" msgid "ms" msgstr "" #: includes/debug/debug-bar-start.php:41 msgid "Freemius API" msgstr "" #: includes/debug/debug-bar-start.php:42 msgid "Requests" msgstr "" #: includes/managers/class-fs-clone-manager.php:839 msgid "Invalid clone resolution action." msgstr "" #: includes/managers/class-fs-clone-manager.php:1024 msgid "products" msgstr "" #: includes/managers/class-fs-clone-manager.php:1205 msgid "%1$s has been placed into safe mode because we noticed that %2$s is an exact copy of %3$s." msgstr "" #: includes/managers/class-fs-clone-manager.php:1211 msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of %3$s:%1$s" msgstr "" #: includes/managers/class-fs-clone-manager.php:1212 msgid "The products below have been placed into safe mode because we noticed that %2$s is an exact copy of these sites:%3$s%1$s" msgstr "" #: includes/managers/class-fs-clone-manager.php:1238 msgid "the above-mentioned sites" msgstr "" #: includes/managers/class-fs-clone-manager.php:1251 msgid "Is %2$s a duplicate of %4$s?" msgstr "" #: includes/managers/class-fs-clone-manager.php:1252 msgid "Yes, %2$s is a duplicate of %4$s for the purpose of testing, staging, or development." msgstr "" #: includes/managers/class-fs-clone-manager.php:1257 msgid "Long-Term Duplicate" msgstr "" #: includes/managers/class-fs-clone-manager.php:1262 msgid "Duplicate Website" msgstr "" #: includes/managers/class-fs-clone-manager.php:1268 msgid "Is %2$s the new home of %4$s?" msgstr "" #: includes/managers/class-fs-clone-manager.php:1270 msgid "Yes, %%2$s is replacing %%4$s. I would like to migrate my %s from %%4$s to %%2$s." msgstr "" #: includes/managers/class-fs-clone-manager.php:1271, templates/forms/subscription-cancellation.php:52 msgid "license" msgstr "" #: includes/managers/class-fs-clone-manager.php:1271 msgid "data" msgstr "" #: includes/managers/class-fs-clone-manager.php:1277 msgid "Migrate License" msgstr "" #: includes/managers/class-fs-clone-manager.php:1278 msgid "Migrate" msgstr "" #: includes/managers/class-fs-clone-manager.php:1284 msgid "Is %2$s a new website?" msgstr "" #: includes/managers/class-fs-clone-manager.php:1285 msgid "Yes, %2$s is a new and different website that is separate from %4$s." msgstr "" #: includes/managers/class-fs-clone-manager.php:1287 msgid "It requires license activation." msgstr "" #: includes/managers/class-fs-clone-manager.php:1294 msgid "New Website" msgstr "" #: includes/managers/class-fs-clone-manager.php:1319 msgctxt "Clone resolution admin notice products list label" msgid "Products" msgstr "" #: includes/managers/class-fs-clone-manager.php:1408 msgid "You marked this website, %s, as a temporary duplicate of %s." msgstr "" #: includes/managers/class-fs-clone-manager.php:1409 msgid "You marked this website, %s, as a temporary duplicate of these sites" msgstr "" #: includes/managers/class-fs-clone-manager.php:1423 msgid "%s automatic security & feature updates and paid functionality will keep working without interruptions until %s (or when your license expires, whatever comes first)." msgstr "" #: includes/managers/class-fs-clone-manager.php:1426 msgctxt "\"The \", e.g.: \"The plugin\"" msgid "The %s's" msgstr "" #: includes/managers/class-fs-clone-manager.php:1429 msgid "The following products'" msgstr "" #: includes/managers/class-fs-clone-manager.php:1437 msgid "If this is a long term duplicate, to keep automatic updates and paid functionality after %s, please %s." msgstr "" #: includes/managers/class-fs-clone-manager.php:1439 msgid "activate a license here" msgstr "" #: includes/managers/class-fs-permission-manager.php:191 msgid "View Basic Website Info" msgstr "" #: includes/managers/class-fs-permission-manager.php:192 msgid "Homepage URL & title, WP & PHP versions, and site language" msgstr "" #: includes/managers/class-fs-permission-manager.php:195 msgid "To provide additional functionality that's relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the %s should be translated and tailored to." msgstr "" #: includes/managers/class-fs-permission-manager.php:207 msgid "View Basic %s Info" msgstr "" #: includes/managers/class-fs-permission-manager.php:210 msgid "Current %s & SDK versions, and if active or uninstalled" msgstr "" #: includes/managers/class-fs-permission-manager.php:261 msgid "View License Essentials" msgstr "" #: includes/managers/class-fs-permission-manager.php:262 msgstr "" #: includes/managers/class-fs-permission-manager.php:272 msgid "To let you manage & control where the license is activated and ensure %s security & feature updates are only delivered to websites you authorize." msgstr "" #: includes/managers/class-fs-permission-manager.php:284 msgid "View %s State" msgstr "" #: includes/managers/class-fs-permission-manager.php:287 msgid "Is active, deactivated, or uninstalled" msgstr "" #: includes/managers/class-fs-permission-manager.php:290 msgid "So you can reuse the license when the %s is no longer active." msgstr "" #: includes/managers/class-fs-permission-manager.php:326 msgid "View Diagnostic Info" msgstr "" #: includes/managers/class-fs-permission-manager.php:326, includes/managers/class-fs-permission-manager.php:363 msgid "optional" msgstr "" #: includes/managers/class-fs-permission-manager.php:327 msgid "WordPress & PHP versions, site language & title" msgstr "" #: includes/managers/class-fs-permission-manager.php:330 msgid "To avoid breaking your website due to WordPress or PHP version incompatibilities, and recognize which languages & regions the %s should be translated and tailored to." msgstr "" #: includes/managers/class-fs-permission-manager.php:363 msgid "View Plugins & Themes List" msgstr "" #: includes/managers/class-fs-permission-manager.php:364 msgid "Names, slugs, versions, and if active or not" msgstr "" #: includes/managers/class-fs-permission-manager.php:365 msgid "To ensure compatibility and avoid conflicts with your installed plugins and themes." msgstr "" #: includes/managers/class-fs-permission-manager.php:382 msgid "View Basic Profile Info" msgstr "" #: includes/managers/class-fs-permission-manager.php:383 msgid "Your WordPress user's: first & last name, and email address" msgstr "" #: includes/managers/class-fs-permission-manager.php:384 msgid "Never miss important updates, get security warnings before they become public knowledge, and receive notifications about special offers and awesome new features." msgstr "" #: includes/managers/class-fs-permission-manager.php:405 msgid "Newsletter" msgstr "" #: includes/managers/class-fs-permission-manager.php:406 msgid "Updates, announcements, marketing, no spam" msgstr "" #: templates/account/billing.php:22 msgctxt "verb" msgid "Update" msgstr "" #: templates/account/billing.php:33 msgid "Billing" msgstr "" #: templates/account/billing.php:38, templates/account/billing.php:38 msgid "Business name" msgstr "" #: templates/account/billing.php:39, templates/account/billing.php:39 msgid "Tax / VAT ID" msgstr "" #: templates/account/billing.php:42, templates/account/billing.php:42, templates/account/billing.php:43, templates/account/billing.php:43 msgid "Address Line %d" msgstr "" #: templates/account/billing.php:46, templates/account/billing.php:46 msgid "City" msgstr "" #: templates/account/billing.php:46, templates/account/billing.php:46 msgid "Town" msgstr "" #: templates/account/billing.php:47, templates/account/billing.php:47 msgid "ZIP / Postal Code" msgstr "" #: templates/account/billing.php:302 msgid "Country" msgstr "" #: templates/account/billing.php:304 msgid "Select Country" msgstr "" #: templates/account/billing.php:311, templates/account/billing.php:312 msgid "State" msgstr "" #: templates/account/billing.php:311, templates/account/billing.php:312 msgid "Province" msgstr "" #: templates/account/payments.php:29 msgid "Payments" msgstr "" #: templates/account/payments.php:36 msgid "Date" msgstr "" #: templates/account/payments.php:37 msgid "Amount" msgstr "" #: templates/account/payments.php:38, templates/account/payments.php:50 msgid "Invoice" msgstr "" #: templates/connect/permissions-group.php:31, templates/forms/optout.php:26, templates/js/permissions.php:78 msgctxt "verb" msgid "Opt Out" msgstr "" #: templates/connect/permissions-group.php:32, templates/js/permissions.php:77 msgctxt "verb" msgid "Opt In" msgstr "" #: templates/debug/api-calls.php:56 msgid "API" msgstr "" #: templates/debug/api-calls.php:68 msgid "Method" msgstr "" #: templates/debug/api-calls.php:69 msgid "Code" msgstr "" #: templates/debug/api-calls.php:70 msgid "Length" msgstr "" #: templates/debug/api-calls.php:71 msgctxt "as file/folder path" msgid "Path" msgstr "" #: templates/debug/api-calls.php:73 msgid "Body" msgstr "" #: templates/debug/api-calls.php:75 msgid "Result" msgstr "" #: templates/debug/api-calls.php:76 msgid "Start" msgstr "" #: templates/debug/api-calls.php:77 msgid "End" msgstr "" #: templates/debug/logger.php:15 msgid "Log" msgstr "" #. translators: %s: time period (e.g. In "2 hours") #: templates/debug/plugins-themes-sync.php:18, templates/debug/scheduled-crons.php:91 msgid "In %s" msgstr "" #. translators: %s: time period (e.g. "2 hours" ago) #: templates/debug/plugins-themes-sync.php:20, templates/debug/scheduled-crons.php:93 msgid "%s ago" msgstr "" #: templates/debug/plugins-themes-sync.php:21, templates/debug/scheduled-crons.php:74 msgctxt "seconds" msgid "sec" msgstr "" #: templates/debug/plugins-themes-sync.php:23 msgid "Plugins & Themes Sync" msgstr "" #: templates/debug/plugins-themes-sync.php:28 msgid "Total" msgstr "" #: templates/debug/plugins-themes-sync.php:29, templates/debug/scheduled-crons.php:84 msgid "Last" msgstr "" #: templates/debug/scheduled-crons.php:76 msgid "Scheduled Crons" msgstr "" #: templates/debug/scheduled-crons.php:81 msgid "Module" msgstr "" #: templates/debug/scheduled-crons.php:82 msgid "Module Type" msgstr "" #: templates/debug/scheduled-crons.php:83 msgid "Cron Type" msgstr "" #: templates/debug/scheduled-crons.php:85 msgid "Next" msgstr "" #: templates/forms/affiliation.php:83 msgid "Non-expiring" msgstr "" #: templates/forms/affiliation.php:86 msgid "Apply to become an affiliate" msgstr "" #: templates/forms/affiliation.php:108 msgid "Your affiliate application for %s has been accepted! Log in to your affiliate area at: %s." msgstr "" #: templates/forms/affiliation.php:123 msgid "Thank you for applying for our affiliate program, we'll review your details during the next 14 days and will get back to you with further information." msgstr "" #: templates/forms/affiliation.php:126 msgid "Your affiliation account was temporarily suspended." msgstr "" #: templates/forms/affiliation.php:129 msgid "Thank you for applying for our affiliate program, unfortunately, we've decided at this point to reject your application. Please try again in 30 days." msgstr "" #: templates/forms/affiliation.php:132 msgid "Due to violation of our affiliation terms, we decided to temporarily block your affiliation account. If you have any questions, please contact support." msgstr "" #: templates/forms/affiliation.php:145 msgid "Like the %s? Become our ambassador and earn cash ;-)" msgstr "" #: templates/forms/affiliation.php:146 msgid "Refer new customers to our %s and earn %s commission on each successful sale you refer!" msgstr "" #: templates/forms/affiliation.php:149 msgid "Program Summary" msgstr "" #: templates/forms/affiliation.php:151 msgid "%s commission when a customer purchases a new license." msgstr "" #: templates/forms/affiliation.php:153 msgid "Get commission for automated subscription renewals." msgstr "" #: templates/forms/affiliation.php:156 msgid "%s tracking cookie after the first visit to maximize earnings potential." msgstr "" #: templates/forms/affiliation.php:159 msgid "Unlimited commissions." msgstr "" #: templates/forms/affiliation.php:161 msgid "%s minimum payout amount." msgstr "" #: templates/forms/affiliation.php:162 msgid "Payouts are in USD and processed monthly via PayPal." msgstr "" #: templates/forms/affiliation.php:163 msgid "As we reserve 30 days for potential refunds, we only pay commissions that are older than 30 days." msgstr "" #: templates/forms/affiliation.php:166 msgid "Affiliate" msgstr "" #: templates/forms/affiliation.php:169, templates/forms/resend-key.php:23 msgid "Email address" msgstr "" #: templates/forms/affiliation.php:173 msgid "Full name" msgstr "" #: templates/forms/affiliation.php:177 msgid "PayPal account email address" msgstr "" #: templates/forms/affiliation.php:181 msgid "Where are you going to promote the %s?" msgstr "" #: templates/forms/affiliation.php:183 msgid "Enter the domain of your website or other websites from where you plan to promote the %s." msgstr "" #: templates/forms/affiliation.php:185 msgid "Add another domain" msgstr "" #: templates/forms/affiliation.php:189 msgid "Extra Domains" msgstr "" #: templates/forms/affiliation.php:190 msgid "Extra domains where you will be marketing the product from." msgstr "" #: templates/forms/affiliation.php:200 msgid "Promotion methods" msgstr "" #: templates/forms/affiliation.php:203 msgid "Social media (Facebook, Twitter, etc.)" msgstr "" #: templates/forms/affiliation.php:207 msgid "Mobile apps" msgstr "" #: templates/forms/affiliation.php:211 msgid "Website, email, and social media statistics (optional)" msgstr "" #: templates/forms/affiliation.php:214 msgid "Please feel free to provide any relevant website or social media statistics, e.g. monthly unique site visits, number of email subscribers, followers, etc. (we will keep this information confidential)." msgstr "" #: templates/forms/affiliation.php:218 msgid "How will you promote us?" msgstr "" #: templates/forms/affiliation.php:221 msgid "Please provide details on how you intend to promote %s (please be as specific as possible)." msgstr "" #: templates/forms/affiliation.php:233, templates/forms/resend-key.php:22, templates/account/partials/disconnect-button.php:92 msgid "Cancel" msgstr "" #: templates/forms/affiliation.php:235 msgid "Become an affiliate" msgstr "" #: templates/forms/data-debug-mode.php:25 msgid "Please enter the license key to enable the debug mode:" msgstr "" #: templates/forms/data-debug-mode.php:27 msgid "To enter the debug mode, please enter the secret key of the license owner (UserID = %d), which you can find in your \"My Profile\" section of your User Dashboard:" msgstr "" #: templates/forms/data-debug-mode.php:32 msgid "Submit" msgstr "" #: templates/forms/data-debug-mode.php:36 msgid "User key" msgstr "" #: templates/forms/email-address-update.php:32 msgid "Email address update" msgstr "" #: templates/forms/email-address-update.php:33, templates/forms/user-change.php:81 msgctxt "close window" msgid "Dismiss" msgstr "" #: templates/forms/email-address-update.php:38 msgid "Enter the new email address" msgstr "" #: templates/forms/email-address-update.php:42 msgid "Are both %s and %s your email addresses?" msgstr "" #: templates/forms/email-address-update.php:50 msgid "Yes - both addresses are mine" msgstr "" #: templates/forms/email-address-update.php:57 msgid "%s is my client's email address" msgstr "" #: templates/forms/email-address-update.php:66 msgid "%s is my email address" msgstr "" #: templates/forms/email-address-update.php:75 msgid "Would you like to merge %s into %s?" msgstr "" #: templates/forms/email-address-update.php:84 msgid "Yes - move all my data and assets from %s to %s" msgstr "" #: templates/forms/email-address-update.php:94 msgid "No - only move this site's data to %s" msgstr "" #: templates/forms/email-address-update.php:292, templates/forms/email-address-update.php:298 msgid "Update" msgstr "" #: templates/forms/license-activation.php:23 msgid "Please enter the license key that you received in the email right after the purchase:" msgstr "" #: templates/forms/license-activation.php:28 msgid "Update License" msgstr "" #: templates/forms/license-activation.php:34 msgid "The %1$s will be periodically sending essential license data to %2$s to check for security and feature updates, and verify the validity of your license." msgstr "" #: templates/forms/license-activation.php:39 msgid "Agree & Activate License" msgstr "" #: templates/forms/license-activation.php:204 msgid "Associate with the license owner's account." msgstr "" #: templates/forms/optout.php:44 msgid "Communication" msgstr "" #: templates/forms/optout.php:56 msgid "Stay Connected" msgstr "" #: templates/forms/optout.php:61 msgid "Diagnostic Info" msgstr "" #: templates/forms/optout.php:77 msgid "Keep Sharing" msgstr "" #: templates/forms/optout.php:82 msgid "Extensions" msgstr "" #: templates/forms/optout.php:104 msgid "Keep automatic updates" msgstr "" #: templates/forms/premium-versions-upgrade-handler.php:40 msgid "There is a new version of %s available." msgstr "" #: templates/forms/premium-versions-upgrade-handler.php:41 msgid " %s to access version %s security & feature updates, and support." msgstr "" #: templates/forms/premium-versions-upgrade-handler.php:54 msgid "New Version Available" msgstr "" #: templates/forms/premium-versions-upgrade-handler.php:75 msgctxt "close a window" msgid "Dismiss" msgstr "" #: templates/forms/resend-key.php:21 msgid "Send License Key" msgstr "" #: templates/forms/resend-key.php:58 msgid "Enter the email address you've used during the purchase and we will resend you the license key." msgstr "" #: templates/forms/resend-key.php:59 msgid "Enter the email address you've used for the upgrade below and we will resend you the license key." msgstr "" #: templates/forms/subscription-cancellation.php:37 msgid "Deactivating or uninstalling the %s will automatically disable the license, which you'll be able to use on another site." msgstr "" #: templates/forms/subscription-cancellation.php:47 msgid "In case you are NOT planning on using this %s on this site (or any other site) - would you like to cancel the %s as well?" msgstr "" #: templates/forms/subscription-cancellation.php:57 msgid "Cancel %s - I no longer need any security & feature updates, nor support for %s because I'm not planning to use the %s on this, or any other site." msgstr "" #: templates/forms/subscription-cancellation.php:68 msgid "Don't cancel %s - I'm still interested in getting security & feature updates, as well as be able to contact support." msgstr "" #: templates/forms/subscription-cancellation.php:103 msgid "Once your license expires you will no longer be able to use the %s, unless you activate it again with a valid premium license." msgstr "" #: templates/forms/subscription-cancellation.php:136 msgid "Cancel %s?" msgstr "" #: templates/forms/subscription-cancellation.php:143 msgid "Proceed" msgstr "" #: templates/forms/subscription-cancellation.php:191, templates/forms/deactivation/form.php:216 msgid "Cancel %s & Proceed" msgstr "" #: templates/forms/trial-start.php:22 msgid "You are 1-click away from starting your %1$s-day free trial of the %2$s plan." msgstr "" #: templates/forms/trial-start.php:28 msgid "For compliance with the WordPress.org guidelines, before we start the trial we ask that you opt in with your user and non-sensitive site information, allowing the %s to periodically send data to %s to check for version updates and to validate your trial." msgstr "" #: templates/forms/user-change.php:26 msgid "By changing the user, you agree to transfer the account ownership to:" msgstr "" #: templates/forms/user-change.php:28 msgid "I Agree - Change User" msgstr "" #: templates/forms/user-change.php:30 msgid "Enter email address" msgstr "" #: templates/js/permissions.php:337, templates/js/permissions.php:485 msgid "Saved" msgstr "" #: templates/js/style-premium-theme.php:39 msgid "Premium" msgstr "" #: templates/js/style-premium-theme.php:42 msgid "Beta" msgstr "" #: templates/partials/network-activation.php:32 msgid "Activate license on all sites in the network." msgstr "" #: templates/partials/network-activation.php:33 msgid "Apply on all sites in the network." msgstr "" #: templates/partials/network-activation.php:36 msgid "Activate license on all pending sites." msgstr "" #: templates/partials/network-activation.php:37 msgid "Apply on all pending sites." msgstr "" #: templates/partials/network-activation.php:45, templates/partials/network-activation.php:79 msgid "allow" msgstr "" #: templates/partials/network-activation.php:48, templates/partials/network-activation.php:82 msgid "delegate" msgstr "" #: templates/partials/network-activation.php:52, templates/partials/network-activation.php:86 msgid "skip" msgstr "" #: templates/plugin-info/description.php:72, templates/plugin-info/screenshots.php:31 msgid "Click to view full-size screenshot %d" msgstr "" #: templates/plugin-info/features.php:56 msgid "Unlimited Updates" msgstr "" #: templates/account/partials/activate-license-button.php:46 msgid "Localhost" msgstr "" #: templates/account/partials/activate-license-button.php:50 msgctxt "as 5 licenses left" msgid "%s left" msgstr "" #: templates/account/partials/activate-license-button.php:51 msgid "Last license" msgstr "" #. translators: %1$s: Either 'Downgrading your plan' or 'Cancelling the subscription' #: templates/account/partials/addon.php:34 msgid "%1$s will immediately stop all future recurring payments and your %s plan license will expire in %s." msgstr "" #: templates/account/partials/addon.php:190 msgid "Cancelled" msgstr "" #: templates/account/partials/addon.php:200 msgid "No expiration" msgstr "" #: templates/account/partials/disconnect-button.php:74 msgid "By disconnecting the website, previously shared diagnostic data about %1$s will be deleted and no longer visible to %2$s." msgstr "" #: templates/account/partials/disconnect-button.php:78 msgid "Disconnecting the website will permanently remove %s from your User Dashboard's account." msgstr "" #: templates/account/partials/disconnect-button.php:84 msgid "If you wish to cancel your %1$s plan's subscription instead, please navigate to the %2$s and cancel it there." msgstr "" #: templates/account/partials/disconnect-button.php:88 msgid "Are you sure you would like to proceed with the disconnection?" msgstr "" #: templates/account/partials/site.php:190 msgid "Owner Name" msgstr "" #: templates/account/partials/site.php:202 msgid "Owner Email" msgstr "" #: templates/account/partials/site.php:214 msgid "Owner ID" msgstr "" #: templates/account/partials/site.php:288 msgid "Subscription" msgstr "" #: templates/forms/deactivation/contact.php:19 msgid "Sorry for the inconvenience and we are here to help if you give us a chance." msgstr "" #: templates/forms/deactivation/contact.php:22 msgid "Contact Support" msgstr "" #: templates/forms/deactivation/form.php:65 msgid "Anonymous feedback" msgstr "" #: templates/forms/deactivation/form.php:71 msgid "hour" msgstr "" #: templates/forms/deactivation/form.php:76 msgid "hours" msgstr "" #: templates/forms/deactivation/form.php:81, templates/forms/deactivation/form.php:86 msgid "days" msgstr "" #: templates/forms/deactivation/form.php:106 msgid "Deactivate" msgstr "" #: templates/forms/deactivation/form.php:108 msgid "Activate %s" msgstr "" #: templates/forms/deactivation/form.php:111 msgid "Submit & %s" msgstr "" #: templates/forms/deactivation/form.php:130 msgid "Quick Feedback" msgstr "" #: templates/forms/deactivation/form.php:134 msgid "If you have a moment, please let us know why you are %s" msgstr "" #: templates/forms/deactivation/form.php:134 msgid "deactivating" msgstr "" #: templates/forms/deactivation/form.php:134 msgid "switching" msgstr "" #: templates/forms/deactivation/form.php:448 msgid "Kindly tell us the reason so we can improve." msgstr "" #: templates/forms/deactivation/form.php:478 msgid "Snooze & %s" msgstr "" #: templates/forms/deactivation/form.php:638 msgid "Yes - %s" msgstr "" #: templates/forms/deactivation/form.php:645 msgid "Skip & %s" msgstr "" #: templates/forms/deactivation/retry-skip.php:21 msgid "Click here to use the plugin anonymously" msgstr "" #: templates/forms/deactivation/retry-skip.php:23 msgid "You might have missed it, but you don't have to share any data and can just %s the opt-in." msgstr "" PK!2 assets/css/admin-general.css.mapnu[{"version":3,"sources":["admin-general.scss","admin-general.css"],"names":[],"mappings":"AAAA;EAIE,aAAU;EAEV,kBAAW;EAuFZ,UAAA;ECvFC,WAAW;AADb;;ADLA;EAUI,iBAAkB;EAClB,kBAAkB;EAClB,yBAAW;EACX,WAAY;EACZ,kBAAe;EACf,eAAe;EACf,eAAY;EACZ,YAAW;EACX,WAAU;EACV,UAAU;EACV,UAAS;EAYV,cAAA;ACZH;;ADpBA;EAuBM,qBAAqB;EACrB,mDAA6C;UAA7C,2CAA6C;ACCnD;;ADzBA;EA4BM,WAAW;EACX,YAAY;EACZ,eAAe;ACCrB;;AD/BA;EAoCI,yBAAW;EACZ,WAAA;ACDH;;ADpCA;EA0CI,aAAU;EAEV,kBAAU;EACV,QAAA;EACA,UAAW;EACX,sBAAY;EACZ,WAAA;EAuCD,YAAA;ECvCC,iCAAA;AADJ;;AD/CA;EAoDM,cAAkB;EAClB,6CAAyB;EAgC1B,yBAAA;AChCL;;ADrDA;EAwDa,WAAM;EACL,eAAS;EACf,cAAc;EACd,mBAAmB;EACnB,qBAAqB;EACrB,cAAc;EACd,aAAa;ACCrB;;AD/DA;EAAA,cAkDM;EAeW,SAAO;EACN,wBAAC;UAAD,gBAAC;ACGnB;;ADrEA;EA0EO,gBAAA;ACDP;;ADzEA;EAmFO,yBAAA;ACNP;;AD7EA;EAgFiB,WAAI;ACCrB;;ADjFA;EC0FI,cAAc;AALlB;;AAUA;EAMM,kBAAkB;AAZxB;;AAMA;EAYQ,UAAU;EACV,qBAAqB;EACrB,WAAW;EACX,eAAe;AAdvB;;AADA;EAsBQ,cAAc;AAjBtB;;AAEA,i3KAAi3K","file":"admin-general.css","sourcesContent":[".jpum-user-codes {\n\n display: none;\n\n position: absolute;\n right: 3px;\n bottom: 2px;\n\n > button {\n border: 1px solid;\n border-radius: 2px;\n background-color: #23282d;\n color: #eee;\n text-align: center;\n cursor: pointer;\n font-size: 22px;\n height: 22px;\n width: 22px;\n padding: 0;\n outline: 0;\n display: block;\n\n &:focus {\n border-color: #5b9dd9;\n box-shadow: 0 0 2px rgba( 30, 140, 190, 0.8 );\n }\n\n .dashicons {\n width: 20px;\n height: 20px;\n font-size: 20px;\n }\n }\n\n &:hover > button {\n background-color: #0073aa;\n color: #fff;\n }\n\n ul {\n display: none;\n\n position: absolute;\n top: 1px;\n left: 20px;\n background-color: #fff;\n width: auto;\n z-index: 999;\n /*box-shadow: 1px 1px 5px -1px; */\n\n li {\n display: block;\n border-bottom: 1px dashed rgba(0, 0, 0, .25);\n background-color: #23282d;\n\n > a {\n color: #eee;\n cursor: pointer;\n line-height: 1;\n white-space: nowrap;\n text-decoration: none;\n display: block;\n padding: .5em;\n\n &:focus {\n color: #0073aa;\n border: 0;\n box-shadow: none;\n\n }\n }\n\n &:last-child {\n border-bottom: 0;\n }\n\n &:hover {\n background-color: #0073aa;\n\n > a {\n color: #fff;\n\n }\n }\n\n }\n\n }\n\n &.open ul {\n display: block;\n }\n\n}\n\n#menu-to-edit {\n\n .menu-item {\n\n p.description-wide {\n\n position: relative;\n\n }\n\n .nav_item_options-roles {\n label {\n width: 33%;\n display: inline-block;\n float: left;\n margin-top: 5px;\n }\n }\n\n &.show-insert-button {\n\n .jpum-user-codes {\n display: block;\n }\n\n }\n\n }\n\n}\n",".jpum-user-codes {\n\n display: none;\n\n position: absolute;\n right: 3px;\n bottom: 2px;\n\n > button {\n border: 1px solid;\n border-radius: 2px;\n background-color: #23282d;\n color: #eee;\n text-align: center;\n cursor: pointer;\n font-size: 22px;\n height: 22px;\n width: 22px;\n padding: 0;\n outline: 0;\n display: block;\n\n &:focus {\n border-color: #5b9dd9;\n box-shadow: 0 0 2px rgba( 30, 140, 190, 0.8 );\n }\n\n .dashicons {\n width: 20px;\n height: 20px;\n font-size: 20px;\n }\n }\n\n &:hover > button {\n background-color: #0073aa;\n color: #fff;\n }\n\n ul {\n display: none;\n\n position: absolute;\n top: 1px;\n left: 20px;\n background-color: #fff;\n width: auto;\n z-index: 999;\n /*box-shadow: 1px 1px 5px -1px; */\n\n li {\n display: block;\n border-bottom: 1px dashed rgba(0, 0, 0, .25);\n background-color: #23282d;\n\n > a {\n color: #eee;\n cursor: pointer;\n line-height: 1;\n white-space: nowrap;\n text-decoration: none;\n display: block;\n padding: .5em;\n\n &:focus {\n color: #0073aa;\n border: 0;\n box-shadow: none;\n\n }\n }\n\n &:last-child {\n border-bottom: 0;\n }\n\n &:hover {\n background-color: #0073aa;\n\n > a {\n color: #fff;\n\n }\n }\n\n }\n\n }\n\n &.open ul {\n display: block;\n }\n\n}\n\n#menu-to-edit {\n\n .menu-item {\n\n p.description-wide {\n\n position: relative;\n\n }\n\n .nav_item_options-roles {\n label {\n width: 33%;\n display: inline-block;\n float: left;\n margin-top: 5px;\n }\n }\n\n &.show-insert-button {\n\n .jpum-user-codes {\n display: block;\n }\n\n }\n\n }\n\n}\n"]}PK!~p assets/css/admin-general.min.cssnu[.jpum-user-codes{display:none;position:absolute;right:3px;bottom:2px}.jpum-user-codes>button{border:1px solid;border-radius:2px;background-color:#23282d;color:#eee;text-align:center;cursor:pointer;font-size:22px;height:22px;width:22px;padding:0;outline:0;display:block}.jpum-user-codes>button:focus{border-color:#5b9dd9;-webkit-box-shadow:0 0 2px rgba(30,140,190,.8);box-shadow:0 0 2px rgba(30,140,190,.8)}.jpum-user-codes>button .dashicons{width:20px;height:20px;font-size:20px}.jpum-user-codes:hover>button{background-color:#0073aa;color:#fff}.jpum-user-codes ul{display:none;position:absolute;top:1px;left:20px;background-color:#fff;width:auto;z-index:999}.jpum-user-codes ul li{display:block;border-bottom:1px dashed rgba(0,0,0,.25);background-color:#23282d}.jpum-user-codes ul li>a{color:#eee;cursor:pointer;line-height:1;white-space:nowrap;text-decoration:none;display:block;padding:.5em}.jpum-user-codes ul li>a:focus{color:#0073aa;border:0;-webkit-box-shadow:none;box-shadow:none}.jpum-user-codes ul li:last-child{border-bottom:0}.jpum-user-codes ul li:hover{background-color:#0073aa}.jpum-user-codes ul li:hover>a{color:#fff}#menu-to-edit .menu-item.show-insert-button .jpum-user-codes,.jpum-user-codes.open ul{display:block}#menu-to-edit .menu-item p.description-wide{position:relative}#menu-to-edit .menu-item .nav_item_options-roles label{width:33%;display:inline-block;float:left;margin-top:5px}PK!9lѫassets/css/admin-general.cssnu[.jpum-user-codes { display: none; position: absolute; right: 3px; bottom: 2px; } .jpum-user-codes > button { border: 1px solid; border-radius: 2px; background-color: #23282d; color: #eee; text-align: center; cursor: pointer; font-size: 22px; height: 22px; width: 22px; padding: 0; outline: 0; display: block; } .jpum-user-codes > button:focus { border-color: #5b9dd9; -webkit-box-shadow: 0 0 2px rgba(30, 140, 190, 0.8); box-shadow: 0 0 2px rgba(30, 140, 190, 0.8); } .jpum-user-codes > button .dashicons { width: 20px; height: 20px; font-size: 20px; } .jpum-user-codes:hover > button { background-color: #0073aa; color: #fff; } .jpum-user-codes ul { display: none; position: absolute; top: 1px; left: 20px; background-color: #fff; width: auto; z-index: 999; /*box-shadow: 1px 1px 5px -1px; */ } .jpum-user-codes ul li { display: block; border-bottom: 1px dashed rgba(0, 0, 0, 0.25); background-color: #23282d; } .jpum-user-codes ul li > a { color: #eee; cursor: pointer; line-height: 1; white-space: nowrap; text-decoration: none; display: block; padding: .5em; } .jpum-user-codes ul li > a:focus { color: #0073aa; border: 0; -webkit-box-shadow: none; box-shadow: none; } .jpum-user-codes ul li:last-child { border-bottom: 0; } .jpum-user-codes ul li:hover { background-color: #0073aa; } .jpum-user-codes ul li:hover > a { color: #fff; } .jpum-user-codes.open ul { display: block; } #menu-to-edit .menu-item p.description-wide { position: relative; } #menu-to-edit .menu-item .nav_item_options-roles label { width: 33%; display: inline-block; float: left; margin-top: 5px; } #menu-to-edit .menu-item.show-insert-button .jpum-user-codes { display: block; } /*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRtaW4tZ2VuZXJhbC5jc3MiLCJzb3VyY2VzIjpbImFkbWluLWdlbmVyYWwuc2NzcyJdLCJzb3VyY2VzQ29udGVudCI6WyIuanB1bS11c2VyLWNvZGVzIHtcblxuICBkaXNwbGF5OiBub25lO1xuXG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgcmlnaHQ6IDNweDtcbiAgYm90dG9tOiAycHg7XG5cbiAgPiBidXR0b24ge1xuICAgIGJvcmRlcjogMXB4IHNvbGlkO1xuICAgIGJvcmRlci1yYWRpdXM6IDJweDtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMjMyODJkO1xuICAgIGNvbG9yOiAjZWVlO1xuICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgICBjdXJzb3I6IHBvaW50ZXI7XG4gICAgZm9udC1zaXplOiAyMnB4O1xuICAgIGhlaWdodDogMjJweDtcbiAgICB3aWR0aDogMjJweDtcbiAgICBwYWRkaW5nOiAwO1xuICAgIG91dGxpbmU6IDA7XG4gICAgZGlzcGxheTogYmxvY2s7XG5cbiAgICAmOmZvY3VzIHtcbiAgICAgIGJvcmRlci1jb2xvcjogIzViOWRkOTtcbiAgICAgIGJveC1zaGFkb3c6IDAgMCAycHggcmdiYSggMzAsIDE0MCwgMTkwLCAwLjggKTtcbiAgICB9XG5cbiAgICAuZGFzaGljb25zIHtcbiAgICAgIHdpZHRoOiAyMHB4O1xuICAgICAgaGVpZ2h0OiAyMHB4O1xuICAgICAgZm9udC1zaXplOiAyMHB4O1xuICAgIH1cbiAgfVxuXG4gICY6aG92ZXIgPiBidXR0b24ge1xuICAgIGJhY2tncm91bmQtY29sb3I6ICMwMDczYWE7XG4gICAgY29sb3I6ICNmZmY7XG4gIH1cblxuICB1bCB7XG4gICAgZGlzcGxheTogbm9uZTtcblxuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0b3A6IDFweDtcbiAgICBsZWZ0OiAyMHB4O1xuICAgIGJhY2tncm91bmQtY29sb3I6ICNmZmY7XG4gICAgd2lkdGg6IGF1dG87XG4gICAgei1pbmRleDogOTk5O1xuICAgIC8qYm94LXNoYWRvdzogMXB4IDFweCA1cHggLTFweDsgKi9cblxuICAgIGxpIHtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgYm9yZGVyLWJvdHRvbTogMXB4IGRhc2hlZCByZ2JhKDAsIDAsIDAsIC4yNSk7XG4gICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMjMyODJkO1xuXG4gICAgICA+IGEge1xuICAgICAgICBjb2xvcjogI2VlZTtcbiAgICAgICAgY3Vyc29yOiBwb2ludGVyO1xuICAgICAgICBsaW5lLWhlaWdodDogMTtcbiAgICAgICAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICAgICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgICAgcGFkZGluZzogLjVlbTtcblxuICAgICAgICAmOmZvY3VzIHtcbiAgICAgICAgICBjb2xvcjogIzAwNzNhYTtcbiAgICAgICAgICBib3JkZXI6IDA7XG4gICAgICAgICAgYm94LXNoYWRvdzogbm9uZTtcblxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgICY6bGFzdC1jaGlsZCB7XG4gICAgICAgIGJvcmRlci1ib3R0b206IDA7XG4gICAgICB9XG5cbiAgICAgICY6aG92ZXIge1xuICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDA3M2FhO1xuXG4gICAgICAgID4gYSB7XG4gICAgICAgICAgY29sb3I6ICNmZmY7XG5cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgfVxuXG4gIH1cblxuICAmLm9wZW4gdWwge1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICB9XG5cbn1cblxuI21lbnUtdG8tZWRpdCB7XG5cbiAgLm1lbnUtaXRlbSB7XG5cbiAgICBwLmRlc2NyaXB0aW9uLXdpZGUge1xuXG4gICAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG5cbiAgICB9XG5cbiAgICAubmF2X2l0ZW1fb3B0aW9ucy1yb2xlcyB7XG4gICAgICBsYWJlbCB7XG4gICAgICAgIHdpZHRoOiAzMyU7XG4gICAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgICAgICAgZmxvYXQ6IGxlZnQ7XG4gICAgICAgIG1hcmdpbi10b3A6IDVweDtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAmLnNob3ctaW5zZXJ0LWJ1dHRvbiB7XG5cbiAgICAgIC5qcHVtLXVzZXItY29kZXMge1xuICAgICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgIH1cblxuICAgIH1cblxuICB9XG5cbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxBQUFBLGdCQUFnQixDQUFDO0VBRWYsT0FBTyxFQUFFLElBQUk7RUFFYixRQUFRLEVBQUUsUUFBUTtFQUNsQixLQUFLLEVBQUUsR0FBRztFQUNWLE1BQU0sRUFBRSxHQUFHO0NBdUZaOztBQTdGRCxBQVFFLGdCQVJjLEdBUVosTUFBTSxDQUFDO0VBQ1AsTUFBTSxFQUFFLFNBQVM7RUFDakIsYUFBYSxFQUFFLEdBQUc7RUFDbEIsZ0JBQWdCLEVBQUUsT0FBTztFQUN6QixLQUFLLEVBQUUsSUFBSTtFQUNYLFVBQVUsRUFBRSxNQUFNO0VBQ2xCLE1BQU0sRUFBRSxPQUFPO0VBQ2YsU0FBUyxFQUFFLElBQUk7RUFDZixNQUFNLEVBQUUsSUFBSTtFQUNaLEtBQUssRUFBRSxJQUFJO0VBQ1gsT0FBTyxFQUFFLENBQUM7RUFDVixPQUFPLEVBQUUsQ0FBQztFQUNWLE9BQU8sRUFBRSxLQUFLO0NBWWY7O0FBaENILEFBc0JJLGdCQXRCWSxHQVFaLE1BQU0sQUFjTCxNQUFNLENBQUM7RUFDTixZQUFZLEVBQUUsT0FBTztFQUNyQixVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsdUJBQXlCO0NBQzlDOztBQXpCTCxBQTJCSSxnQkEzQlksR0FRWixNQUFNLENBbUJOLFVBQVUsQ0FBQztFQUNULEtBQUssRUFBRSxJQUFJO0VBQ1gsTUFBTSxFQUFFLElBQUk7RUFDWixTQUFTLEVBQUUsSUFBSTtDQUNoQjs7QUEvQkwsQUFrQ0UsZ0JBbENjLEFBa0NiLE1BQU0sR0FBRyxNQUFNLENBQUM7RUFDZixnQkFBZ0IsRUFBRSxPQUFPO0VBQ3pCLEtBQUssRUFBRSxJQUFJO0NBQ1o7O0FBckNILEFBdUNFLGdCQXZDYyxDQXVDZCxFQUFFLENBQUM7RUFDRCxPQUFPLEVBQUUsSUFBSTtFQUViLFFBQVEsRUFBRSxRQUFRO0VBQ2xCLEdBQUcsRUFBRSxHQUFHO0VBQ1IsSUFBSSxFQUFFLElBQUk7RUFDVixnQkFBZ0IsRUFBRSxJQUFJO0VBQ3RCLEtBQUssRUFBRSxJQUFJO0VBQ1gsT0FBTyxFQUFFLEdBQUc7RUFDWixrQ0FBa0M7Q0F1Q25DOztBQXZGSCxBQWtESSxnQkFsRFksQ0F1Q2QsRUFBRSxDQVdBLEVBQUUsQ0FBQztFQUNELE9BQU8sRUFBRSxLQUFLO0VBQ2QsYUFBYSxFQUFFLEdBQUcsQ0FBQyxNQUFNLENBQUMsbUJBQWtCO0VBQzVDLGdCQUFnQixFQUFFLE9BQU87Q0FnQzFCOztBQXJGTCxBQXVETSxnQkF2RFUsQ0F1Q2QsRUFBRSxDQVdBLEVBQUUsR0FLRSxDQUFDLENBQUM7RUFDRixLQUFLLEVBQUUsSUFBSTtFQUNYLE1BQU0sRUFBRSxPQUFPO0VBQ2YsV0FBVyxFQUFFLENBQUM7RUFDZCxXQUFXLEVBQUUsTUFBTTtFQUNuQixlQUFlLEVBQUUsSUFBSTtFQUNyQixPQUFPLEVBQUUsS0FBSztFQUNkLE9BQU8sRUFBRSxJQUFJO0NBUWQ7O0FBdEVQLEFBZ0VRLGdCQWhFUSxDQXVDZCxFQUFFLENBV0EsRUFBRSxHQUtFLENBQUMsQUFTQSxNQUFNLENBQUM7RUFDTixLQUFLLEVBQUUsT0FBTztFQUNkLE1BQU0sRUFBRSxDQUFDO0VBQ1QsVUFBVSxFQUFFLElBQUk7Q0FFakI7O0FBckVULEFBd0VNLGdCQXhFVSxDQXVDZCxFQUFFLENBV0EsRUFBRSxBQXNCQyxXQUFXLENBQUM7RUFDWCxhQUFhLEVBQUUsQ0FBQztDQUNqQjs7QUExRVAsQUE0RU0sZ0JBNUVVLENBdUNkLEVBQUUsQ0FXQSxFQUFFLEFBMEJDLE1BQU0sQ0FBQztFQUNOLGdCQUFnQixFQUFFLE9BQU87Q0FNMUI7O0FBbkZQLEFBK0VRLGdCQS9FUSxDQXVDZCxFQUFFLENBV0EsRUFBRSxBQTBCQyxNQUFNLEdBR0gsQ0FBQyxDQUFDO0VBQ0YsS0FBSyxFQUFFLElBQUk7Q0FFWjs7QUFsRlQsQUF5RkUsZ0JBekZjLEFBeUZiLEtBQUssQ0FBQyxFQUFFLENBQUM7RUFDUixPQUFPLEVBQUUsS0FBSztDQUNmOztBQUlILEFBSUksYUFKUyxDQUVYLFVBQVUsQ0FFUixDQUFDLEFBQUEsaUJBQWlCLENBQUM7RUFFakIsUUFBUSxFQUFFLFFBQVE7Q0FFbkI7O0FBUkwsQUFXTSxhQVhPLENBRVgsVUFBVSxDQVFSLHVCQUF1QixDQUNyQixLQUFLLENBQUM7RUFDSixLQUFLLEVBQUUsR0FBRztFQUNWLE9BQU8sRUFBRSxZQUFZO0VBQ3JCLEtBQUssRUFBRSxJQUFJO0VBQ1gsVUFBVSxFQUFFLEdBQUc7Q0FDaEI7O0FBaEJQLEFBcUJNLGFBckJPLENBRVgsVUFBVSxBQWlCUCxtQkFBbUIsQ0FFbEIsZ0JBQWdCLENBQUM7RUFDZixPQUFPLEVBQUUsS0FBSztDQUNmIn0= */ /*# sourceMappingURL=admin-general.css.map */ PK!$SSassets/images/icon-128x128.pngnu[PNG  IHDR>asRGB pHYs  YiTXtXML:com.adobe.xmp 1 L'Y@IDATx}`U#`l,]kݝ*bI("* ;7`sgcp*79S\c &U;O|:~[&wW9;vN`nU ۏZ>sFΞ !X!P;+|8T{@%5j /,<'vW@o=իVTXtP)(/sgAvUp3]!;_&QE5U?csUN(,T꿃ڟ>sZڝmKVpOݙ3*ZO4H }? #"[?WE )4؂[^H]1X:SjU)˕\Vr:zv] ^v-8+v@RFLxH Ipt.}Wwtq˯Kprhy'+9\,r r jմE~Td9b\T@ 5NU5h8:96ycZM;:mu1jz~yɊ|ӬUi۳d&)R&yS ϒ@▔we^ARwba"(?_2N쐧"} ;D{?t/a:qvCnImo]} ,>p]3HFb>yPrD;zW8גѩEY8Μ=qU Xt̔'$kW4:iТ4nFjyza7$KRll_R 45NKIիdے$-ld<7rwت̭'&s?->mC ɕf>eH[EA?zpgd _575C:K@ppjZ9!mYN^K^?Ta nLjթ#5ks Ȱ~`]'{oJ`$+Tdy)ȀHρCqₙkx#;-o?dKnJK2\S& bxaLt۹CBW>*w=`V"ೳR|4VjP"ʤР./D"浫}A;CkQRU:uےx2J#˝}(7C/cؽu, zkY%+OtuF)O>+m;vu @'lx cĿU'9z̸.:A+`!X!derl.yxX}pyRN} ~PH #?Vo0S6-[,CGݩ\颶M` 9ء}{%)tORM\jבdҾҸywP7:]d-elUv2b%+['=F=!>ת%4"4 BR UH ȮR˿;ۢfȌَ2&]s[rͭ[ ]@-_RBt%tEEdq t@!8yIdxdedge_~! 4 |[+6-/+{"W-eMҠqmȴy P=]>!@0>z5’RBoVWXGH;zV23cr`.ٿm$`М`5deOMp9zx4 .nRX'I[6weuҩgi`]k=e?es,DbQ858io4kF -o},;ˆ% eRVxҁv%uqz椄C;cnY R*%2LKK.]$kx5,K~UGK“ķOwՕ8!kpI yҴow'/}8Qg$JMJRy ٺhd kzH &R+M{ MML&r!ٺjl FQi~9/!w&qXjזg?"!;idoo)Q+kwH˶Y{&u$HUS)0Hzq)[}__Go2n`$ɧ#6ԖR?-t f*ӬŴ^C~tNj4ux"$ʚߋ̳5$fryfop}Ϫ&#Aɞ ApIww7P܁HX"V,T,K~Zd-ώgKH5 /Ǥuj6Svh 62ZJCɎ))ۏəR7h'h){Q,#Qa `tbߍJ|eU2NJkoU`f}+Kfz3)ꕊ} ,(mB/?-=$R?P,3Gؾ]"e}O9>W,]<,@HP,&ahqʞ@ ۄH̦ eod1٨fÇPS}C0HA16*ZQɮyоk3Q?v:$6LU4.}KCa(ˆӺD.](mڥ}Ci.+-w]X)RVX9@ekّUdҚUҥWTc{H/r/5n׋K ]Y9ٔ,9K!dͣaV̖MRd{,-W Ao>)h,Ta¬ynWҹG/}z=.) !;lAc i ~ty~Rj3/_yR HACtq3қB&TBFSk%`@-D>S>~؀ɂ%Xo;+Y&)>^}0^"JfU%"ߵS+vj(]'ѬA8kl &5S- E~ӜďdWį$j5l$՜\dKz=]]>w90=OfIˀQ#9NV~cR. ࡣ'nZTa$G]=j S'A0m~]Kw2FTpX/Pd`0;y$ZVn>e|,㗟^:s-z`X?Ϻ4H{_@w2E@",嬮@"8f&>}EEIu} ٵd[X23RB9 ,>`,̻{av;[g$lGv.5P%1j9G@3Rc(8}{~||qM+SS!A𻄨C$SrANºR72-Cׯ2KݴzX?5ώͰAQ$3,Mjo&u):/tq0O>w}rtVqSWbn=剱jG yS:Kmomx]h#~BV $CȐt^({ɣ~!#oght`cgm,\e&}!cN5k z{W"td:'GGܜ'YJp |5n=,YNI:OՆ/aCU}떉e5+|3DiKSUԭ'E#+Q#u86,.~զnq9K>,=|A4]rD͜*uqO kP[aga9ρB%ūߗٯUsG.wb8E Plc"O[,uֈ3_̛ +j#'A6}8@A1V2!NeF2k357ҮW?iծ\tG?H*#kvM;Sޒ"|PFK؂e 6I$2 U~2ᥫU.uU™GUvB.(=%1>N+ao D$NPbmMZe:gWl>ZL|QVʹLd<$O5zj "`/qurdFdϙ3&ԇЇNh1uI@[A8[*H) eIݐ@LUgO'\,9c98o%jX p:~R yl@XP߲?bÿUmYQZB= u٩K2e;!5 !Q6J^=Ӭ!JFj|srvB=JQvf?{IٴqE9cΔ `~ ;"Q/fM)  lH6^]L$X&N|ڵwSlݾ 5 ict}"Ϫzԇ*H82wse(X~fV7!D;F ϰ0f (_^k$b v\Ph̩ߤFFQna! {ٵr |",z$Mӷd7DdLY>=mլ~b%j꒼Dīn\M'MOahlu.F>%2jE>i0"YVEeJQV%ИGշ=D[W 38g:P$~W xVJ?AD.`fϐeZmeYw"墴uJ"o?Zz= x Oo~s*bRA%F>{ri9Xs4'}R8+ec\i 0e-˴)}8G6}Bu*|<Bui1W%ɢB" ~9@;NsFPyxBCPOc׍UҊ1*q;!(3; ijNt)&l5q`Y]g>PQGVcA%o㪕2_iYZ Ęh?-ׯf2%E}@~:a((n26G:+Y^2:_ŇS#vFYe]ȵ7ߢknKبC|ia[l QRz2@:Ώ2 =OC6b 'M)G$D}4@0c'GPHA{lkdaoj`S{d ?A}TwRK]v]K'C;*_H!‰DJשּׁA꟦#<54jU~> C:Y!O힅-exj3GINqyyVB1oʞxҗ.a2p?V͵q=des$ H^fkd JF6>HV|3^髮cBA0a?8z Jfo IG}§w͕P.v zbW3I7D ^|wooµm\cI?!uyo$}h[$yERvk!2?7kȘǜbۂmeMI2b$MyN! ác" s ܝ`g}66:Z>Mٷzx7nsQg 1~F)H-A\sd[_/^ivnun];tCY0Kgհ͕Ae0!gHNHPN7lN6gW/^(0\3Ʊ)i*@ZA&,6GƌZL;l>!$v9m]hTcY LwPcER̡4^"n_gӷ<:y~K ? _PFX?ۗK 6 Wmgv[‡mExIBer(gS'Ƚo|,t*;n7;? +Yo`o]5ls~0;4jZTNf^7m$܄V@P[<[oNd{N6n{%d/6 ({w_ɺs{E,.T1(]+ſ@uMҴe+˥c3!:(;֯}KFC1sb197+EXRbmyS\GA6 j@G6tJ2{ d&J"RUNHml:RY{,a6TSpP%0>AL%V藪v>ONS}1"}K`kdh瘸6-.uAF[{hQ+*hLp<ҫ ]VGrz0h`ВGLVoԟ2gA8WfȎ6 N,k.")>9lm`9H4=2ws` TPLg ^WwwZ+Yj.DzynKb% ,uu/?儌DɛaNC`LJJѺEvu1sp B̀<urR/F:ɻ ;JPpe*0/{,e@ ],W{ߚ,I2^"y$풼8|0+R!r yf`E>㶵ޣ:BWW 0ڏ {`}/t۞Sc]-?/qs`qOUS0l59cN쒛_#li~m -|XZ[F6y/Hd\3RS_ʣ| ~~bΆbx =$1XAw]? 8Y7Ӯ&Q怐Cz @ /G\'G' <{oÿߪ]ŨPk7V z[nmhca'S 4.PFH<"xX-4vu #e\ZI) !kho9.ZH142TNw>ġ= ۘF$]n/-bdrjHn`՝kyĢ1e"\- +$.Bjҽ9"Ӈ t\Kb^J} }.udS`}frWBYVvPש tIJOSq3"ΆœLE*>z`Tt(^N 4n\g/>&+OkUF@#)YT< |p|[G?Ͻ.!uEz_>rr7fF;w``TE.QyT=a`X#; w QՏ=pA52cA?b9r=NZBZ9Ɯu/VQ  v~k %ʂIJKgV/5F._.!V3@/դ#4sh/![,L0R &T-To|PZz< ŷ=m˿V&P*YǑ2S<& R-lbęDնu4Q|ת8SX q`&նW[1ٰj&0i7H6ߗXHpQJ̎2?ꬤ$'[>5Rc+Dě*So'H[56*q &kB>v$ܩfW [t[j##L&bndDy>BJxb& s,B?j ˥0<ӽلW^lwc, c[v3eMh}h,y{FVS> @ZUЉ*Fiz L):uF}eWTRx>?ЕcրN[,~9?1g"v$wPvc_Kt撄u$";9N Dnط},M(yiIҰmG9없~ >"wb"8]ck8ә|7il?!a0ܪ\n҅[_Veg}!g @hRuB@;.%BB0x؂/X7&{ >iF2."8,;6oj(y}ctƦQbSY Ŭ) K]e-L Xy/5k-KS1 &g~"oMB@zX3=LrCjQvm 7x 0Wogɸf .xc 0h01rPO)4` ?(H¨)M剿y]UQK(N6 $9SʎۤiqtUJ+j/_B04l/?JݠIHXfGԌzw+:8yhS>{صػV]&.(3ܱ}~|xֿ_O'?Z .w)7>_|+(I{݌ b ʦ2 !Xb,K'1{hyW36t3ƛ!/DSwW =ejLq}.\h*ڻu0e[6e4LO40^ewx ZdLjXm~{:ld}۷OnP"ScI@TOdqґ lA[Z)Žoi2 `wOG@艛ԭؗ< '[%[7iU>d#WC|E҉lhyG{Ns SOL/BKVsd% AHppA]u(ZNb!ض [Ɗ^ӵ -9sG2Y􋳋kuMsGs{/3Byk։H\?hBhUF\ Ra f-l|Аi$o+L`/Jv6nZJ  r)"}.7Gsp@,0Y>T bG0,+kk^>\*7R4#gBő=d aR=>cPCf>PEE?', )X7=R!ҙΚK,-%b2rbih9p\w WskR2kV 28#9yT2OFBIuC4=%l'$kh,E >$eŬ/eɧJ`}ATMy$ p}e9 qd9w$!۫S7Ӣxfpt E¶oENل|4ZJX ѡK. q" m,-cjCZT-_)@;\=ui6 ;|E=;pvņ!\Z6._*^tPɽo]yG~G,".9Ӫ`>Μi)8nD r8V4ZwiDu G;~Lm#yƛ!MN9|9~MG&4xAs͞WhԪ愓HZ?{DPpC:/u@=˾P)m3!7YBd3̼吻4pдY#wG.e2dg|̦^wêu7Pv/X,K3⿓;KQ4^/X-\6s9fn}ȼi*i([֭ l3/|Ml{BBBcD`_QOM2JeSV.tlΰ-ۤ;Ȣ9{X_ ̍*~@ꞑ<3i⣓~ {vq.H*1.QMy4~K# 00 B)8[H-F%ep6L'4ʆGᑈXxL?ī*Q)@ȒskG;cbb,3f0%W M5Ox7d.]>=f'AJv;p=2t䍺l+N!|tN1/@Gpӏ!kmk hrTz,lŸ# jn񱐐iX:h8j＀c % O-G ).^ U57w%Z Ug ,J cbDT?Vyn7I@b̓{䏹W2LF HdЏHA2 KBUGJ GUrճ^%4%qj88h'ݥlZxi=l$!o u. v%\ߕG謅*-fhG&J]8fN;D cO phf 뤺I^;jmH@$wpY:x͞SH2Q@c+B.">˿5=.Gx!3PY[v-ENz4F#% M}KEhٲ 2S1Qt):,s\w@ݠSt-(i֩[*@3ɥ `!܃ʎ k46Q_9 ŋ3<@>X M"A?öYԚGē@zz>D@JHdHS\'zY4UWΠ̄HrE| vw~Z&T0 %i${{ˉ`-^?*G HJλ tg$I$-z捷ξօjs:T Ē 䑃jV ?A#U@os0O> A H+CG \>D " d!bFb,@!E; ou0 q{ȠB[{ Y7oDNM}le+eNk42 Sngr UFx,X]jQ|qGC/'T4jЬ쌄)ɒ}6v t. mRwˎB7XW.hpzje&鱑Xm|k8llctѕHu]TCuZ|vֵŻYiS-۰߶DV<ȷ6iqص )AP˃ak/@w1'ڟgo} xl.$`6njQNC_bŎF%xܟW*=`^1( >] k#R1% 9xRR2(G!"gBjt-Ҵkq`b[DwGr655&"눛佹+C~؟3J(DMȗKI&|F!W K<4[-L1F>*ZZub| 2oQ6E%ϔW_"ٽs@}=Q@oLF|%Qdm‚#;gɓc^Ӄi+i|J̕(g3'0*#7~22J2Y4V\̝+`et4X14廹XoE5'8xe0-cmF}w(32 92qM[D܎lKwR?ۨT (X1lt6iΙ9t[0A4D⌼H ǩ"_ӉwPnA=oym]ӷnؠUs)Spl<Pt+] Y Or*Qu{t8O(d_6)"Orl9cHGs&AngXs1~/]L5՗.R m3}ve.EѨb56}ƵThXJg&fϗW-I}J~{&.߫\@EYX oteoJ#X5@rcV[XK}+dģ/{ء4~yy@J,%]¿spZhp4EFX$j$ܸLjYdT>KZS;H6~\@3߆ߗ䳥#Ru*S!LMQ3]6} 7>&_(+ ,@^_BԎ?V m<ܱkkuփcaaw6%r0q\g AfXaf(þ`܇hmq;wxK UUQ VDod?Y< 7uvb-J(T%Tf nIOHuo[)0ŽVmUOb4 -I"vʭ88#%{Э_k-l!Ӧ{ڒsx* >"$;Kt}!^@EnMi4gc̑j5܄il_@ܼ4*I ȭW_[ i|85d._kaKQͯ|st I,=dJשaD>3w|>0`3ۯe6XC+fM3c##9G9!Jgd 1G?8EztP&Pe 7I֡Csˤfqxt^f .>pbu0Y^+F'TsvPSiMRe;_U4j"9 afD+c5dJ3^V/Yb݀ݷM(fa{uiX#pf Q\28.f`46DG>V=+dԘhdx路Ѝ%$npi;n]n}rZ;Jm,d .u-pScl$ !6 DKڵ!d"ZmSu ILDz >hk , -p#p4dd$pL_;ZdnTFJ$w:G(ۋ:1;VH<5BGt2CmٳE%B!".n6܃31M)LAôX[x_{IfL{u Y}K6@V6O2E&I&mB~>w2Z(XLvU$pb8IڕI븘yӐe@k|n9uy*{*DQ$S?ר#փH`yQ4 LVA"3P,b ڲb>HD (T)6WbƮ%23 ؽ]SEMU72hĿuY &j gA[K?C^r"#= yW@GHc9ɒ=^bA J82g)HpzCsK9 rȶͲ7,Y -ъYFwD޽S'յoMڢԂB"HC ~9ҌtI)K J4dQ ___Ig84rrU*ph"{Љ6e{8VٳG& l C?~T]YmT"h ;“e->B*Aٍ("-܄q>z1yd#J"#$^1k2CA-tuÞK[dvHܸc£nS#pH&2xB9jbQn9 :i ܢ@O '.(Xc{ݰOnjp`թ#}5 $Ү و=xÞ]k18SP !g!Eǎb&"W/'y!KB&8۰Ym\(8^=c57 `KR3رv LQ  W` ԰*ni*`(W jºmz| HDz@mS)w9WOeR>ͮ fG3~zLcnmͧl_1TRμl5yioS)뾷f<羯պ !;33Gq -d=†,& Ag]@V>S?- Gjj2ӭĪejm2ɫNlV39v ގ+0>KX0("\bdOH„gI嬧"«w2i󪂒ː|_f q5nn1ʈ'La:6K?6ΟOM+]һ+ݒMt{CfsNb$ZsA5(;YouGKZ VO"\67=mdr!Gf(Gƞ#ONY%tI:EM|O/Ϳ8P['+^siT`9.ظ|yϔ =BvtB Pʟl7Fxû/HNx8asWʒBH_{ϿԘ&bT4=[Z8^uFOkIo#Q+/%JJW)F FdMf.l7]sHXsɞy^+F(&t 5g`#{xnLNxcax| PEoE `;VU:O*gmo*#{Mu3s~se[ⶵ03sL:6̩Sdy؁ /m"q h#(jYOmw;KۮXpۘ1R _WH^XSL Ϩg &3d4C^z:[h\ .FϏF ^%b[gĄ>9w r JuDK5b0<g"n\LB!6)ff=M&2 Ds^#Vag<$~Z+Rin S_(:4~7q9S3~>~Rנ}QOU5(,1@3?ajLj{uLܱ)I8ͣۨo4[D)J%(^mC%`e%pNmy}.XfH?ڿl JAHDUf7m drlߺ8Ku%Q U/!}V=0L]UՏGa[Yy!@C}ԩ$"($Y}808_3Z>*a_|jjUDhHv2 YU/,Qdow_rM7O_zS NNA ʳq5'ǎ[L&:T@*IENDB`PK!^#}assets/js/admin-general.jsnu[(function ($, $document) { "use strict"; function disable_register_menu_item() { var $disabled = $('.user-menus-registration-disabled'); if ($disabled.length) { $disabled.find('li:eq(1) input[type="checkbox"]').attr('disabled', true); } } function redirect_type() { var $this = $(this), $url = $this.parents('.menu-item').find('.nav_item_options-redirect_url'); if ($this.val() == 'custom') { $url.slideDown(); } else { $url.slideUp(); } } function avatar_check() { var $label = $(this), $size = $label.parents('.menu-item').find('.nav_item_options-avatar_size'); if ($label.val().indexOf('{avatar}') >= 0) { $size.slideDown(); } else { $size.slideUp(); } } function which_users() { var $this = $(this), $item = $this.parents('.menu-item'), $can_see = $item.find('.nav_item_options-can_see'), $roles = $item.find('.nav_item_options-roles'), $insert_button = $item.find('.jpum-user-codes'); if ($this.val() === 'logged_in') { $can_see.slideDown(); $roles.slideDown(); $item.addClass('show-insert-button'); $insert_button.fadeOut(0).fadeIn(); } else { $can_see.slideUp(); $roles.slideUp(); $insert_button.fadeOut(function () { $item.removeClass('show-insert-button'); }); } } function toggle_user_codes() { $(this).parent().toggleClass('open'); } function reset_user_codes(e) { if (e !== undefined && $(e.target).parents('.jpum-user-codes').length) { return; } $('.jpum-user-codes').removeClass('open'); } function insert_user_code(event) { var $this = $(this), $input = $this.parents('p').find('input'), val = $input.val(); event.which = event.which || event.keyCode; if (event.type === 'keypress' && event.keyCode !== 13 && event.keyCode !== 32) { return; } $input.val(val + "{" + $this.data('code') + "}").trigger('change'); reset_user_codes(); event.preventDefault(); } function append_user_codes() { return $('input.edit-menu-item-title').each(function () { var $this = $(this).parents('label'), template = _.template($('#tmpl-jpum-user-codes').html()); if (!$this.parents('p').find('.jpum-user-codes').length) { $this.after(template()); } }); } function refresh_all_items() { append_user_codes(); $('.nav_item_options-redirect_type select').each(redirect_type); $('.nav_item_options-which_users select').each(which_users); $('.nav_item_options-which_users select').each(which_users); $('input.edit-menu-item-title').each(avatar_check); } $document .on('change', '.nav_item_options-redirect_type select', redirect_type) .on('change', '.nav_item_options-which_users select', which_users) .on('change keyup focusout', 'input.edit-menu-item-title', avatar_check) .on('click', '.jpum-user-codes > button', toggle_user_codes) .on('click keypress', '.jpum-user-codes li > a', insert_user_code) .on('click', reset_user_codes) .on('menu-item-added', refresh_all_items); // Add click event directly to submit buttons to prevent being prevented by default action. $('.submit-add-to-menu').click(function () { setTimeout(refresh_all_items, 1000); }); $(refresh_all_items); $(disable_register_menu_item); }(jQuery, jQuery(document)));PK!Iassets/js/admin-general.min.jsnu[!function(o){"use strict";function e(){var e=o(this),t=e.parents(".menu-item").find(".nav_item_options-redirect_url");"custom"==e.val()?t.slideDown():t.slideUp()}function t(){var e=o(this),t=e.parents(".menu-item").find(".nav_item_options-avatar_size");0<=e.val().indexOf("{avatar}")?t.slideDown():t.slideUp()}function n(){var e=o(this),t=e.parents(".menu-item"),n=t.find(".nav_item_options-can_see"),i=t.find(".nav_item_options-roles"),s=t.find(".jpum-user-codes");"logged_in"===e.val()?(n.slideDown(),i.slideDown(),t.addClass("show-insert-button"),s.fadeOut(0).fadeIn()):(n.slideUp(),i.slideUp(),s.fadeOut(function(){t.removeClass("show-insert-button")}))}function s(e){void 0!==e&&o(e.target).parents(".jpum-user-codes").length||o(".jpum-user-codes").removeClass("open")}function i(){o("input.edit-menu-item-title").each(function(){var e=o(this).parents("label"),t=_.template(o("#tmpl-jpum-user-codes").html());e.parents("p").find(".jpum-user-codes").length||e.after(t())}),o(".nav_item_options-redirect_type select").each(e),o(".nav_item_options-which_users select").each(n),o(".nav_item_options-which_users select").each(n),o("input.edit-menu-item-title").each(t)}jQuery(document).on("change",".nav_item_options-redirect_type select",e).on("change",".nav_item_options-which_users select",n).on("change keyup focusout","input.edit-menu-item-title",t).on("click",".jpum-user-codes > button",function(){o(this).parent().toggleClass("open")}).on("click keypress",".jpum-user-codes li > a",function(e){var t=o(this),n=t.parents("p").find("input"),i=n.val();e.which=e.which||e.keyCode,"keypress"===e.type&&13!==e.keyCode&&32!==e.keyCode||(n.val(i+"{"+t.data("code")+"}").trigger("change"),s(),e.preventDefault())}).on("click",s).on("menu-item-added",i),o(".submit-add-to-menu").click(function(){setTimeout(i,1e3)}),o(i),o(function(){var e=o(".user-menus-registration-disabled");e.length&&e.find('li:eq(1) input[type="checkbox"]').attr("disabled",!0)})}(jQuery);PK!nNcontributors.txtnu[jungleplugins, danieliserPK!s_6 index.phpnu[header(); $step = empty( $_GET['step'] ) ? 0 : (int) $_GET['step']; switch ( $step ) { case 0: $this->greet(); break; case 1: check_admin_referer( 'import-upload' ); if ( $this->handle_upload() ) { $file = get_attached_file( $this->id ); set_time_limit( 0 ); $this->import( $file ); } break; } $this->footer(); } /** * Page header. */ public function header() { echo '
    '; echo '

    ' . __( 'Import Nav Menus', 'user-menus' ) . '

    '; $updates = get_plugin_updates(); $basename = plugin_basename( __FILE__ ); if ( isset( $updates[ $basename ] ) ) { $update = $updates[ $basename ]; echo '

    '; printf( __( 'A new version of this importer is available. Please update to version %s to ensure compatibility with newer export files.', 'user-menus' ), $update->update->new_version ); echo '

    '; } } /** * Display introductory text and file upload form */ public function greet() { echo '
    '; echo '

    ' . __( 'Upload your WordPress export (WXR) file and import the Nav Menus and any meta for the Nav Menu items.', 'user-menus' ) . '

    '; echo '

    ' . __( 'Choose a WXR (.xml) file to upload, then click Upload file and import.', 'user-menus' ) . '

    '; wp_import_upload_form( 'admin.php?import=jpum_nav_menu_importer&step=1' ); echo '
    '; } /** * Handles the WXR upload and initial parsing of the file to prepare for * displaying author import options * * @return bool False if error uploading or invalid file, true otherwise */ public function handle_upload() { $file = wp_import_handle_upload(); if ( isset( $file['error'] ) ) { echo '

    ' . __( 'Sorry, there has been an error.', 'user-menus' ) . '
    '; echo esc_html( $file['error'] ) . '

    '; return false; } elseif ( ! file_exists( $file['file'] ) ) { echo '

    ' . __( 'Sorry, there has been an error.', 'user-menus' ) . '
    '; printf( __( 'The export file could not be found at %s. It is likely that this was caused by a permissions problem.', 'user-menus' ), esc_html( $file['file'] ) ); echo '

    '; return false; } $this->id = (int) $file['id']; $import_data = $this->parse( $file['file'] ); if ( is_wp_error( $import_data ) ) { echo '

    ' . __( 'Sorry, there has been an error.', 'user-menus' ) . '
    '; echo esc_html( $import_data->get_error_message() ) . '

    '; return false; } $this->version = $import_data['version']; if ( $this->version > $this->max_wxr_version ) { echo '

    '; printf( __( 'This WXR file (version %s) may not be supported by this version of the importer. Please consider updating.', 'user-menus' ), esc_html( $import_data['version'] ) ); echo '

    '; } return true; } /** * The main controller for the actual import stage. * * @param string $file Path to the WXR file for importing. */ public function import( $file ) { add_filter( 'import_post_meta_key', [ $this, 'is_valid_meta_key' ] ); add_filter( 'http_request_timeout', [ $this, 'bump_request_timeout' ] ); $this->import_start( $file ); wp_suspend_cache_invalidation( true ); $this->process_nav_menu_meta(); wp_suspend_cache_invalidation( false ); $this->import_end(); } /** * Render the page footer. */ public function footer() { echo '
    '; } /** * Parse a WXR file * * @param string $file Path to WXR file for parsing. * * @return array Information gathered from the WXR file. */ public function parse( $file ) { $parser = new \WXR_Parser(); return $parser->parse( $file ); } /** * Parses the WXR file and prepares us for the task of processing parsed data. * * @param string $file Path to the WXR file for importing. */ public function import_start( $file ) { if ( ! is_file( $file ) ) { echo '

    ' . __( 'Sorry, there has been an error.', 'user-menus' ) . '
    '; echo __( 'The file does not exist, please try again.', 'user-menus' ); echo '

    '; $this->footer(); die(); } $import_data = $this->parse( $file ); if ( is_wp_error( $import_data ) ) { echo '

    ' . __( 'Sorry, there has been an error.', 'user-menus' ) . '
    '; echo esc_html( $import_data->get_error_message() ); echo '

    '; $this->footer(); die(); } $this->version = $import_data['version']; $this->posts = $import_data['posts']; $this->base_url = esc_url( $import_data['base_url'] ); do_action( 'import_start' ); } /** * Create new menu items based on import information */ public function process_nav_menu_meta() { foreach ( $this->posts as $post ) { // Exclude other post types. if ( 'nav_menu_item' !== $post['post_type'] || ! empty( $post['post_id'] ) ) { continue; } $post_id = (int) $post['post_id']; if ( isset( $post['postmeta'] ) ) { foreach ( $post['postmeta'] as $meta ) { $key = apply_filters( 'import_post_meta_key', $meta['key'] ); $value = false; if ( $key ) { // export gets meta straight from the DB so could have a serialized string. if ( ! $value ) { $value = maybe_unserialize( $meta['value'] ); } update_post_meta( $post_id, $key, $value ); do_action( 'import_post_meta', $post_id, $key, $value ); } } } } unset( $this->posts ); } /** * Performs post-import cleanup of files and the cache. */ public function import_end() { wp_import_cleanup( $this->id ); wp_cache_flush(); echo '

    ' . __( 'All done.', 'user-menus' ) . ' ' . __( 'Have fun!', 'user-menus' ) . '

    '; do_action( 'import_end' ); } /** * Decide if the given meta key maps to information we will want to import. * * @param string $key The meta key to check. * * @return string|bool The key if we do want to import, false if not. */ public function is_valid_meta_key( $key ) { if ( in_array( $key, $this->invalid_meta_keys, true ) ) { return false; } return $key; } } PK!o9S includes/classes/site/menus.phpnu[ $item ) { // Exclude menu items that are children of excluded items. $exclude = in_array( (int) $item->menu_item_parent, $excluded, true ); if ( 'logout' === $item->object ) { $exclude = ! $logged_in; } elseif ( 'login' === $item->object || 'register' === $item->object ) { $exclude = $logged_in; } else { if ( is_object( $item ) && isset( $item->which_users ) ) { switch ( $item->which_users ) { case 'logged_in': if ( ! $logged_in ) { $exclude = true; } elseif ( ! empty( $item->roles ) ) { /** * If yes * - this value will be true * - $allowed_by_role will be set to false by default, allowing only matched roles to see it. * - if any matching role is found, $allowed_by_role will be set to true. * * If no * - this value will be false. * - $allowed_by_role will be set to true by default, allowing all not-matched roles to see it. * - if any matching role is found, $allowed_by_role will be set to false. */ $can_see = 'yes' === $item->can_see; $allowed_by_role = ! $can_see; foreach ( $item->roles as $role ) { if ( current_user_can( $role ) ) { $allowed_by_role = $can_see; break; } } if ( ! $allowed_by_role ) { $exclude = true; } } break; case 'logged_out': $exclude = $logged_in; break; } } } $exclude = apply_filters( 'jpum_should_exclude_item', $exclude, $item ); // unset non-visible item. if ( $exclude ) { $excluded[] = $item->ID; // store ID of item. unset( $items[ $key ] ); } } return $items; } } Menus::init(); PK!Rɲincludes/classes/user/codes.phpnu[ __( 'Avatar', 'user-menus' ), 'first_name' => __( 'First Name', 'user-menus' ), 'last_name' => __( 'Last Name', 'user-menus' ), 'username' => __( 'Username', 'user-menus' ), 'display_name' => __( 'Display Name', 'user-menus' ), 'nickname' => __( 'Nickname', 'user-menus' ), 'email' => __( 'Email', 'user-menus' ), ]; } } PK!#l l Bincludes/classes/walker/nav-menu-edit-custom-fields-deprecated.phpnu[]+class="[^"]*field-move)/', $this->get_custom_fields( $item, $depth, $args ), $item_output ); } /** * Get custom fields * * @param object $item Menu item data object. * @param int $depth Depth of menu item. Used for padding. * @param array $args Menu item args. * * @return string Additional fields or html for the nav menu editor. * @uses do_action() Calls 'menu_item_custom_fields' hook */ protected function get_custom_fields( $item, $depth, $args = [] ) { ob_start(); $item_id = intval( $item->ID ); /** * Get menu item custom fields from plugins/themes * * @param int $item_id post ID of menu * @param object $item Menu item data object. * @param int $depth Depth of menu item. Used for padding. * @param array $args Menu item args. * * @return string Custom fields */ do_action( 'wp_nav_menu_item_custom_fields', $item_id, $item, $depth, $args ); return ob_get_clean(); } } PK!_v v 7includes/classes/walker/nav-menu-edit-custom-fields.phpnu[=' ) ) { require_once ABSPATH . 'wp-admin/includes/class-walker-nav-menu-edit.php'; } else { require_once ABSPATH . 'wp-admin/includes/nav-menu.php'; } } /** * Custom Walker for Nav Menu Editor * * Add wp_nav_menu_item_custom_fields hook to the nav menu editor. * * Credits: * * @helgatheviking - Initial concept which has made adding settings in the menu editor in a compatible way. * @kucrut - preg_replace() method so that we no longer have to translate core strings * @danieliser - refactor for less complexity between WP versions & updating versioned classes for proper backward compatibility with the new methods. * * @since WordPress 3.6.0 * @uses Walker_Nav_Menu_Edit */ class Walker_Nav_Menu_Edit_Custom_Fields extends Walker_Nav_Menu_Edit { /** * Start the element output. * * @param string $output Passed by reference. Used to append additional content. * @param object $item Menu item data object. * @param int $depth Depth of menu item. * @param array $args Additional arguments. * @param int $id Id of menu item. * * @see Walker_Nav_Menu_Edit::start_el() */ public function start_el( &$output, $item, $depth = 0, $args = [], $id = 0 ) { $item_output = ''; $output .= parent::start_el( $item_output, $item, $depth, $args, $id ); // NOTE: Check this regex on major WP version updates! $output .= preg_replace( '/(?=]+class="[^"]*field-move)/', $this->get_custom_fields( $item, $depth, $args ), $item_output ); } /** * Get custom fields * * @access protected * * @param object $item Menu item data object. * @param int $depth Depth of menu item. Used for padding. * @param array $args Menu item args. * @param int $id Nav menu ID. * * @return string Form fields * @uses do_action() Calls 'menu_item_custom_fields' hook * * @since 0.1.0 */ protected function get_custom_fields( $item, $depth, $args = [], $id = 0 ) { ob_start(); $item_id = intval( $item->ID ); /** * Get menu item custom fields from plugins/themes * * @param int $item_id post ID of menu * @param object $item Menu item data object. * @param int $depth Depth of menu item. Used for padding. * @param array $args Menu item args. * * @return string Custom fields * @since 0.1.0 */ do_action( 'wp_nav_menu_item_custom_fields', $item_id, $item, $depth, $args ); return ob_get_clean(); } } PK!ͭʞincludes/classes/menu/item.phpnu[ 24, 'redirect_type' => 'current', 'redirect_url' => '', 'which_users' => '', 'can_see' => 'yes', 'roles' => [], ] ); } } PK!Vۅincludes/classes/menu/items.phpnu[ID ) as $key => $value ) { $item->$key = $value; } if ( in_array( $item->object, [ 'login', 'register', 'logout' ], true ) ) { $item->type_label = __( 'User Link', 'user-menus' ); switch ( $item->redirect_type ) { case 'current': $redirect = static::current_url(); break; case 'home': $redirect = home_url(); break; case 'custom': $redirect = $item->redirect_url; break; default: $redirect = ''; break; } switch ( $item->object ) { case 'login': $item->url = wp_login_url( $redirect ); break; case 'register': $item->url = add_query_arg( [ 'redirect_to' => $redirect ], wp_registration_url() ); break; case 'logout': $item->url = wp_logout_url( $redirect ); break; } } // User text replacement. if ( ! is_admin() ) { $item->title = static::user_titles( $item->title ); } return $item; } /** * Get the current url. * * @return string */ public static function current_url() { /* phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated */ $protocol = ( ! empty( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] ) || 443 === $_SERVER['SERVER_PORT'] ? 'https://' : 'http://'; return $protocol . sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) . sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); /* phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotValidated */ } /** * Get replacement titles. * * @param string $title Menu item title. * * @return mixed|string */ public static function user_titles( $title = '' ) { preg_match_all( '/{(.*?)}/', $title, $found ); if ( count( $found[1] ) ) { foreach ( $found[1] as $key => $match ) { $title = static::text_replace( $title, $match ); } } return $title; } /** * Replace text. * * @param string $title Text to search. * @param string $match Strings to match. * * @return mixed|string */ public static function text_replace( $title = '', $match = '' ) { if ( empty( $match ) ) { return $title; } if ( strpos( $match, '||' ) !== false ) { $matches = explode( '||', $match ); } else { $matches = [ $match ]; } $current_user = wp_get_current_user(); $replace = ''; foreach ( $matches as $string ) { if ( ! array_key_exists( $string, Codes::valid_codes() ) ) { // If its not a valid code it is likely a fallback. $replace = $string; } elseif ( 0 === $current_user->ID && array_key_exists( $string, Codes::valid_codes() ) ) { // If the code exists & user is not logged in, return nothing. $replace = ''; } else { switch ( $string ) { case 'avatar': $replace = get_avatar( $current_user->ID, self::$current_item->avatar_size ); break; case 'first_name': $replace = $current_user->user_firstname; break; case 'last_name': $replace = $current_user->user_lastname; break; case 'username': $replace = $current_user->user_login; break; case 'display_name': $replace = $current_user->display_name; break; case 'nickname': $replace = $current_user->nickname; break; case 'email': $replace = $current_user->user_email; break; default: $replace = $string; break; } } // If we found a replacement stop the loop. if ( ! empty( $replace ) ) { break; } } return str_replace( '{' . $match . '}', $replace, $title ); } } Items::init(); PK!ċ(includes/classes/admin/menu-importer.phpnu[ self::get_trigger_group(), 'code' => self::get_trigger_code(), 'pri' => self::get_current_trigger( 'pri' ), 'reason' => 'maybe_later', ] ); try { $user_id = get_current_user_id(); $dismissed_triggers = self::dismissed_triggers(); $dismissed_triggers[ $args['group'] ] = $args['pri']; update_user_meta( $user_id, '_jpum_reviews_dismissed_triggers', $dismissed_triggers ); update_user_meta( $user_id, '_jpum_reviews_last_dismissed', current_time( 'mysql' ) ); switch ( $args['reason'] ) { case 'maybe_later': update_user_meta( $user_id, '_jpum_reviews_last_dismissed', current_time( 'mysql' ) ); break; case 'am_now': case 'already_did': self::already_did( true ); break; } wp_send_json_success(); } catch ( \Exception $e ) { wp_send_json_error( $e ); } } /** * Get review trigger group. * * @return int|string */ public static function get_trigger_group() { static $selected; if ( ! isset( $selected ) ) { $dismissed_triggers = self::dismissed_triggers(); $triggers = self::triggers(); foreach ( $triggers as $g => $group ) { foreach ( $group['triggers'] as $t => $trigger ) { if ( ! in_array( false, $trigger['conditions'], true ) && ( empty( $dismissed_triggers[ $g ] ) || $dismissed_triggers[ $g ] < $trigger['pri'] ) ) { $selected = $g; break; } } if ( isset( $selected ) ) { break; } } } return $selected; } /** * Get review trigger code. * * @return int|string */ public static function get_trigger_code() { static $selected; if ( ! isset( $selected ) ) { $dismissed_triggers = self::dismissed_triggers(); foreach ( self::triggers() as $g => $group ) { foreach ( $group['triggers'] as $t => $trigger ) { if ( ! in_array( false, $trigger['conditions'], true ) && ( empty( $dismissed_triggers[ $g ] ) || $dismissed_triggers[ $g ] < $trigger['pri'] ) ) { $selected = $t; break; } } if ( isset( $selected ) ) { break; } } } return $selected; } /** * Get current trigger. * * @param string $key Trigger key string. * * @return bool|mixed|void */ public static function get_current_trigger( $key = null ) { $group = self::get_trigger_group(); $code = self::get_trigger_code(); if ( ! $group || ! $code ) { return false; } $trigger = self::triggers( $group, $code ); return empty( $key ) ? $trigger : ( isset( $trigger[ $key ] ) ? $trigger[ $key ] : false ); } /** * Returns an array of dismissed trigger groups. * * Array contains the group key and highest priority trigger that has been shown previously for each group. * * $return = array( * 'group1' => 20 * ); * * @return array|mixed */ public static function dismissed_triggers() { $user_id = get_current_user_id(); $dismissed_triggers = get_user_meta( $user_id, '_jpum_reviews_dismissed_triggers', true ); if ( ! $dismissed_triggers ) { $dismissed_triggers = []; } return $dismissed_triggers; } /** * Returns true if the user has opted to never see this again. Or sets the option. * * @param bool $set If set this will mark the user as having opted to never see this again. * * @return bool */ public static function already_did( $set = false ) { $user_id = get_current_user_id(); if ( $set ) { update_user_meta( $user_id, '_jpum_reviews_already_did', true ); return true; } return (bool) get_user_meta( $user_id, '_jpum_reviews_already_did', true ); } /** * Gets a list of triggers. * * @param string $group Group key. * @param string $code Trigger ID. * * @return array */ public static function triggers( $group = null, $code = null ) { static $triggers; if ( ! isset( $triggers ) ) { /* phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment */ $time_message = __( 'Hi there! You\'ve been using the User Menus plugin on your site for %s now - We hope it\'s been helpful. If you\'re enjoying the plugin, would you mind rating it 5-stars to help spread the word?', 'user-menus' ); $triggers = [ 'time_installed' => [ 'triggers' => [ 'one_week' => [ 'message' => sprintf( $time_message, __( '2 weeks', 'user-menus' ) ), 'conditions' => [ strtotime( self::installed_on() . ' +2 weeks' ) < time(), ], 'link' => 'https://wordpress.org/support/plugin/user-menus/reviews/?rate=5#rate-response', 'pri' => 10, ], 'three_months' => [ 'message' => sprintf( $time_message, __( '3 months', 'user-menus' ) ), 'conditions' => [ strtotime( self::installed_on() . ' +3 months' ) < time(), ], 'link' => 'https://wordpress.org/support/plugin/user-menus/reviews/?rate=5#rate-response', 'pri' => 20, ], ], 'pri' => 10, ], ]; $triggers = apply_filters( 'jpum_reviews_triggers', $triggers ); // Sort Groups. uasort( $triggers, [ __CLASS__, 'rsort_by_priority' ] ); // Sort each groups triggers. foreach ( $triggers as $k => $v ) { uasort( $triggers[ $k ]['triggers'], [ __CLASS__, 'rsort_by_priority' ] ); } } if ( isset( $group ) ) { if ( ! isset( $triggers[ $group ] ) ) { return false; } if ( ! isset( $code ) ) { return $triggers[ $group ]; } else { return isset( $triggers[ $group ]['triggers'][ $code ] ) ? $triggers[ $group ]['triggers'][ $code ] : false; } } return $triggers; } /** * Render admin notices if available. */ public static function admin_notices() { if ( self::hide_notices() ) { return; } $group = self::get_trigger_group(); $code = self::get_trigger_code(); $pri = self::get_current_trigger( 'pri' ); $trigger = self::get_current_trigger(); // Used to anonymously distinguish unique site+user combinations in terms of effectiveness of each trigger. $uuid = wp_hash( home_url() . '-' . get_current_user_id() ); ?> time(), empty( $code ), ]; return in_array( true, $conditions, true ); } /** * Gets the last dismissed date. * * @return false|string */ public static function last_dismissed() { $user_id = get_current_user_id(); return get_user_meta( $user_id, '_jpum_reviews_last_dismissed', true ); } /** * Sort array by priority value. * * @param string|int $a The first value to compare. * @param string|int $b The second value to compare. * * @return int */ public static function sort_by_priority( $a, $b ) { if ( ! isset( $a['pri'] ) || ! isset( $b['pri'] ) || $a['pri'] === $b['pri'] ) { return 0; } return ( $a['pri'] < $b['pri'] ) ? - 1 : 1; } /** * Sort array in reverse by priority value * * @param string|int $a The first value to compare. * @param string|int $b The second value to compare. * * @return int */ public static function rsort_by_priority( $a, $b ) { if ( ! isset( $a['pri'] ) || ! isset( $b['pri'] ) || $a['pri'] === $b['pri'] ) { return 0; } return ( $a['pri'] < $b['pri'] ) ? 1 : - 1; } } Reviews::init(); PK!P)1CC(includes/classes/admin/menu-settings.phpnu[ __( 'Everyone', 'user-menus' ), 'logged_out' => __( 'Logged Out Users', 'user-menus' ), 'logged_in' => __( 'Logged In Users', 'user-menus' ), ]; if ( in_array( $item->object, [ 'login', 'register', 'logout' ], true ) ) : $redirect_types = [ 'current' => __( 'Current Page', 'user-menus' ), 'home' => __( 'Home Page', 'user-menus' ), 'custom' => __( 'Custom URL', 'user-menus' ), ]; ?> role_names ); if ( ! is_array( $roles ) || empty( $roles ) ) { $roles = []; } } return $roles; } /** * Save menu item data. * * @param int $menu_id Menu ID. * @param int $item_id Item ID. */ public static function save( $menu_id, $item_id ) { $allowed_roles = static::allowed_user_roles(); if ( empty( $_POST['jp_nav_item_options'][ $item_id ] ) || ! isset( $_POST['jpum-menu-editor-nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['jpum-menu-editor-nonce'] ) ), 'jpum-menu-editor-nonce' ) ) { return; } /* phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized */ $item_options = Item::parse_options( wp_unslash( $_POST['jp_nav_item_options'][ $item_id ] ) ); if ( 'logged_in' === $item_options['which_users'] ) { // Validate chosen roles and remove non-allowed roles. foreach ( (array) $item_options['roles'] as $key => $role ) { if ( ! array_key_exists( $role, $allowed_roles ) ) { unset( $item_options['roles'][ $key ] ); } } } else { unset( $item_options['roles'] ); } // Remove empty options to save space. $item_options = array_filter( $item_options ); if ( ! empty( $item_options ) ) { update_post_meta( $item_id, '_jp_nav_item_options', $item_options ); } else { delete_post_meta( $item_id, '_jp_nav_item_options' ); } } } Menu_Settings::init(); PK! q-&includes/classes/admin/menu-editor.phpnu[=' ), // not sure about this one, was part of the original solution. doing_filter( 'plugins_loaded' ), // No need if its already loaded by another plugin. 'Walker_Nav_Menu_Edit_Custom_Fields' === $walker, ]; if ( in_array( true, $bail_early, true ) ) { return $walker; } // Load custom nav menu walker class for custom field compatibility. if ( ! class_exists( 'Walker_Nav_Menu_Edit_Custom_Fields' ) ) { if ( version_compare( $wp_version, '3.6', '>=' ) ) { require_once \JP_User_Menus::$DIR . 'includes/classes/walker/nav-menu-edit-custom-fields.php'; } else { require_once \JP_User_Menus::$DIR . 'includes/classes/walker/nav-menu-edit-custom-fields-deprecated.php'; } } return 'Walker_Nav_Menu_Edit_Custom_Fields'; } /** * Register metaboxes. */ public static function register_metaboxes() { add_meta_box( 'jp_user_menus', __( 'User Links', 'user-menus' ), [ __CLASS__, 'nav_menu_metabox' ], 'nav-menus', 'side', 'default' ); } /** * Render nav menu metabox. * * @param mixed $object Nav menu object. */ public static function nav_menu_metabox( $object ) { global $_nav_menu_placeholder, $nav_menu_selected_id; $link_types = [ [ 'object' => 'login', 'title' => __( 'Login', 'user-menus' ), ], [ 'object' => 'register', 'title' => __( 'Register', 'user-menus' ), ], [ 'object' => 'logout', 'title' => __( 'Logout', 'user-menus' ), ], ]; foreach ( $link_types as $key => $link ) { $i = isset( $i ) ? $i + 1 : 1; $link_types[ $key ] = (object) array_replace_recursive( [ 'type' => '', 'object' => '', 'title' => '', 'ID' => $i, 'object_id' => $i, 'db_id' => 0, 'post_parent' => 0, 'menu_item_parent' => 0, 'url' => '', 'target' => '', 'attr_title' => '', 'description' => '', 'classes' => [], 'xfn' => '', ], $link ); } $walker = new \Walker_Nav_Menu_Checklist(); $removed_args = [ 'action', 'customlink-tab', 'edit-menu-item', 'menu-item', 'page-tab', '_wpnonce', ]; ?>
    ', '' ) ); ?>
      $walker ] ); ?>

    class="button-secondary submit-add-to-menu right" value="" name="add-user-menus-menu-item" id="submit-user-menus-div" />

    \n" "POT-Creation-Date: 2023-07-19 07:48+0000\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/user-menus/\n" "X-Poedit-Basepath: ..\n" "X-Poedit-KeywordsList: __;_e;_ex:1,2c;_n:1,2;_n_noop:1,2;_nx:1,2,4c;_nx_noop:1,2,3c;_x:1,2c;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n" "X-Poedit-SearchPath-0: .\n" "X-Poedit-SearchPathExcluded-0: *.js\n" "X-Poedit-SourceCharset: UTF-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. translators: 1: Plugin Name, 2: Flagged software (PHP or WP), 3: PHP or WordPress version #: user-menus.php:235 msgid "The %4$s %1$s %5$s plugin requires %2$s version %3$s or greater." msgstr "" #: user-menus.php:238 msgid "Plugin Activation Error" msgstr "" #: includes/classes/admin/menu-editor.php:70 msgid "User Links" msgstr "" #: includes/classes/admin/menu-editor.php:84 msgid "Login" msgstr "" #: includes/classes/admin/menu-editor.php:88 msgid "Register" msgstr "" #: includes/classes/admin/menu-editor.php:92 msgid "Logout" msgstr "" #. translators: 1: link to registration page, 2: closing link tag. #: includes/classes/admin/menu-editor.php:140 msgid "Registration is %1$scurrently disabled%2$s on your site." msgstr "" #: includes/classes/admin/menu-editor.php:161 msgid "Select All" msgstr "" #: includes/classes/admin/menu-editor.php:167 msgid "Add to Menu" msgstr "" #: includes/classes/admin/menu-editor.php:203 msgid "Insert User Menu Codes" msgstr "" #: includes/classes/admin/menu-importer.php:45 msgid "WP Nav Menus" msgstr "" #: includes/classes/admin/menu-importer.php:46 msgid "Import nav menus and other menu item meta skipped by the default importer" msgstr "" #: includes/classes/admin/menu-settings.php:46 msgid "Avatar Size" msgstr "" #: includes/classes/admin/menu-settings.php:58 msgid "Everyone" msgstr "" #: includes/classes/admin/menu-settings.php:59 msgid "Logged Out Users" msgstr "" #: includes/classes/admin/menu-settings.php:60 msgid "Logged In Users" msgstr "" #: includes/classes/admin/menu-settings.php:129, includes/classes/admin/menu-settings.php:105 msgid "Who can see this link?" msgstr "" #: includes/classes/admin/menu-settings.php:147 msgid "Choose which roles can see this link" msgstr "" #: includes/classes/admin/menu-settings.php:154 msgid "Choose which roles won't see this link" msgstr "" #: includes/classes/admin/menu-settings.php:65 msgid "Current Page" msgstr "" #: includes/classes/admin/menu-settings.php:66 msgid "Home Page" msgstr "" #: includes/classes/admin/menu-settings.php:67 msgid "Custom URL" msgstr "" #: includes/classes/admin/menu-settings.php:75 msgid "Where should users be taken afterwards?" msgstr "" #: includes/classes/admin/menu-settings.php:93 msgid "Enter a url user should be redirected to" msgstr "" #: includes/classes/admin/reviews.php:239 msgid "Hi there! You've been using the User Menus plugin on your site for %s now - We hope it's been helpful. If you're enjoying the plugin, would you mind rating it 5-stars to help spread the word?" msgstr "" #: includes/classes/admin/reviews.php:244 msgid "2 weeks" msgstr "" #: includes/classes/admin/reviews.php:252 msgid "3 months" msgstr "" #: includes/classes/admin/reviews.php:398 msgid "Ok, you deserve it" msgstr "" #: includes/classes/admin/reviews.php:403 msgid "Nope, maybe later" msgstr "" #: includes/classes/admin/reviews.php:408 msgid "I already did" msgstr "" #: includes/classes/importer/menu.php:116 msgid "Import Nav Menus" msgstr "" #: includes/classes/importer/menu.php:123 msgid "A new version of this importer is available. Please update to version %s to ensure compatibility with newer export files." msgstr "" #: includes/classes/importer/menu.php:133 msgid "Upload your WordPress export (WXR) file and import the Nav Menus and any meta for the Nav Menu items." msgstr "" #: includes/classes/importer/menu.php:134 msgid "Choose a WXR (.xml) file to upload, then click Upload file and import." msgstr "" #: includes/classes/importer/menu.php:154, includes/classes/importer/menu.php:149, includes/classes/importer/menu.php:164, includes/classes/importer/menu.php:225, includes/classes/importer/menu.php:236 msgid "Sorry, there has been an error." msgstr "" #: includes/classes/importer/menu.php:155 msgid "The export file could not be found at %s. It is likely that this was caused by a permissions problem." msgstr "" #: includes/classes/importer/menu.php:173 msgid "This WXR file (version %s) may not be supported by this version of the importer. Please consider updating." msgstr "" #: includes/classes/importer/menu.php:226 msgid "The file does not exist, please try again." msgstr "" #: includes/classes/importer/menu.php:294 msgid "All done." msgstr "" #: includes/classes/importer/menu.php:294 msgid "Have fun!" msgstr "" #: includes/classes/menu/items.php:51 msgid "User Link" msgstr "" #: includes/classes/user/codes.php:26 msgid "Avatar" msgstr "" #: includes/classes/user/codes.php:27 msgid "First Name" msgstr "" #: includes/classes/user/codes.php:28 msgid "Last Name" msgstr "" #: includes/classes/user/codes.php:29 msgid "Username" msgstr "" #: includes/classes/user/codes.php:30 msgid "Display Name" msgstr "" #: includes/classes/user/codes.php:31 msgid "Nickname" msgstr "" #: includes/classes/user/codes.php:32 msgid "Email" msgstr "" PK!}ʆuser-menus.phpnu[ '3637', 'slug' => 'user-menus', 'type' => 'plugin', 'public_key' => 'pk_367ac2d0a38c35ef2a78d161fed88', 'is_premium' => false, 'has_addons' => false, 'has_paid_plans' => false, 'menu' => [ 'first-path' => 'plugins.php', 'account' => false, 'contact' => false, 'support' => false, ], ] ); } return $um_fs; } // Init Freemius. um_fs(); // Signal that SDK was initiated. do_action( 'um_fs_loaded' ); } /** * Class JP_User_Menus */ class JP_User_Menus { /** * Plugin Name * * @var string */ public static $NAME = 'User Menus'; /** * Plugin Version * * @var string */ public static $VER = '1.3.2'; /** * Minimum PHP version * * @var string */ public static $MIN_PHP_VER = '5.6'; /** * Minimum WP version * * @var string */ public static $MIN_WP_VER = '4.6'; /** * Plugin URL * * @var string */ public static $URL = ''; /** * Plugin Directory * * @var string */ public static $DIR = ''; /** * Plugin File * * @var string */ public static $FILE = ''; /** * Plugin Template Directory * * @var string */ public static $TEMPLATE_PATH = 'jp/user-menus/'; /** * Text Domain * * @var string */ public static $TD = 'user-menus'; /** * Instance of the plugin class * * @var JP_User_Menus $instance The one true JP_User_Menus */ private static $instance; /** * Get active instance * * @access public * @since 1.0.0 * @return object self::$instance The one true JP_User_Menus */ public static function instance() { if ( ! self::$instance ) { self::$instance = new static(); self::$instance->setup_constants(); add_action( 'plugins_loaded', [ self::$instance, 'load_textdomain' ] ); self::$instance->includes(); } return self::$instance; } /** * Setup plugin constants * * @since 1.0.0 */ private function setup_constants() { self::$DIR = self::$instance->plugin_path(); self::$URL = self::$instance->plugin_url(); self::$FILE = __FILE__; } /** * Include necessary files * * @since 1.0.0 */ private function includes() { // Menu Items. require_once self::$DIR . 'includes/classes/menu/item.php'; require_once self::$DIR . 'includes/classes/menu/items.php'; require_once self::$DIR . 'includes/classes/user/codes.php'; if ( is_admin() ) { // Admin Menu Editor. require_once self::$DIR . 'includes/classes/admin/menu-editor.php'; require_once self::$DIR . 'includes/classes/admin/menu-settings.php'; require_once self::$DIR . 'includes/classes/admin/menu-importer.php'; require_once self::$DIR . 'includes/classes/admin/reviews.php'; } else { // Site Menu Filter. require_once self::$DIR . 'includes/classes/site/menus.php'; } } /** * Get the plugin path. * * @return string */ public function plugin_path() { return plugin_dir_path( __FILE__ ); } /** * Get the plugin url. * * @return string */ public function plugin_url() { return plugins_url( '/', __FILE__ ); } /** * Plugin Activation hook function to check for Minimum PHP and WordPress versions */ public static function activation_check() { global $wp_version; if ( version_compare( PHP_VERSION, self::$MIN_PHP_VER, '<' ) ) { $flag = 'PHP'; } elseif ( version_compare( $wp_version, self::$MIN_WP_VER, '<' ) ) { $flag = 'WordPress'; } else { return; } $version = 'PHP' === $flag ? self::$MIN_PHP_VER : self::$MIN_WP_VER; // Deactivate automatically due to insufficient PHP or WP Version. deactivate_plugins( basename( __FILE__ ) ); /* translators: 1: Plugin Name, 2: Flagged software (PHP or WP), 3: PHP or WordPress version */ $notice = sprintf( __( 'The %4$s %1$s %5$s plugin requires %2$s version %3$s or greater.', 'user-menus' ), self::$NAME, $flag, $version, '', '' ); /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ wp_die( sprintf( '

    %s

    ', $notice ), __( 'Plugin Activation Error', 'user-menus' ), [ 'response' => 200, 'back_link' => true, ] ); } /** * Internationalization * * @since 1.0.0 */ public function load_textdomain() { load_plugin_textdomain( 'user-menus' ); } } /** * Globally available function to get plugin instance. * * @return object */ function jp_user_menus() { return JP_User_Menus::instance(); } jp_user_menus(); // Ensure plugin & environment compatibility. register_activation_hook( __FILE__, [ 'JP_User_Menus', 'activation_check' ] ); PK!"a// readme.txtnu[PK!iYe9e9ifreemius/config.phpnu[PK!  "Yfreemius/assets/css/customizer.cssnu[PK!p2WWuefreemius/assets/css/index.phpnu[PK!]k\)ffreemius/assets/css/admin/affiliation.cssnu[PK!zp00% ofreemius/assets/css/admin/account.cssnu[PK!p//%freemius/assets/css/admin/add-ons.cssnu[PK!ղ.ܵfreemius/assets/css/admin/clone-resolution.cssnu[PK!X 00%freemius/assets/css/admin/connect.cssnu[PK!a$(freemius/assets/css/admin/common.cssnu[PK!}Ǿ++#Vfreemius/assets/css/admin/debug.cssnu[PK!J% freemius/assets/css/admin/plugins.cssnu[PK!',,$+ freemius/assets/css/admin/optout.cssnu[PK!p2WW#freemius/assets/css/admin/index.phpnu[PK!U%c7c7*Ufreemius/assets/css/admin/dialog-boxes.cssnu[PK!뀅;;/Vfreemius/assets/css/admin/gdpr-optin-notice.cssnu[PK!_?SS&Wfreemius/assets/css/admin/checkout.cssnu[PK!:0$$#UXfreemius/assets/img/plugin-icon.pngnu[PK!Aw++"L}freemius/assets/img/theme-icon.pngnu[PK!p2WWfreemius/assets/img/index.phpnu[PK!p2WW'freemius/assets/index.phpnu[PK!f y]]-Ǫfreemius/assets/js/nojquery.ba-postmessage.jsnu[PK!p2WWfreemius/assets/js/index.phpnu[PK!Pfreemius/includes/sdk/Exceptions/ArgumentNotExistException.phpnu[PK!#YUT$$;freemius/includes/sdk/Exceptions/EmptyArgumentException.phpnu[PK! 7MM3wfreemius/includes/sdk/Exceptions/OAuthException.phpnu[PK!/7''freemius/includes/fs-core-functions.phpnu[PK! ) ) # freemius/includes/class-fs-lock.phpnu[PK!D0rXrX&freemius/includes/class-fs-storage.phpnu[PK!KK>Ofreemius/includes/supplements/fs-essential-functions-2.2.1.phpnu[PK!Rז4freemius/includes/supplements/fs-migration-2.5.1.phpnu[PK!p2WW'^freemius/includes/supplements/index.phpnu[PK!@ freemius/includes/supplements/fs-essential-functions-1.1.7.1.phpnu[PK!p2WWf%freemius/includes/index.phpnu[PK! llC&freemius/includes/customizer/class-fs-customizer-upsell-control.phpnu[PK!p2WW&:freemius/includes/customizer/index.phpnu[PK!WB B D;freemius/includes/customizer/class-fs-customizer-support-section.phpnu[PK!=] 3JFfreemius/includes/entities/class-fs-plugin-plan.phpnu[PK!96 6 .rQfreemius/includes/entities/class-fs-entity.phpnu[PK!Ly/^freemius/includes/entities/class-fs-billing.phpnu[PK!**4Mdfreemius/includes/entities/class-fs-scope-entity.phpnu[PK!Qaa,ffreemius/includes/entities/class-fs-user.phpnu[PK!7&,lfreemius/includes/entities/class-fs-site.phpnu[PK!!H{vv.freemius/includes/entities/class-fs-plugin.phpnu[PK!73ʙfreemius/includes/entities/class-fs-plugin-info.phpnu[PK!p2WW$freemius/includes/entities/index.phpnu[PK!qK /freemius/includes/entities/class-fs-pricing.phpnu[PK!9n1թfreemius/includes/entities/class-fs-affiliate.phpnu[PK!c[, ` ` 4'freemius/includes/entities/class-fs-subscription.phpnu[PK!7freemius/includes/entities/class-fs-affiliate-terms.phpnu[PK!얀!!6(freemius/includes/entities/class-fs-plugin-license.phpnu[PK!g[/freemius/includes/entities/class-fs-payment.phpnu[PK!# ))2freemius/includes/entities/class-fs-plugin-tag.phpnu[PK!qe//--freemius/includes/class-freemius-abstract.phpnu[PK!B-{8freemius/includes/class-fs-plugin-updater.phpnu[PK!2r7/freemius/includes/managers/class-fs-license-manager.phpnu[PK!'$$58freemius/includes/managers/class-fs-cache-manager.phpnu[PK!>P@H@H<]freemius/includes/managers/class-fs-admin-notice-manager.phpnu[PK!GM  4freemius/includes/managers/class-fs-plan-manager.phpnu[PK!+ 886freemius/includes/managers/class-fs-option-manager.phpnu[PK!p2WW$freemius/includes/managers/index.phpnu[PK!`ee4freemius/includes/managers/class-fs-gdpr-manager.phpnu[PK!&6yfreemius/includes/managers/class-fs-plugin-manager.phpnu[PK!aa:freemius/includes/managers/class-fs-permission-manager.phpnu[PK!~5 }freemius/includes/managers/class-fs-clone-manager.phpnu[PK!9A$((9tfreemius/includes/managers/class-fs-key-value-storage.phpnu[PK!ff:freemius/includes/managers/class-fs-admin-menu-manager.phpnu[PK!X6EBEB&freemius/includes/class-fs-options.phpnu[PK![!ѲAA+Gfreemius/includes/fs-plugin-info-dialog.phpnu[PK!w.w.,freemius/includes/class-fs-admin-notices.phpnu[PK!aTT"hfreemius/includes/class-fs-api.phpnu[PK!/`` freemius/includes/l10n.phpnu[PK!nTfreemius/templates/email.phpnu[PK!_freemius/templates/pricing.phpnu[PK!0 %4freemius/templates/partials/index.phpnu[PK!>0Euu2U5freemius/templates/partials/network-activation.phpnu[PK!1˜!,Ifreemius/templates/powered-by.phpnu[PK!, *Rfreemius/templates/clone-resolution-js.phpnu[PK!i%,,`freemius/templates/checkout.phpnu[PK!ZM* freemius/templates/secure-https-header.phpnu[PK![0Gvfreemius/templates/contact.phpnu[PK!Gao&Mfreemius/templates/debug/api-calls.phpnu[PK!@> 0freemius/templates/debug/plugins-themes-sync.phpnu[PK!ru},freemius/templates/debug/scheduled-crons.phpnu[PK!p2WW"freemius/templates/debug/index.phpnu[PK!.  #freemius/templates/debug/logger.phpnu[PK!t8+freemius/templates/add-trial-to-pricing.phpnu[PK!24o&ffreemius/templates/tabs-capture-js.phpnu[PK!LҐAAfreemius/templates/tabs.phpnu[PK![[6freemius/templates/add-ons.phpnu[PK!Y--6\freemius/templates/forms/subscription-cancellation.phpnu[PK!Z<܋ =freemius/templates/forms/premium-versions-upgrade-handler.phpnu[PK!U881freemius/templates/forms/email-address-update.phpnu[PK!tt(freemius/templates/forms/affiliation.phpnu[PK!>Zfreemius/templates/forms/premium-versions-upgrade-metadata.phpnu[PK!|(`freemius/templates/forms/trial-start.phpnu[PK!p2WW"ufreemius/templates/forms/index.phpnu[PK!| 22,mvfreemius/templates/forms/data-debug-mode.phpnu[PK!l$'freemius/templates/forms/resend-key.phpnu[PK!+R=&&# freemius/templates/forms/optout.phpnu[PK!_ٚXX/freemius/templates/forms/license-activation.phpnu[PK!1afreemius/templates/forms/deactivation/contact.phpnu[PK!˙4&efreemius/templates/forms/deactivation/retry-skip.phpnu[PK!)Yee.jfreemius/templates/forms/deactivation/form.phpnu[PK!p2WW/"freemius/templates/forms/deactivation/index.phpnu[PK!pU !--(freemius/templates/forms/user-change.phpnu[PK!Rf.freemius/templates/plugin-info/screenshots.phpnu[PK!p2WW((freemius/templates/plugin-info/index.phpnu[PK!@ KxNN+freemius/templates/plugin-info/features.phpnu[PK!.freemius/templates/plugin-info/description.phpnu[PK!i/"ffreemius/templates/ajax-loader.phpnu[PK!p2WWfreemius/templates/index.phpnu[PK!"Wfreemius/templates/plugin-icon.phpnu[PK!Y##freemius/templates/connect.phpnu[PK!jŏ.freemius/templates/debug.phpnu[PK!f7__ a freemius/templates/account.phpnu[PK!ō<(`!freemius/templates/auto-installation.phpnu[PK!%YU-}!freemius/templates/sticky-admin-notice-js.phpnu[PK!%ⴕOO,=!freemius/templates/account/partials/site.phpnu[PK!p2WW-.!freemius/templates/account/partials/index.phpnu[PK!i9!freemius/templates/account/partials/disconnect-button.phpnu[PK!GGA!freemius/templates/account/partials/deactivate-license-button.phpnu[PK!MZ!W!W-!freemius/templates/account/partials/addon.phpnu[PK!g]6 6 ?ND"freemius/templates/account/partials/activate-license-button.phpnu[PK!p2WW$M"freemius/templates/account/index.phpnu[PK!ϋ44&N"freemius/templates/account/billing.phpnu[PK!B['"freemius/templates/account/payments.phpnu[PK!+ 0"freemius/templates/connect/permissions-group.phpnu[PK!p2WW$ "freemius/templates/connect/index.phpnu[PK!U)"freemius/templates/connect/permission.phpnu[PK!Hݧ2"freemius/templates/api-connectivity-message-js.phpnu[PK!K3((/"freemius/templates/js/jquery.content-change.phpnu[PK!R9~~1 "freemius/templates/js/open-license-activation.phpnu[PK!p2WW"freemius/templates/js/index.phpnu[PK!Cτ1contributors.txtnu[PK!s_6 >1index.phpnu[PK!mw"?1includes/classes/importer/menu.phpnu[PK!o9S M]1includes/classes/site/menus.phpnu[PK!Rɲ#g1includes/classes/user/codes.phpnu[PK!#l l B$j1includes/classes/walker/nav-menu-edit-custom-fields-deprecated.phpnu[PK!_v v 7t1includes/classes/walker/nav-menu-edit-custom-fields.phpnu[PK!ͭʞ~1includes/classes/menu/item.phpnu[PK!Vۅ˂1includes/classes/menu/items.phpnu[PK!ċ(1includes/classes/admin/menu-importer.phpnu[PK!f.f."1includes/classes/admin/reviews.phpnu[PK!P)1CC(1includes/classes/admin/menu-settings.phpnu[PK! q-&91includes/classes/admin/menu-editor.phpnu[PK!B2!1languages/user-menus.potnu[PK!}ʆ|2user-menus.phpnu[PKP@*2