updated plugin Menu Icons version 0.13.23
This commit is contained in:
184
wp-content/plugins/menu-icons/vendor/codeinwp/themeisle-sdk/AGENTS.md
vendored
Normal file
184
wp-content/plugins/menu-icons/vendor/codeinwp/themeisle-sdk/AGENTS.md
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
# ThemeIsle SDK — Agent Reference
|
||||
|
||||
> Quick-reference guide for AI agents working on this codebase.
|
||||
|
||||
## What This Is
|
||||
|
||||
A shared WordPress library bundled into Themeisle plugins and themes. It provides common features (licensing, analytics, notifications, promotions, etc.) so each product doesn't reimplement them. Multiple products may bundle different versions; only the highest version ever loads.
|
||||
|
||||
## Directory Map
|
||||
|
||||
```
|
||||
themeisle-sdk-main/
|
||||
├── load.php Entry point bundled by each product. Handles version arbitration.
|
||||
├── start.php Bootstrap: requires all class files, calls Loader::init().
|
||||
├── src/
|
||||
│ ├── Loader.php Singleton. Owns $products, $available_modules, $labels.
|
||||
│ ├── Product.php Model for a registered plugin/theme. Reads file headers.
|
||||
│ ├── Common/
|
||||
│ │ ├── Abstract_module.php Base class every module extends.
|
||||
│ │ └── Module_factory.php Instantiates + attaches modules to products.
|
||||
│ └── Modules/ One file per feature module (18 total).
|
||||
├── tests/ PHPUnit tests. One file per module.
|
||||
├── docs/ Integration guides. One file per feature.
|
||||
└── assets/ Compiled JS/CSS for SDK UI components.
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### How Products Register
|
||||
Products add their base file to the `themeisle_sdk_products` filter — that is the *only* required step:
|
||||
|
||||
```php
|
||||
add_filter( 'themeisle_sdk_products', function( $products ) {
|
||||
$products[] = __FILE__;
|
||||
return $products;
|
||||
} );
|
||||
```
|
||||
|
||||
### Product File Headers
|
||||
The SDK reads WordPress file headers to configure itself per product:
|
||||
|
||||
```
|
||||
WordPress Available: yes # yes = on WP.org (free). no = premium only.
|
||||
Requires License: yes # yes = activates the Licenser module.
|
||||
Pro Slug: neve-pro # Slug of the companion pro plugin.
|
||||
```
|
||||
|
||||
### Module Loading Contract
|
||||
Each module in `src/Modules/` extends `Abstract_Module` and implements:
|
||||
- `can_load( $product ) : bool` — Should this module run for this product?
|
||||
- `load( $product ) : self` — Register WordPress hooks.
|
||||
|
||||
`Module_Factory::attach()` calls both methods for every registered module/product pair.
|
||||
|
||||
### Labels (UI Strings)
|
||||
All UI strings are in `Loader::$labels` (see [src/Loader.php](src/Loader.php) lines 73–328). Products and plugins override them via:
|
||||
|
||||
```php
|
||||
add_filter( 'themeisle_sdk_labels', function( $labels ) {
|
||||
$labels['review']['notice'] = __( 'Custom message', 'text-domain' );
|
||||
return $labels;
|
||||
} );
|
||||
```
|
||||
|
||||
The merge logic ensures the first real translation wins; later callbacks cannot overwrite already-translated values.
|
||||
|
||||
## All 18 Modules
|
||||
|
||||
| Module | File | Loads when | Doc |
|
||||
|--------|------|-----------|-----|
|
||||
| `licenser` | `Licenser.php` | `Requires License: yes` in header | [docs/LICENSER.md](docs/LICENSER.md) |
|
||||
| `logger` | `Logger.php` | Always (filterable) | [docs/LOGGER.md](docs/LOGGER.md) |
|
||||
| `notification` | `Notification.php` | Installed >100h, admin user | [docs/NOTIFICATIONS.md](docs/NOTIFICATIONS.md) |
|
||||
| `review` | `Review.php` | `WordPress Available: yes`, not partner | [docs/REVIEW.md](docs/REVIEW.md) |
|
||||
| `promotions` | `Promotions.php` | Not partner, not recently dismissed | [docs/PROMOTIONS.md](docs/PROMOTIONS.md) |
|
||||
| `rollback` | `Rollback.php` | Always | [docs/ROLLBACK.md](docs/ROLLBACK.md) |
|
||||
| `uninstall_feedback` | `Uninstall_feedback.php` | Always | [docs/UNINSTALL-FEEDBACK.md](docs/UNINSTALL-FEEDBACK.md) |
|
||||
| `about_us` | `About_us.php` | `{key}_about_us_metadata` filter returns data | [docs/ABOUT-US.md](docs/ABOUT-US.md) |
|
||||
| `float_widget` | `Float_widget.php` | `{key}_float_widget_metadata` filter returns data | [docs/FLOAT-WIDGET.md](docs/FLOAT-WIDGET.md) |
|
||||
| `announcements` | `Announcements.php` | Not partner | [docs/ANNOUNCEMENTS.md](docs/ANNOUNCEMENTS.md) |
|
||||
| `welcome` | `Welcome.php` | `{key}_welcome_metadata` filter returns enabled data | [docs/WELCOME.md](docs/WELCOME.md) |
|
||||
| `compatibilities` | `Compatibilities.php` | Not partner, admin user | [docs/COMPATIBILITIES.md](docs/COMPATIBILITIES.md) |
|
||||
| `dashboard_widget` | `Dashboard_widget.php` | Not partner | — |
|
||||
| `featured_plugins` | `Featured_plugins.php` | Always | — |
|
||||
| `recommendation` | `Recommendation.php` | Always | — |
|
||||
| `script_loader` | `Script_loader.php` | Always | [docs/TELEMETRY.md](docs/TELEMETRY.md) |
|
||||
| `translate` | `Translate.php` | Always | — |
|
||||
| `translations` | `Translations.php` | Always | — |
|
||||
|
||||
## Common Filter Reference
|
||||
|
||||
| Filter | Purpose |
|
||||
|--------|---------|
|
||||
| `themeisle_sdk_products` | Register a product base file |
|
||||
| `themeisle_sdk_labels` | Override any UI string |
|
||||
| `themeisle_sdk_modules` | Add custom module names |
|
||||
| `themeisle_sdk_required_files` | Add custom module PHP files |
|
||||
| `themeisle_sdk_enable_telemetry` | Enable JS telemetry (return `true`) |
|
||||
| `themeisle_sdk_disable_telemetry` | Disable all telemetry (return `true`) |
|
||||
| `themeisle_sdk_hide_notifications` | Suppress all admin notices |
|
||||
| `themeisle_sdk_is_black_friday_sale` | Force Black Friday banner on/off |
|
||||
| `themeisle_sdk_promo_debug` | Force promotions to show (dev only) |
|
||||
| `themeisle_sdk_welcome_debug` | Force welcome notice to show (dev only) |
|
||||
| `themeisle_sdk_current_date` | Override current date (useful in tests) |
|
||||
| `{product_key}_about_us_metadata` | Configure About Us page |
|
||||
| `{product_key}_float_widget_metadata` | Configure floating help widget |
|
||||
| `{product_key}_welcome_metadata` | Configure welcome/upgrade notice |
|
||||
| `{product_key}_load_promotions` | Add promotion slugs for this product |
|
||||
| `{product_key}_dissallowed_promotions` | Block specific promotion slugs |
|
||||
| `{product_slug}_sdk_enable_logger` | Enable/disable logger for product |
|
||||
| `{product_slug}_sdk_should_review` | Enable/disable review prompt |
|
||||
| `{product_key}_enable_licenser` | Enable/disable licenser module |
|
||||
| `{product_key}_hide_license_field` | Hide license field on settings page |
|
||||
| `{product_key}_hide_license_notices` | Suppress license admin notices |
|
||||
| `themeisle_sdk_compatibilities/{slug}` | Declare version compatibility requirements |
|
||||
| `themesle_sdk_namespace_{md5(basefile)}` | Set product namespace for license filters |
|
||||
| `themeisle_sdk_license_process_{ns}` | Trigger license activate/deactivate |
|
||||
| `product_{ns}_license_status` | Read license status |
|
||||
| `product_{ns}_license_key` | Read license key |
|
||||
| `product_{ns}_license_plan` | Read license plan/price ID |
|
||||
| `tsdk_utmify_{content}` | Override UTM params for a URL |
|
||||
| `tsdk_utmify_url_{content}` | Override final UTM-ified URL |
|
||||
|
||||
## Global Helper Functions
|
||||
|
||||
Defined in `load.php`, available everywhere after `init`:
|
||||
|
||||
```php
|
||||
tsdk_utmify( $url, $area, $location ) // Append UTM params
|
||||
tsdk_lstatus( $file ) // License status string
|
||||
tsdk_lis_valid( $file ) // bool — is license valid?
|
||||
tsdk_lplan( $file ) // int — license price_id
|
||||
tsdk_lkey( $file ) // string — license key
|
||||
tsdk_translate_link( $url, $type, $langs ) // Localize a URL
|
||||
tsdk_support_link( $file ) // Pre-filled support URL or false
|
||||
```
|
||||
|
||||
## Options Written by the SDK
|
||||
|
||||
All options use `{product_key}` where key = slug with hyphens replaced by underscores.
|
||||
|
||||
| Option | Content |
|
||||
|--------|---------|
|
||||
| `{key}_install` | Unix timestamp of first activation |
|
||||
| `{key}_version` | Last known product version |
|
||||
| `{key}_license` | Raw license key (free products) |
|
||||
| `{key}_license_data` | JSON object from license API |
|
||||
| `{key}_license_status` | `valid` \| `not_active` \| `active_expired` |
|
||||
| `{key}_logger_flag` | `yes` \| `no` |
|
||||
| `themeisle_sdk_notifications` | Notification queue metadata |
|
||||
| `themeisle_sdk_promotions` | Promotion dismiss timestamps |
|
||||
| `themeisle_sdk_promotions_{promo}_installed` | Whether a promoted plugin was installed |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
```
|
||||
https://api.themeisle.com/license/check/{product}/{key}/{url}/{token}
|
||||
https://api.themeisle.com/license/activate/{product}/{key}
|
||||
https://api.themeisle.com/license/deactivate/{product}/{key}
|
||||
https://api.themeisle.com/license/version/{product}/{key}/{version}/{url}
|
||||
https://api.themeisle.com/license/versions/{product}/{key}/{url}/{version}
|
||||
https://api.themeisle.com/tracking/log
|
||||
https://api.themeisle.com/tracking/events
|
||||
https://api.themeisle.com/tracking/uninstall
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
composer install
|
||||
./vendor/bin/phpunit
|
||||
```
|
||||
|
||||
Test files mirror module names: `tests/licenser-test.php`, `tests/loader-test.php`, etc.
|
||||
Sample products used in tests live in `tests/sample_products/`.
|
||||
|
||||
## Adding a New Module
|
||||
|
||||
1. Create `src/Modules/My_Feature.php` extending `Abstract_Module`
|
||||
2. Implement `can_load()` and `load()`
|
||||
3. Add `'my_feature'` to `$available_modules` in `Loader.php`
|
||||
4. Add the file path to the `$files_to_load` array in `start.php`
|
||||
5. Add a test in `tests/my-feature-test.php`
|
||||
6. Add a doc in `docs/MY-FEATURE.md`
|
||||
@ -1,3 +1,38 @@
|
||||
##### [Version 3.3.51](https://github.com/Codeinwp/themeisle-sdk-main/compare/v3.3.50...v3.3.51) (2026-03-30)
|
||||
|
||||
- Add SDK docs
|
||||
- Add Migration Module
|
||||
- Update Black Friday module
|
||||
- Update the labels for the sharing data notice
|
||||
- Update the design of the expired notice
|
||||
|
||||
##### [Version 3.3.50](https://github.com/Codeinwp/themeisle-sdk-main/compare/v3.3.49...v3.3.50) (2025-11-25)
|
||||
|
||||
> Things are getting better every day. 🚀
|
||||
|
||||
##### [Version 3.3.49](https://github.com/Codeinwp/themeisle-sdk-main/compare/v3.3.48...v3.3.49) (2025-09-18)
|
||||
|
||||
> Things are getting better every day. 🚀
|
||||
|
||||
##### [Version 3.3.48](https://github.com/Codeinwp/themeisle-sdk-main/compare/v3.3.47...v3.3.48) (2025-08-11)
|
||||
|
||||
Development
|
||||
|
||||
##### [Version 3.3.47](https://github.com/Codeinwp/themeisle-sdk-main/compare/v3.3.46...v3.3.47) (2025-07-21)
|
||||
|
||||
- Fixed review link
|
||||
- Fixed plugins ranking
|
||||
|
||||
##### [Version 3.3.46](https://github.com/Codeinwp/themeisle-sdk-main/compare/v3.3.45...v3.3.46) (2025-05-16)
|
||||
|
||||
- Add masteriyo recommandation
|
||||
- Add bf helpers
|
||||
- update formbricks
|
||||
|
||||
##### [Version 3.3.45](https://github.com/Codeinwp/themeisle-sdk-main/compare/v3.3.44...v3.3.45) (2025-04-28)
|
||||
|
||||
- feat: add review_link param to about_us filter
|
||||
|
||||
##### [Version 3.3.44](https://github.com/Codeinwp/themeisle-sdk-main/compare/v3.3.43...v3.3.44) (2025-02-18)
|
||||
|
||||
- Fix variable mismatch in the install category function.
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
.tsdk-banner-cta {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tsdk-banner-urgency-text {
|
||||
position: absolute;
|
||||
top: 6%;
|
||||
left: 1%;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
font-size: 16px;
|
||||
z-index: 10;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tsdk-banner-img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.tsdk-banner-urgency-text {
|
||||
font-size: 10px;
|
||||
top: 0;
|
||||
left: 1%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
.tsdk-banner-urgency-text {
|
||||
font-size: 10px;
|
||||
left: 1%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.tsdk-banner-urgency-text {
|
||||
font-size: 6px;
|
||||
top: -10%;
|
||||
left: 1%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.tsdk-banner-urgency-text {
|
||||
left: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
.notice:not(#tsdk_banner) {
|
||||
display: none;
|
||||
}
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'wp-components', 'wp-element'), 'version' => '7d782affa8469fa8f48d');
|
||||
<?php return array('dependencies' => array('react', 'wp-components', 'wp-element'), 'version' => '8d9c74cada5a40e4082b');
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
<?php return array('dependencies' => array(), 'version' => '9c795bb600f6ae533935');
|
||||
@ -1 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded",(()=>{document.dispatchEvent(new Event("themeisle:banner:init"))})),document.addEventListener("themeisle:banner:init",(()=>{!function(){if(void 0===window.tsdk_banner_data)return;const n=document.getElementById("tsdk_banner");n&&(n.innerHTML=window.tsdk_banner_data.content)}()}));
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-components', 'wp-compose', 'wp-data', 'wp-edit-post', 'wp-element', 'wp-hooks', 'wp-plugins'), 'version' => '3997ba6be36742082cb2');
|
||||
<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-components', 'wp-compose', 'wp-data', 'wp-edit-post', 'wp-element', 'wp-hooks', 'wp-plugins'), 'version' => '1e086c2d21f2850672d5');
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => '92a432317d1433f31603');
|
||||
<?php return array('dependencies' => array(), 'version' => '44eb6f2a376d36991f76');
|
||||
|
||||
@ -1 +1 @@
|
||||
(()=>{"use strict";let i=!1,r=!1;const o=[],t=new Proxy({},{get:(t,e,n)=>(...t)=>(async(t,...e)=>{if(r){if(window.formbricks){const i=t;await window.formbricks[i](...e)}}else if("init"===t){if(i)return void console.warn("🧱 Formbricks - Warning: Formbricks is already initializing.");i=!0;const t=e[0].apiHost;if((await(async i=>{if(!window.formbricks){const r=document.createElement("script");r.type="text/javascript",r.src=`${i}/js/formbricks.umd.cjs`,r.async=!0;const o=async()=>new Promise(((i,o)=>{const t=setTimeout((()=>{o(new Error("Formbricks SDK loading timed out"))}),1e4);r.onload=()=>{clearTimeout(t),i()},r.onerror=()=>{clearTimeout(t),o(new Error("Failed to load Formbricks SDK"))}}));document.head.appendChild(r);try{return await o(),{ok:!0,data:void 0}}catch(i){return{ok:!1,error:new Error(i.message??"Failed to load Formbricks SDK")}}}return{ok:!0,data:void 0}})(t)).ok&&window.formbricks){window.formbricks.init(...e),i=!1,r=!0;for(const{prop:i,args:r}of o)"function"==typeof window.formbricks[i]?window.formbricks[i](...r):console.error(`🧱 Formbricks - Error: Method ${i} does not exist on formbricks`)}}else console.warn("🧱 Formbricks - Warning: Formbricks not initialized. This method will be queued and executed after initialization."),o.push({prop:t,args:e})})(e,...t)});document.addEventListener("DOMContentLoaded",(()=>{window.tsdk_formbricks={init:i=>{var r,o;"object"==typeof i&&null!==i||(i={});const e={...window.tsdk_survey_data,...i,attributes:{...null!==(r=window.tsdk_survey_data.attributes)&&void 0!==r?r:{},...null!==(o=i.attributes)&&void 0!==o?o:{}}};t?.init(e)}};let i=null;var r;r=window.tsdk_survey_data?.attributes?.install_days_number,isNaN(r)||"boolean"==typeof r||(i=setTimeout((()=>{window.tsdk_formbricks?.init()}),350)),window.addEventListener("themeisle:survey:trigger:cancel",(()=>{clearTimeout(i)})),window.dispatchEvent(new Event("themeisle:survey:loaded"))}))})();
|
||||
(()=>{"use strict";let r=!1,e=!1;const t=[],o=new Proxy({},{get:(o,i,n)=>(...o)=>(async(o,...i)=>{if(e){if(window.formbricks){const r=window.formbricks,e=o;await r[e](...i)}}else if("setup"===o){if(r)return void console.warn("🧱 Formbricks - Warning: Formbricks is already initializing.");r=!0;const o=i[0],{appUrl:n,environmentId:s}=o;if(!n)return void console.error("🧱 Formbricks - Error: appUrl is required");if(!s)return void console.error("🧱 Formbricks - Error: environmentId is required");if((await(async r=>{if(!window.formbricks){const e=document.createElement("script");e.type="text/javascript",e.src=`${r}/js/formbricks.umd.cjs`,e.async=!0;const t=async()=>new Promise(((r,t)=>{const o=setTimeout((()=>{t(new Error("Formbricks SDK loading timed out"))}),1e4);e.onload=()=>{clearTimeout(o),r()},e.onerror=()=>{clearTimeout(o),t(new Error("Failed to load Formbricks SDK"))}}));document.head.appendChild(e);try{return await t(),{ok:!0,data:void 0}}catch(r){return{ok:!1,error:new Error(r.message??"Failed to load Formbricks SDK")}}}return{ok:!0,data:void 0}})(n)).ok&&window.formbricks){const o=window.formbricks;o.setup(...i),r=!1,e=!0;for(const{prop:r,args:e}of t)"function"==typeof o[r]?o[r](...e):console.error(`🧱 Formbricks - Error: Method ${r} does not exist on formbricks`)}}else console.warn("🧱 Formbricks - Warning: Formbricks not initialized. This method will be queued and executed after initialization."),t.push({prop:o,args:i})})(i,...o)});document.addEventListener("DOMContentLoaded",(()=>{window.tsdk_formbricks={init:async r=>{var e,t;"object"==typeof r&&null!==r||(r={});const i={...window.tsdk_survey_data,...r,attributes:{...null!==(e=window.tsdk_survey_data.attributes)&&void 0!==e?e:{},...null!==(t=r.attributes)&&void 0!==t?t:{}}},{environmentId:n,appUrl:s,attributes:a,userId:d}=i;await(o?.setup({environmentId:n,appUrl:s})),o?.setAttributes(a),function(){const r=localStorage.getItem("formbricks-js");if(!r)return!0;try{const e=JSON.parse(r);if(e?.user?.data?.userId)return!1}catch(r){console.warn(r)}return!0}()&&o?.setUserId(d)}};let r=null;var e;e=window.tsdk_survey_data?.attributes?.install_days_number,isNaN(e)||"boolean"==typeof e||(r=setTimeout((()=>{window.tsdk_formbricks?.init()}),350)),window.addEventListener("themeisle:survey:trigger:cancel",(()=>{clearTimeout(r)})),window.dispatchEvent(new Event("themeisle:survey:loaded"))}))})();
|
||||
@ -14,7 +14,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
return;
|
||||
}
|
||||
// Current SDK version and path.
|
||||
$themeisle_sdk_version = '3.3.44';
|
||||
$themeisle_sdk_version = '3.3.51';
|
||||
$themeisle_sdk_path = dirname( __FILE__ );
|
||||
|
||||
global $themeisle_sdk_max_version;
|
||||
|
||||
@ -41,6 +41,7 @@ abstract class Abstract_Module {
|
||||
'templates-patterns-collection' => 'templates-patterns-collection/templates-patterns-collection.php',
|
||||
'wpcf7-redirect' => 'wpcf7-redirect/wpcf7-redirect.php',
|
||||
'wp-full-stripe-free' => 'wp-full-stripe-free/wp-full-stripe.php',
|
||||
'learning-management-system' => 'learning-management-system/lms.php',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -220,4 +221,13 @@ abstract class Abstract_Module {
|
||||
|
||||
return is_plugin_active( $plugin );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current date.
|
||||
*
|
||||
* @return \DateTime The date time.
|
||||
*/
|
||||
public function get_current_date() {
|
||||
return apply_filters( 'themeisle_sdk_current_date', new \DateTime( 'now' ) );
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +64,7 @@ final class Loader {
|
||||
'announcements',
|
||||
'featured_plugins',
|
||||
'float_widget',
|
||||
'migrator',
|
||||
];
|
||||
/**
|
||||
* Holds the labels for the modules.
|
||||
@ -72,10 +73,11 @@ final class Loader {
|
||||
*/
|
||||
public static $labels = [
|
||||
'announcements' => [
|
||||
'hurry_up' => 'Hurry up! Only %s left.',
|
||||
'sale_live' => 'Themeisle Black Friday Sale is Live!',
|
||||
'learn_more' => 'Learn more',
|
||||
'max_savings' => 'Enjoy Maximum Savings on %s',
|
||||
'notice_link_label' => 'See the deals',
|
||||
'max_savings' => 'Best WordPress Black Friday deals of %s — themes, plugins, hosting. Curated by the Themeisle team.',
|
||||
'black_friday' => 'Black Friday Sale',
|
||||
'time_left' => '%s left',
|
||||
'plugin_meta_message' => 'Black Friday Sale - 60% OFF',
|
||||
],
|
||||
'compatibilities' => [
|
||||
'notice' => '%s requires a newer version of %s. Please %supdate%s %s %s to the latest version.',
|
||||
@ -108,10 +110,14 @@ final class Loader {
|
||||
'valid' => 'Valid',
|
||||
'invalid' => 'Invalid',
|
||||
'notice' => 'Enter your license from %s purchase history in order to get %s updates',
|
||||
'expired' => 'Your %s\'s License Key has expired. In order to continue receiving support and software updates you must %srenew%s your license key.',
|
||||
'expired' => '%s license expired',
|
||||
'expired_date' => 'Expired on %s',
|
||||
'expired_notice' => 'Your current setup continues working, but premium features are disabled and you\'re no longer receive updates - including critical patches - or support.',
|
||||
|
||||
'inactive' => 'In order to benefit from updates and support for %s, please add your license code from your %spurchase history%s and validate it %shere%s.',
|
||||
'no_activations' => 'No more activations left for %s. You need to upgrade your plan in order to use %s on more websites. If you need assistance, please get in touch with %s staff.',
|
||||
'renew_license' => 'Renew License',
|
||||
'learn_more' => 'Learn More',
|
||||
],
|
||||
'promotions' => [
|
||||
'recommended' => 'Recommended by %s',
|
||||
@ -172,6 +178,12 @@ final class Loader {
|
||||
'dismisscta' => 'Dismiss this notice.',
|
||||
'message' => 'Enhance your donation page with WP Full Pay—create custom Stripe forms for one-time and recurring donations, manage transactions easily, and boost support with a seamless setup.',
|
||||
],
|
||||
'masteriyo' => [
|
||||
'gotodash' => 'Go to Masteriyo Dashboard',
|
||||
'install' => 'Install Masteriyo',
|
||||
'dismisscta' => 'Dismiss this notice.',
|
||||
'message' => 'Transform your site into a learning hub with Masteriyo LMS. Build engaging courses with intuitive tools, track student progress effortlessly, and grow your education business with powerful marketing features and seamless payment integration.',
|
||||
],
|
||||
],
|
||||
'welcome' => [
|
||||
'ctan' => 'No, thanks.',
|
||||
@ -243,9 +255,9 @@ final class Loader {
|
||||
'cta' => 'Rollback to v%s',
|
||||
],
|
||||
'logger' => [
|
||||
'notice' => 'Do you enjoy <b>{product}</b>? Become a contributor by opting in to our anonymous data tracking. We guarantee no sensitive data is collected.',
|
||||
'cta_y' => 'Sure, I would love to help.',
|
||||
'cta_n' => 'No, thanks.',
|
||||
'notice' => 'Help improve <b>{product}</b> by sharing anonymous usage data about your setup. No personal data collected.',
|
||||
'cta_y' => 'Count me in',
|
||||
'cta_n' => 'No thanks',
|
||||
],
|
||||
'about_us' => [
|
||||
'title' => 'About Us',
|
||||
@ -325,10 +337,7 @@ final class Loader {
|
||||
* Initialize the sdk logic.
|
||||
*/
|
||||
public static function init() {
|
||||
/**
|
||||
* This filter can be used to localize the labels inside each product.
|
||||
*/
|
||||
self::$labels = apply_filters( 'themeisle_sdk_labels', self::$labels );
|
||||
self::localize_labels();
|
||||
if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Loader ) ) {
|
||||
self::$instance = new Loader();
|
||||
$modules = array_merge( self::$available_modules, apply_filters( 'themeisle_sdk_modules', [] ) );
|
||||
@ -338,8 +347,92 @@ final class Loader {
|
||||
}
|
||||
}
|
||||
self::$available_modules = $modules;
|
||||
|
||||
add_action( 'themeisle_sdk_first_activation', array( __CLASS__, 'activate' ) );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize the labels.
|
||||
*/
|
||||
public static function localize_labels() {
|
||||
$originals = self::$labels;
|
||||
$all_translations = [];
|
||||
|
||||
global $wp_filter;
|
||||
if ( isset( $wp_filter['themeisle_sdk_labels'] ) ) {
|
||||
foreach ( $wp_filter['themeisle_sdk_labels']->callbacks as $priority => $hooks ) {
|
||||
foreach ( $hooks as $hook ) {
|
||||
// Each callback gets fresh originals, not previous callback's output
|
||||
$result = call_user_func( $hook['function'], $originals );
|
||||
$all_translations[] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the filter so it doesn't run again via apply_filters
|
||||
remove_all_filters( 'themeisle_sdk_labels' );
|
||||
}
|
||||
|
||||
// Merge all results, first real translation wins
|
||||
self::$labels = self::merge_all_translations( $originals, $all_translations );
|
||||
}
|
||||
/**
|
||||
* Merge all translations.
|
||||
*
|
||||
* @param array $originals The original labels.
|
||||
* @param array $all_translations The all translations.
|
||||
*
|
||||
* @return array The merged labels.
|
||||
*/
|
||||
private static function merge_all_translations( $originals, $all_translations ) {
|
||||
$result = $originals;
|
||||
|
||||
foreach ( $all_translations as $translations ) {
|
||||
$result = self::merge_if_translated( $result, $translations, $originals );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Merge if translated.
|
||||
*
|
||||
* @param array $current The current labels.
|
||||
* @param array $new The new labels.
|
||||
* @param array $originals The original labels.
|
||||
* @return array The merged labels.
|
||||
*/
|
||||
private static function merge_if_translated( $current, $new, $originals ) {
|
||||
foreach ( $new as $key => $value ) {
|
||||
if ( ! isset( $originals[ $key ] ) ) {
|
||||
// New key, accept it
|
||||
if ( ! isset( $current[ $key ] ) ) {
|
||||
$current[ $key ] = $value;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( is_array( $value ) && is_array( $originals[ $key ] ) ) {
|
||||
$current[ $key ] = self::merge_if_translated(
|
||||
$current[ $key ],
|
||||
$value,
|
||||
$originals[ $key ]
|
||||
);
|
||||
} else {
|
||||
// Only accept if:
|
||||
// 1. New value is actually translated (differs from original)
|
||||
// 2. Current value is NOT already translated
|
||||
$is_new_translated = ( $value !== $originals[ $key ] );
|
||||
$is_current_untranslated = ( $current[ $key ] === $originals[ $key ] );
|
||||
|
||||
if ( $is_new_translated && $is_current_untranslated ) {
|
||||
$current[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache token used in API requests.
|
||||
@ -384,6 +477,28 @@ final class Loader {
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the product routine.
|
||||
*
|
||||
* @param string $file The base file of the product.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function activate( $file ) {
|
||||
|
||||
$dirname = trailingslashit( dirname( ( $file ) ) );
|
||||
if ( ! file_exists( $dirname . '_reference.php' ) ) {
|
||||
return;
|
||||
}
|
||||
$reference_data = require_once $dirname . '_reference.php';
|
||||
if ( ! is_array( $reference_data ) ||
|
||||
! isset( $reference_data['key'] ) ||
|
||||
! isset( $reference_data['value'] ) ||
|
||||
! preg_match( '/^[a-zA-Z0-9_]+_reference_key$/', $reference_data['key'] ) ) {
|
||||
return;
|
||||
}
|
||||
add_option( $reference_data['key'], sanitize_key( $reference_data['value'] ) );
|
||||
}
|
||||
/**
|
||||
* Get all registered modules by the SDK.
|
||||
*
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
* 'has_upgrade_menu' => <condition>,
|
||||
* 'upgrade_link' => <url>,
|
||||
* 'upgrade_text' => 'Get Pro Version',
|
||||
* 'review_link' => false, // Leave it empty for default WPorg link or false to hide it.
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
@ -95,6 +96,9 @@ class About_Us extends Abstract_Module {
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh the about data to get the latest changes.
|
||||
$this->about_data = apply_filters( $this->product->get_key() . '_about_us_metadata', array() );
|
||||
|
||||
add_submenu_page(
|
||||
$this->about_data['location'],
|
||||
Loader::$labels['about_us']['title'],
|
||||
@ -182,6 +186,8 @@ class About_Us extends Abstract_Module {
|
||||
$asset_file = require $themeisle_sdk_max_path . '/assets/js/build/about/about.asset.php';
|
||||
$deps = array_merge( $asset_file['dependencies'], [ 'updates' ] );
|
||||
|
||||
do_action( 'themeisle_internal_page', $this->product->get_slug(), 'about_us' );
|
||||
|
||||
wp_register_script( $handle, $this->get_sdk_uri() . 'assets/js/build/about/about.js', $deps, $asset_file['version'], true );
|
||||
wp_localize_script( $handle, 'tiSDKAboutData', $this->get_about_localization_data() );
|
||||
|
||||
@ -228,6 +234,7 @@ class About_Us extends Abstract_Module {
|
||||
],
|
||||
'canInstallPlugins' => current_user_can( 'install_plugins' ),
|
||||
'canActivatePlugins' => current_user_can( 'activate_plugins' ),
|
||||
'showReviewLink' => ! ( isset( $this->about_data['review_link'] ) && false === $this->about_data['review_link'] ),
|
||||
];
|
||||
}
|
||||
|
||||
@ -334,6 +341,9 @@ class About_Us extends Abstract_Module {
|
||||
'description' => Loader::$labels['about_us']['others']['neve_desc'],
|
||||
'icon' => $this->get_sdk_uri() . 'assets/images/neve.png',
|
||||
],
|
||||
'learning-management-system' => [
|
||||
'name' => 'Masteriyo LMS',
|
||||
],
|
||||
'otter-blocks' => [
|
||||
'name' => 'Otter',
|
||||
],
|
||||
|
||||
88
wp-content/plugins/menu-icons/vendor/codeinwp/themeisle-sdk/src/Modules/Abstract_Migration.php
vendored
Normal file
88
wp-content/plugins/menu-icons/vendor/codeinwp/themeisle-sdk/src/Modules/Abstract_Migration.php
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* The abstract migration class for ThemeIsle SDK.
|
||||
*
|
||||
* @package ThemeIsleSDK
|
||||
* @subpackage Modules
|
||||
* @copyright Copyright (c) 2024, Themeisle
|
||||
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
|
||||
* @since 3.3.50
|
||||
*/
|
||||
|
||||
namespace ThemeisleSDK\Modules;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for SDK migrations.
|
||||
*
|
||||
* Migration files should return an anonymous class instance extending this class:
|
||||
*
|
||||
* return new class extends \ThemeisleSDK\Modules\Abstract_Migration {
|
||||
* public function up() { ... }
|
||||
* };
|
||||
*/
|
||||
abstract class Abstract_Migration {
|
||||
/**
|
||||
* WordPress database object.
|
||||
*
|
||||
* @var \wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* WordPress table prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix;
|
||||
|
||||
/**
|
||||
* WordPress charset and collation string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $charset_collate;
|
||||
|
||||
/**
|
||||
* Constructor. Populates database helpers.
|
||||
*/
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$this->wpdb = $wpdb;
|
||||
$this->prefix = $wpdb->prefix;
|
||||
$this->charset_collate = $wpdb->get_charset_collate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migration.
|
||||
*/
|
||||
abstract public function up();
|
||||
|
||||
/**
|
||||
* Reverse the migration.
|
||||
*
|
||||
* Override in concrete migrations to undo what up() did. Called by
|
||||
* Migrator::rollback() — never invoked automatically.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down() {
|
||||
// No-op by default. Override to implement rollback logic.
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this migration should run.
|
||||
*
|
||||
* Override to add a custom idempotency check beyond name-based tracking.
|
||||
* Return false to skip the migration without recording it.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_run() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,7 @@
|
||||
|
||||
namespace ThemeisleSDK\Modules;
|
||||
|
||||
use DateTime;
|
||||
use ThemeisleSDK\Common\Abstract_Module;
|
||||
use ThemeisleSDK\Loader;
|
||||
use ThemeisleSDK\Product;
|
||||
@ -22,56 +23,29 @@ use ThemeisleSDK\Product;
|
||||
*/
|
||||
class Announcements extends Abstract_Module {
|
||||
|
||||
const SALE_DURATION_BLACK_FRIDAY = '+7 days'; // DateTime modifier. (Include Cyber Monday)
|
||||
const MINIMUM_INSTALL_AGE = 3 * DAY_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* Holds the timeline for the announcements.
|
||||
* Mark if the notice was already loaded.
|
||||
*
|
||||
* @var array
|
||||
* @var boolean
|
||||
*/
|
||||
private static $timeline = array(
|
||||
'black_friday' => array(
|
||||
'start' => '2024-11-25 00:00:00',
|
||||
'end' => '2024-12-03 23:59:59',
|
||||
'rendered' => false,
|
||||
),
|
||||
);
|
||||
private static $notice_loaded = false;
|
||||
|
||||
/**
|
||||
* Mark is a banner for a product was already loaded.
|
||||
* Mark if the plugin meta link was already loaded.
|
||||
*
|
||||
* @var array
|
||||
* @var boolean
|
||||
*/
|
||||
private static $banner_loaded = array();
|
||||
|
||||
const PLUGIN_PAGE = 'https://themeisle.com/plugins';
|
||||
const THEME_PAGE = 'https://themeisle.com/themes';
|
||||
const REVIVE_SOCIAL = 'https://revive.social/plugins';
|
||||
private static $meta_link_loaded = false;
|
||||
|
||||
/**
|
||||
* Holds the option prefix for the announcements.
|
||||
*
|
||||
* This is used to store the dismiss date for each announcement.
|
||||
* The product to be used.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $option_prefix = 'themeisle_sdk_announcement_';
|
||||
|
||||
/**
|
||||
* Holds the time for the current request.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $time = '';
|
||||
|
||||
/**
|
||||
* Constructor for the Announcements module.
|
||||
*
|
||||
* @param array $timeline Optional. An array representing the timeline of announcements. Default is an empty array.
|
||||
*/
|
||||
public function __construct( $timeline = array() ) {
|
||||
if ( is_array( $timeline ) && ! empty( $timeline ) ) {
|
||||
self::$timeline = $timeline;
|
||||
}
|
||||
}
|
||||
private static $current_product = '';
|
||||
|
||||
/**
|
||||
* Check if the module can be loaded.
|
||||
@ -96,16 +70,17 @@ class Announcements extends Abstract_Module {
|
||||
* @return void
|
||||
*/
|
||||
public function load( $product ) {
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->product = $product;
|
||||
|
||||
add_action( 'admin_init', array( $this, 'load_announcements' ) );
|
||||
add_filter( 'themeisle_sdk_active_announcements', array( $this, 'get_active_announcements' ) );
|
||||
add_filter( 'themeisle_sdk_announcements', array( $this, 'get_announcements_for_plugins' ) );
|
||||
add_action( 'themeisle_sdk_load_banner', array( $this, 'load_dashboard_banner_renderer' ) );
|
||||
add_filter(
|
||||
'themeisle_sdk_is_black_friday_sale',
|
||||
function( $is_black_friday ) {
|
||||
return $this->is_black_friday_sale( $this->get_current_date() );
|
||||
}
|
||||
);
|
||||
|
||||
add_action( 'admin_menu', array( $this, 'load_announcements' ), 9 );
|
||||
add_action( 'wp_ajax_themeisle_sdk_dismiss_black_friday_notice', array( $this, 'disable_notification_ajax' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,197 +89,132 @@ class Announcements extends Abstract_Module {
|
||||
* @return void
|
||||
*/
|
||||
public function load_announcements() {
|
||||
$active = $this->get_active_announcements();
|
||||
|
||||
if ( empty( $active ) ) {
|
||||
$current_date = $this->get_current_date();
|
||||
if ( ! $this->is_black_friday_sale( $current_date ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $active as $announcement ) {
|
||||
|
||||
$method = $announcement . '_notice_render';
|
||||
|
||||
if ( method_exists( $this, $method ) ) {
|
||||
add_action( 'admin_notices', array( $this, $method ) );
|
||||
}
|
||||
if ( self::MINIMUM_INSTALL_AGE > ( $current_date->getTimestamp() - $this->product->get_install_time() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the ajax handler.
|
||||
add_action( 'wp_ajax_themeisle_sdk_dismiss_announcement', array( $this, 'disable_notification_ajax' ) );
|
||||
add_action( 'admin_notices', array( $this, 'black_friday_notice_render' ) );
|
||||
|
||||
add_action(
|
||||
'themeisle_internal_page',
|
||||
function( $plugin, $page_slug ) {
|
||||
self::$current_product = $plugin;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
add_filter( 'plugin_row_meta', array( $this, 'add_plugin_meta_links' ), 10, 2 );
|
||||
add_filter( $this->product->get_key() . '_about_us_metadata', array( $this, 'override_about_us_metadata' ), 100 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active announcements.
|
||||
* Get the remaining time for the event in a human-readable format.
|
||||
*
|
||||
* @return array List of active announcements.
|
||||
*/
|
||||
public function get_active_announcements() {
|
||||
$active = array();
|
||||
|
||||
foreach ( self::$timeline as $announcement_slug => $dates ) {
|
||||
if ( $this->is_active( $dates ) && $this->can_show( $announcement_slug, $dates ) ) {
|
||||
$active[] = $announcement_slug;
|
||||
}
|
||||
}
|
||||
|
||||
return $active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all announcements along with plugin specific data.
|
||||
*
|
||||
* @return array List of announcements.
|
||||
*/
|
||||
public function get_announcements_for_plugins() {
|
||||
|
||||
$announcements = array();
|
||||
|
||||
foreach ( self::$timeline as $announcement => $dates ) {
|
||||
$announcements[ $announcement ] = $dates;
|
||||
|
||||
if ( false !== strpos( $announcement, 'black_friday' ) ) {
|
||||
$announcements[ $announcement ]['active'] = $this->is_active( $dates );
|
||||
|
||||
// Dashboard banners URLs.
|
||||
$announcements[ $announcement ]['neve_dashboard_url'] = tsdk_utmify( self::THEME_PAGE . '/neve/blackfriday/', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['hestia_dashboard_url'] = tsdk_utmify( self::THEME_PAGE . '/hestia/blackfriday/', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['feedzy_dashboard_url'] = tsdk_utmify( self::PLUGIN_PAGE . '/feedzy-rss-feeds/blackfriday/', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['otter_dashboard_url'] = tsdk_utmify( self::PLUGIN_PAGE . '/otter-blocks/blackfriday/', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['mpg_dashboard_url'] = tsdk_utmify( self::PLUGIN_PAGE . '/multi-pages-generator/blackfriday', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['ppom_dashboard_url'] = tsdk_utmify( self::PLUGIN_PAGE . '/ppom-pro/blackfriday/', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['rfc7r_dashboard_url'] = tsdk_utmify( self::PLUGIN_PAGE . '/wpcf7-redirect/blackfriday/', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['hyve_dashboard_url'] = tsdk_utmify( self::PLUGIN_PAGE . '/hyve/', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['spc_dashboard_url'] = tsdk_utmify( self::PLUGIN_PAGE . '/super-page-cache-pro/blackfriday/', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['visualizer_dashboard_url'] = tsdk_utmify( self::PLUGIN_PAGE . '/visualizer-charts-and-graphs/blackfriday/', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['feedzy_dashboard_url'] = tsdk_utmify( self::PLUGIN_PAGE . '/feedzy-rss-feeds/blackfriday/', 'bfcm24', 'dashboard' );
|
||||
$announcements[ $announcement ]['rop_dashboard_url'] = tsdk_utmify( self::REVIVE_SOCIAL . '/revive-old-post/', 'bfcm24', 'dashboard' );
|
||||
|
||||
// Customizer banners URLs.
|
||||
$announcements[ $announcement ]['hestia_customizer_url'] = tsdk_utmify( 'https://themeisle.com/black-friday/', 'bfcm24', 'hestiacustomizer' );
|
||||
$announcements[ $announcement ]['neve_customizer_url'] = tsdk_utmify( 'https://themeisle.com/black-friday/', 'bfcm24', 'nevecustomizer' );
|
||||
|
||||
// Banners urgency text.
|
||||
$remaining_time = $this->get_remaining_time_for_event( $dates['end'] );
|
||||
$announcements[ $announcement ]['remaining_time'] = $remaining_time;
|
||||
$announcements[ $announcement ]['urgency_text'] = ! empty( $remaining_time ) ? sprintf( Loader::$labels['announcements']['hurry_up'], $remaining_time ) : '';
|
||||
|
||||
$announcements[ $announcement ]['feedzy_banner_src'] = defined( 'FEEDZY_ABSURL' ) ? FEEDZY_ABSURL . 'img/black-friday.jpg' : '';
|
||||
$announcements[ $announcement ]['visualizer_banner_src'] = defined( 'VISUALIZER_ABSURL' ) ? VISUALIZER_ABSURL . 'images/black-friday.jpg' : '';
|
||||
$announcements[ $announcement ]['ppom_banner_src'] = defined( 'PPOM_URL' ) ? PPOM_URL . '/images/black-friday.jpg' : '';
|
||||
$announcements[ $announcement ]['mpg_banner_src'] = defined( 'MPG_BASE_IMG_PATH' ) ? MPG_BASE_IMG_PATH . '/black-friday.jpg' : '';
|
||||
$announcements[ $announcement ]['spc_banner_src'] = defined( 'SWCFPC_PLUGIN_URL' ) ? SWCFPC_PLUGIN_URL . 'assets/img/black-friday.jpg' : '';
|
||||
$announcements[ $announcement ]['hestia_banner_src'] = defined( 'HESTIA_ASSETS_URL' ) ? HESTIA_ASSETS_URL . 'img/black-friday.jpg' : '';
|
||||
$announcements[ $announcement ]['hyve_banner_src'] = defined( 'HYVE_LITE_URL' ) ? HYVE_LITE_URL . 'assets/images/black-friday.jpg' : '';
|
||||
$announcements[ $announcement ]['rfc7r_banner_src'] = defined( 'WPCF7_PRO_REDIRECT_ASSETS_PATH' ) ? WPCF7_PRO_REDIRECT_ASSETS_PATH . 'images/black-friday.jpg' : '';
|
||||
$announcements[ $announcement ]['rop_banner_src'] = defined( 'ROP_LITE_URL' ) ? ROP_LITE_URL . 'assets/img/black-friday.jpg' : '';
|
||||
|
||||
foreach ( $announcements[ $announcement ] as $key => $value ) {
|
||||
if ( strpos( $key, '_url' ) !== false ) {
|
||||
$announcements[ $announcement ][ $key ] = tsdk_translate_link( $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return apply_filters( 'themeisle_sdk_announcements_data', $announcements );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the announcement data.
|
||||
*
|
||||
* @param string $announcement The announcement to get the data for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_announcement_data( $announcement ) {
|
||||
return ! empty( $announcement ) && is_string( $announcement ) && isset( self::$timeline[ $announcement ] ) ? self::$timeline[ $announcement ] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the announcement has an active timeline.
|
||||
*
|
||||
* @param array $dates The announcement to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_active( $dates ) {
|
||||
|
||||
if ( empty( $this->time ) ) {
|
||||
$this->time = current_time( 'Y-m-d' );
|
||||
}
|
||||
|
||||
$start = isset( $dates['start'] ) ? $dates['start'] : null;
|
||||
$end = isset( $dates['end'] ) ? $dates['end'] : null;
|
||||
|
||||
if ( $start && $end ) {
|
||||
return $start <= $this->time && $this->time <= $end;
|
||||
} elseif ( $start ) {
|
||||
return $this->time >= $start;
|
||||
} elseif ( $end ) {
|
||||
return $this->time <= $end;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remaining time for the event in a human readable format.
|
||||
*
|
||||
* @param string $end_date The end date for event.
|
||||
* @param DateTime $end_date The end date for event.
|
||||
*
|
||||
* @return string Remaining time for the event.
|
||||
*/
|
||||
public function get_remaining_time_for_event( $end_date ) {
|
||||
if ( empty( $end_date ) || ! is_string( $end_date ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return human_time_diff( time(), strtotime( $end_date ) );
|
||||
|
||||
return human_time_diff( $this->get_current_date()->getTimestamp(), $end_date->getTimestamp() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the announcement can be shown.
|
||||
*
|
||||
* @param string $announcement_slug The announcement to check.
|
||||
* @param array $dates The announcement to check.
|
||||
* @param DateTime $current_date The announcement to check.
|
||||
* @param int $user_id The user id to show the notice.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_show( $announcement_slug, $dates ) {
|
||||
$dismiss_date = get_option( $this->option_prefix . $announcement_slug, false );
|
||||
public function can_show_notice( $current_date, $user_id ) {
|
||||
$current_year = $current_date->format( 'Y' );
|
||||
$user_notice_dismiss_timestamp = get_user_meta( $user_id, 'themeisle_sdk_dismissed_notice_black_friday', true );
|
||||
|
||||
if ( false === $dismiss_date ) {
|
||||
if ( empty( $user_notice_dismiss_timestamp ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the start date is after the dismiss date, show the notice.
|
||||
$start = isset( $dates['start'] ) ? $dates['start'] : null;
|
||||
if ( $start && $dismiss_date < $start ) {
|
||||
return true;
|
||||
}
|
||||
$dismissed_year = wp_date( 'Y', $user_notice_dismiss_timestamp );
|
||||
|
||||
return false;
|
||||
return $current_year !== $dismissed_year;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the notification via ajax.
|
||||
* Calculate the start date for Black Friday based on the year of the given date.
|
||||
*
|
||||
* @return void
|
||||
* Black Friday is the day after the Thanksgiving and the sale starts on the Monday of that week.
|
||||
*
|
||||
* @param DateTime $date The current date object, used to determine the year.
|
||||
* @return DateTime The start date of Black Friday for the given year.
|
||||
*/
|
||||
public function disable_notification_ajax() {
|
||||
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'dismiss_themeisle_event_notice' ) ) {
|
||||
wp_die( 'Invalid nonce! Refresh the page and try again.' );
|
||||
public function get_start_date( $date ) {
|
||||
$year = $date->format( 'Y' );
|
||||
$black_friday = new DateTime( "last friday of november {$year}" );
|
||||
|
||||
$sale_start = clone $black_friday;
|
||||
$sale_start->modify( 'monday this week' );
|
||||
$sale_start->setTime( 0, 0 );
|
||||
|
||||
return $sale_start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the event end date.
|
||||
*
|
||||
* @param DateTime $start_date The start date.
|
||||
* @return DateTime The end date.
|
||||
*/
|
||||
public function get_end_date( $start_date ) {
|
||||
$black_friday_end = clone $start_date;
|
||||
$black_friday_end->modify( self::SALE_DURATION_BLACK_FRIDAY );
|
||||
$black_friday_end->setTime( 23, 59, 59 );
|
||||
return $black_friday_end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current date falls within the Black Friday sale period.
|
||||
*
|
||||
* @param DateTime $current_date The date to check.
|
||||
* @return bool True if the date is within the Black Friday sale period, false otherwise.
|
||||
*/
|
||||
public function is_black_friday_sale( $current_date ) {
|
||||
$black_friday_start_date = $this->get_start_date( $current_date );
|
||||
$black_friday_end = $this->get_end_date( $black_friday_start_date );
|
||||
return $black_friday_start_date <= $current_date && $current_date <= $black_friday_end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notice data.
|
||||
*
|
||||
* @return array The notice data.
|
||||
*/
|
||||
public function get_notice_data() {
|
||||
$time_left_label = $this->get_remaining_time_for_event( $this->get_end_date( $this->get_start_date( $this->get_current_date() ) ) );
|
||||
$time_left_label = sprintf( Loader::$labels['announcements']['time_left'], $time_left_label );
|
||||
|
||||
$utm_location = 'globalnotice';
|
||||
if ( ! empty( $this->product ) ) {
|
||||
$utm_location = $this->product->get_friendly_name();
|
||||
}
|
||||
|
||||
if ( ! isset( $_POST['announcement'] ) || ! is_string( $_POST['announcement'] ) ) {
|
||||
wp_die( 'Invalid announcement! Refresh the page and try again.' );
|
||||
}
|
||||
$sale_title = Loader::$labels['announcements']['black_friday'];
|
||||
$sale_url = tsdk_translate_link( tsdk_utmify( 'https://themeisle.com/blackfriday/', 'bfcm26', $utm_location ) );
|
||||
|
||||
$announcement = sanitize_key( $_POST['announcement'] );
|
||||
$current_year = $this->get_current_date()->format( 'Y' );
|
||||
$sale_message = sprintf( Loader::$labels['announcements']['max_savings'], $current_year );
|
||||
|
||||
update_option( $this->option_prefix . $announcement, current_time( 'Y-m-d' ) );
|
||||
wp_die( 'success' );
|
||||
return array(
|
||||
'title' => $sale_title,
|
||||
'sale_url' => $sale_url,
|
||||
'message' => $sale_message,
|
||||
'time_left' => $time_left_label,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,183 +225,322 @@ class Announcements extends Abstract_Module {
|
||||
public function black_friday_notice_render() {
|
||||
|
||||
// Prevent the notice from being rendered twice.
|
||||
if ( self::$timeline['black_friday']['rendered'] ) {
|
||||
if ( self::$notice_loaded ) {
|
||||
return;
|
||||
}
|
||||
self::$timeline['black_friday']['rendered'] = true;
|
||||
self::$notice_loaded = true;
|
||||
|
||||
$product_names = array();
|
||||
$current_user_id = get_current_user_id();
|
||||
|
||||
foreach ( Loader::get_products() as $product ) {
|
||||
$slug = $product->get_slug();
|
||||
|
||||
// NOTE: No notice if the user has at least one Pro product.
|
||||
if ( $product->requires_license() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product_names[] = $product->get_name();
|
||||
if ( ! $this->can_show_notice( $this->get_current_date(), $current_user_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Randomize the products and get only 4.
|
||||
shuffle( $product_names );
|
||||
$product_names = array_slice( $product_names, 0, 4 );
|
||||
$all_configs = apply_filters( 'themeisle_sdk_blackfriday_data', array( 'default' => $this->get_notice_data() ) );
|
||||
|
||||
if ( empty( $all_configs ) || ! is_array( $all_configs ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = isset( $all_configs['default'] ) ? $all_configs['default'] : $this->get_notice_data();
|
||||
$products = Loader::get_products();
|
||||
$current_time = $this->get_current_date()->getTimestamp();
|
||||
$can_show = false;
|
||||
|
||||
// Check if we have products that are eligible to show the notice with the default data. If the product provide its own config, use it.
|
||||
foreach ( $products as $product ) {
|
||||
$slug = $product->get_slug();
|
||||
|
||||
if ( self::MINIMUM_INSTALL_AGE < ( $current_time - $product->get_install_time() ) ) {
|
||||
$can_show = true;
|
||||
|
||||
if ( isset( $all_configs[ $slug ] ) && ! empty( $all_configs[ $slug ] ) && is_array( $all_configs[ $slug ] ) ) {
|
||||
$data = $all_configs[ $slug ];
|
||||
|
||||
if ( self::$current_product === $slug ) {
|
||||
$data = $all_configs[ $slug ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $can_show ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$displayed_on_internal_page = 0 < did_action( 'themeisle_internal_page' );
|
||||
|
||||
$title = ! empty( $data['title'] ) ? $data['title'] : Loader::$labels['announcements']['black_friday'];
|
||||
$time_left_label = ! empty( $data['time_left'] ) ? $data['time_left'] : '';
|
||||
$message = ! empty( $data['message'] ) ? $data['message'] : '';
|
||||
$logo_url = ! empty( $data['logo_url'] ) ? $data['logo_url'] : $this->get_sdk_uri() . 'assets/images/themeisle-logo.png';
|
||||
$cta_label = ! empty( $data['cta_label'] ) ? $data['cta_label'] : Loader::$labels['announcements']['notice_link_label'];
|
||||
$sale_url = ! empty( $data['sale_url'] ) ? $data['sale_url'] : '';
|
||||
$hide_other_notices = ! empty( $data['hide_other_notices'] ) ? $data['hide_other_notices'] : $displayed_on_internal_page;
|
||||
$dismiss_notice_url = wp_nonce_url(
|
||||
add_query_arg(
|
||||
array( 'action' => 'themeisle_sdk_dismiss_black_friday_notice' ),
|
||||
admin_url( 'admin-ajax.php' )
|
||||
),
|
||||
'dismiss_themeisle_event_notice'
|
||||
);
|
||||
|
||||
if ( empty( $sale_url ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
$sale_url = remove_query_arg( 'lkey', $sale_url );
|
||||
}
|
||||
|
||||
?>
|
||||
<style>
|
||||
.themeisle-sale {
|
||||
border-left-color: #0466CB;
|
||||
}
|
||||
.themeisle-sale :is(.themeisle-sale-title, p) {
|
||||
margin: 0;
|
||||
}
|
||||
.themeisle-sale-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0;
|
||||
gap: 0.5rem;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.themeisle-sale-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
.themeisle-sale a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.themeisle-sale p a {
|
||||
margin-left: 1rem;
|
||||
padding: 7px 12px;
|
||||
border-radius: 4px;
|
||||
background: #0466CB;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
.themeisle-sale-dismiss {
|
||||
padding-top: 5px;
|
||||
}
|
||||
.themeisle-sale-dismiss span {
|
||||
color: #787c82;
|
||||
font-size: 16px;
|
||||
}
|
||||
.notice.themeisle-sale {
|
||||
padding: 0;
|
||||
}
|
||||
.themeisle-sale-logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.themeisle-sale-time-left {
|
||||
margin-left: 5px;
|
||||
padding: 3px 5px;
|
||||
border-radius: 4px;
|
||||
background-color: #dfdfdf;
|
||||
font-weight: 600;
|
||||
font-size: x-small;
|
||||
line-height: 1;
|
||||
}
|
||||
.themeisle-sale-title {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.themeisle-sale-action {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
<?php if ( $hide_other_notices ) : ?>
|
||||
.notice:not(.themeisle-sale) {
|
||||
display: none;
|
||||
}
|
||||
<?php endif; ?>
|
||||
</style>
|
||||
<div class="themeisle-sale notice notice-info is-dismissible" data-announcement="black_friday">
|
||||
<img width="24" src="<?php echo esc_url_raw( $this->get_sdk_uri() . 'assets/images/themeisle-logo.png' ); ?>"/>
|
||||
<p>
|
||||
<strong><?php echo esc_html( Loader::$labels['announcements']['sale_live'] ); ?></strong>
|
||||
- <?php echo sprintf( esc_html( Loader::$labels['announcements']['max_savings'] ), esc_html( implode( ', ', $product_names ) ) ); ?>.
|
||||
<a href="<?php echo esc_url_raw( tsdk_utmify( 'https://themeisle.com/blackfriday/', 'bfcm24', 'globalnotice' ) ); ?>"
|
||||
target="_blank"><?php echo esc_html( Loader::$labels['announcements']['learn_more'] ); ?></a>
|
||||
<span class="themeisle-sale-error"></span>
|
||||
</p>
|
||||
<div class="themeisle-sale notice notice-info" data-event-slug="black_friday">
|
||||
<div class="themeisle-sale-container">
|
||||
<div class="themeisle-sale-logo">
|
||||
<img
|
||||
width="45"
|
||||
src="<?php echo esc_url( $logo_url ); ?>"
|
||||
/>
|
||||
</div>
|
||||
<div class="themeisle-sale-content">
|
||||
<h4 class="themeisle-sale-title">
|
||||
<?php echo esc_html( $title ); ?>
|
||||
<span class="themeisle-sale-time-left">
|
||||
<?php echo esc_html( $time_left_label ); ?>
|
||||
</span>
|
||||
</h4>
|
||||
<p>
|
||||
<?php echo wp_kses_post( $message ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="themeisle-sale-action">
|
||||
<a
|
||||
href="<?php echo esc_url( $sale_url ); ?>"
|
||||
target="_blank"
|
||||
class="button button-primary themeisle-sale-button"
|
||||
>
|
||||
<?php echo esc_html( $cta_label ); ?>
|
||||
</a>
|
||||
</div>
|
||||
<a href="<?php echo esc_url( $dismiss_notice_url ); ?>" class="themeisle-sale-dismiss">
|
||||
<span class="dashicons dashicons-dismiss"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" data-origin="themeisle-sdk">
|
||||
window.document.addEventListener('DOMContentLoaded', () => {
|
||||
const observer = new MutationObserver((mutationsList, observer) => {
|
||||
for (let mutation of mutationsList) {
|
||||
if (mutation.type === 'childList') {
|
||||
const container = document.querySelector('.themeisle-sale.notice');
|
||||
const button = container?.querySelector('button');
|
||||
if (button) {
|
||||
button.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
fetch('<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
action: 'themeisle_sdk_dismiss_announcement',
|
||||
nonce: '<?php echo esc_attr( wp_create_nonce( 'dismiss_themeisle_event_notice' ) ); ?>',
|
||||
announcement: container.dataset.announcement
|
||||
})
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
if (!response?.includes('success')) {
|
||||
document.querySelector('.themeisle-sale-error').innerHTML = response;
|
||||
return;
|
||||
}
|
||||
<script>
|
||||
// Note: Some plugins use React and the content is ready after the `DOMContentLoaded` event. Use this function to reposition the notice after components have rendered.
|
||||
window.tsdk_reposition_notice = function() {
|
||||
const bannerRoot = document.getElementById('tsdk_banner');
|
||||
const saleNotice = document.querySelector('.themeisle-sale');
|
||||
if ( ! bannerRoot || ! saleNotice ) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelectorAll('.themeisle-sale.notice').forEach(el => {
|
||||
el.classList.add('hidden');
|
||||
setTimeout(() => {
|
||||
el.remove();
|
||||
}, 800);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
document.querySelector('.themeisle-sale-error').innerHTML = error;
|
||||
});
|
||||
});
|
||||
observer.disconnect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
bannerRoot.appendChild(saleNotice);
|
||||
};
|
||||
|
||||
observer.observe(document.body, {childList: true, subtree: true});
|
||||
});
|
||||
document.addEventListener( 'DOMContentLoaded', function() {
|
||||
window.tsdk_reposition_notice();
|
||||
} );
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the dashboard banner renderer.
|
||||
*
|
||||
* @param string $product_key The product key.
|
||||
* Disable the notification via ajax.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load_dashboard_banner_renderer( $product_key ) {
|
||||
public function disable_notification_ajax() {
|
||||
check_ajax_referer( 'dismiss_themeisle_event_notice' );
|
||||
|
||||
$banner_handler = apply_filters( 'themeisle_sdk_dependency_script_handler', 'banner' );
|
||||
update_user_meta( get_current_user_id(), 'themeisle_sdk_dismissed_notice_black_friday', $this->get_current_date()->getTimestamp() );
|
||||
|
||||
if ( empty( $banner_handler ) ) {
|
||||
return;
|
||||
$return_page_url = wp_get_referer();
|
||||
if ( empty( $return_page_url ) ) {
|
||||
$return_page_url = admin_url();
|
||||
}
|
||||
|
||||
if ( isset( self::$banner_loaded[ $product_key ] ) && true === self::$banner_loaded[ $product_key ] ) {
|
||||
return;
|
||||
}
|
||||
self::$banner_loaded[ $product_key ] = true;
|
||||
|
||||
$banner_data = array();
|
||||
|
||||
// Get the first active banner.
|
||||
foreach ( $this->get_announcements_for_plugins() as $announcement ) {
|
||||
if ( false === $announcement['active'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cta_key = $product_key . '_dashboard_url';
|
||||
$banner_src_key = $product_key . '_banner_src';
|
||||
|
||||
if (
|
||||
! isset( $announcement[ $cta_key ] ) ||
|
||||
! isset( $announcement[ $banner_src_key ] ) ||
|
||||
empty( $announcement[ $banner_src_key ] ) ||
|
||||
! isset( $announcement['urgency_text'] )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$banner_data = array(
|
||||
'content' => $this->render_banner(
|
||||
array(
|
||||
'cta_url' => $announcement[ $cta_key ],
|
||||
'img_src' => $announcement[ $banner_src_key ],
|
||||
'urgency_text' => $announcement['urgency_text'],
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ( empty( $banner_data ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
do_action( 'themeisle_sdk_dependency_enqueue_script', 'banner' );
|
||||
wp_localize_script( $banner_handler, 'tsdk_banner_data', $banner_data );
|
||||
wp_safe_redirect( $return_page_url );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a banner with the provided settings.
|
||||
* Add the plugin meta links.
|
||||
*
|
||||
* @param array $settings {
|
||||
* Optional. An array of settings for the banner.
|
||||
*
|
||||
* @type string $cta_url The URL for the call-to-action link.
|
||||
* @type string $img_src The source URL for the banner image.
|
||||
* @type string $urgency_text The urgency text to display on the banner.
|
||||
* }
|
||||
* @return string The HTML output of the banner.
|
||||
* @param array<string, string> $links The plugin meta links.
|
||||
* @param string $plugin_file The plugin file.
|
||||
* @return array<string, string> The plugin meta links.
|
||||
*/
|
||||
public function render_banner( $settings = array() ) {
|
||||
if ( empty( $settings ) ) {
|
||||
return '';
|
||||
public function add_plugin_meta_links( $links, $plugin_file ) {
|
||||
if ( self::$meta_link_loaded ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
return wp_kses_post(
|
||||
wp_sprintf(
|
||||
'<a href="%s" target="_blank" class="tsdk-banner-cta"><img src="%s" class="tsdk-banner-img"><div class="tsdk-banner-urgency-text">%s</div></a>',
|
||||
esc_url_raw( $settings['cta_url'] ),
|
||||
esc_url_raw( $settings['img_src'] ),
|
||||
sanitize_text_field( $settings['urgency_text'] )
|
||||
)
|
||||
);
|
||||
if ( $plugin_file !== plugin_basename( $this->product->get_basefile() ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$configs = apply_filters( 'themeisle_sdk_blackfriday_data', array( 'default' => $this->get_notice_data() ) );
|
||||
|
||||
if ( empty( $configs ) || ! is_array( $configs ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$current_slug = $this->product->get_slug();
|
||||
$data = isset( $configs[ $current_slug ] ) && ! empty( $configs[ $current_slug ] ) && is_array( $configs[ $current_slug ] ) ? $configs[ $current_slug ] : array();
|
||||
|
||||
$plugin_meta_message = '';
|
||||
$plugin_meta_url = '';
|
||||
|
||||
if ( isset( $data['plugin_meta_targets'] ) && ! empty( $data['plugin_meta_targets'] ) && ! in_array( $current_slug, $data['plugin_meta_targets'] ) ) {
|
||||
return $links; // The current configuration is for another plugins.
|
||||
}
|
||||
|
||||
$plugin_meta_message = ! empty( $data['plugin_meta_message'] ) ? $data['plugin_meta_message'] : '';
|
||||
$plugin_meta_url = ! empty( $data['sale_url'] ) ? $data['sale_url'] : '';
|
||||
|
||||
if ( empty( $plugin_meta_url ) || empty( $plugin_meta_message ) ) {
|
||||
|
||||
// Check if a configuration is in another plugin.
|
||||
$products = Loader::get_products();
|
||||
foreach ( $products as $product ) {
|
||||
$slug = $product->get_slug();
|
||||
|
||||
if ( $slug === $current_slug || ! isset( $configs[ $slug ] ) || empty( $configs[ $slug ] ) || ! is_array( $configs[ $slug ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! empty( $configs[ $slug ]['plugin_meta_targets'] ) && in_array( $current_slug, $configs[ $slug ]['plugin_meta_targets'] ) ) {
|
||||
$plugin_meta_message = ! empty( $configs[ $slug ]['plugin_meta_message'] ) ? $configs[ $slug ]['plugin_meta_message'] : '';
|
||||
$plugin_meta_url = ! empty( $configs[ $slug ]['sale_url'] ) ? $configs[ $slug ]['sale_url'] : '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $plugin_meta_url ) || empty( $plugin_meta_message ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$links[] = sprintf( '<a class="themeisle-sale-plugin-meta-link" style="color: red;" href="%s" target="_blank">%s</a>', esc_url( $plugin_meta_url ), esc_html( $plugin_meta_message ) );
|
||||
|
||||
self::$meta_link_loaded = true;
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the About Us upgrade menu during Black Friday.
|
||||
*
|
||||
* Registered dynamically during admin_menu when sale is active.
|
||||
* Only applies if About_Us module is loaded for the product.
|
||||
*
|
||||
* @param array<string, mixed> $about_data About Us metadata.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function override_about_us_metadata( $about_data ) {
|
||||
if ( ! $this->is_black_friday_sale( $this->get_current_date() ) ) {
|
||||
return $about_data;
|
||||
}
|
||||
|
||||
if ( empty( $about_data ) || ! is_array( $about_data ) ) {
|
||||
return $about_data;
|
||||
}
|
||||
|
||||
if ( empty( $about_data['has_upgrade_menu'] ) || true !== $about_data['has_upgrade_menu'] ) {
|
||||
return $about_data;
|
||||
}
|
||||
|
||||
$configs = apply_filters( 'themeisle_sdk_blackfriday_data', array( 'default' => $this->get_notice_data() ) );
|
||||
|
||||
$current_slug = $this->product->get_slug();
|
||||
if ( ! isset( $configs[ $current_slug ] ) || empty( $configs[ $current_slug ] ) || ! is_array( $configs[ $current_slug ] ) ) {
|
||||
return $about_data;
|
||||
}
|
||||
|
||||
$config = $configs[ $current_slug ];
|
||||
|
||||
if ( empty( $config['upgrade_menu_text'] ) || empty( $config['sale_url'] ) ) {
|
||||
return $about_data;
|
||||
}
|
||||
|
||||
$about_data['upgrade_text'] = $config['upgrade_menu_text'];
|
||||
$about_data['upgrade_link'] = $config['sale_url'];
|
||||
|
||||
return $about_data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
namespace ThemeisleSDK\Modules;
|
||||
|
||||
use ThemeisleSDK\Common\Abstract_Module;
|
||||
use ThemeisleSDK\Loader;
|
||||
use ThemeisleSDK\Product;
|
||||
|
||||
// Exit if accessed directly.
|
||||
@ -32,6 +33,13 @@ class Featured_Plugins extends Abstract_Module {
|
||||
*/
|
||||
private $transient_key = 'themeisle_sdk_featured_plugins_';
|
||||
|
||||
/**
|
||||
* The current product instance.
|
||||
*
|
||||
* @var Product|null
|
||||
*/
|
||||
protected $product = null;
|
||||
|
||||
/**
|
||||
* Check if the module can be loaded.
|
||||
*
|
||||
@ -59,6 +67,8 @@ class Featured_Plugins extends Abstract_Module {
|
||||
* @return void
|
||||
*/
|
||||
public function load( $product ) {
|
||||
$this->product = $product;
|
||||
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return;
|
||||
}
|
||||
@ -69,7 +79,58 @@ class Featured_Plugins extends Abstract_Module {
|
||||
}
|
||||
add_filter( 'themeisle_sdk_plugin_api_filter_registered', '__return_true' );
|
||||
|
||||
add_filter( 'plugins_api_result', [ $this, 'filter_plugin_api_results' ], 10, 3 );
|
||||
add_filter( 'plugins_api_result', [ $this, 'filter_plugin_api_results' ], 11, 3 );
|
||||
|
||||
// Enqueue inline JS only on plugin-install.php.
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'maybe_add_inline_js' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue inline JavaScript only on plugin-install.php.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_add_inline_js() {
|
||||
$screen = get_current_screen();
|
||||
if ( isset( $screen->base ) && 'plugin-install' === $screen->base ) {
|
||||
add_action(
|
||||
'admin_footer',
|
||||
function() {
|
||||
$text = esc_html( sprintf( Loader::$labels['promotions']['recommended'], $this->product->get_friendly_name() ) );
|
||||
|
||||
echo '<script>(function(){
|
||||
function onPluginCardFound(card) {
|
||||
var recommendedDiv = document.createElement("div");
|
||||
Object.assign(recommendedDiv.style, {
|
||||
display: "block",
|
||||
textAlign: "center",
|
||||
padding: "0 12px 12px",
|
||||
background: "#f6f7f7"
|
||||
});
|
||||
recommendedDiv.innerHTML = "' . esc_html( $text ) . '";
|
||||
card.appendChild(recommendedDiv);
|
||||
}
|
||||
|
||||
function checkAndRun() {
|
||||
var card = document.querySelector(".plugin-card-learning-management-system");
|
||||
if (card && !card.dataset.recommendedAdded) {
|
||||
onPluginCardFound(card);
|
||||
card.dataset.recommendedAdded = "true";
|
||||
}
|
||||
}
|
||||
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
checkAndRun();
|
||||
});
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
// Initial check in case the card is already present.
|
||||
checkAndRun();
|
||||
})();</script>';
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,6 +148,11 @@ class Featured_Plugins extends Abstract_Module {
|
||||
return $res;
|
||||
}
|
||||
|
||||
if ( isset( $args->page ) && 1 === (int) $args->page && isset( $args->search ) && ! empty( $args->search ) ) {
|
||||
$res->plugins = $this->maybe_prepend_lms_plugin( $res->plugins, $args );
|
||||
return $res;
|
||||
}
|
||||
|
||||
if ( ! isset( $args->browse ) || $args->browse !== 'featured' ) {
|
||||
return $res;
|
||||
}
|
||||
@ -100,6 +166,38 @@ class Featured_Plugins extends Abstract_Module {
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend the LMS plugin if the search query matches LMS-related terms.
|
||||
*
|
||||
* @param array $plugins The plugins array.
|
||||
* @param object $args The plugin API arguments.
|
||||
* @return array
|
||||
*/
|
||||
private function maybe_prepend_lms_plugin( $plugins, $args ) {
|
||||
$search = isset( $args->search ) ? strtolower( $args->search ) : '';
|
||||
if (
|
||||
strpos( $search, 'lms' ) !== false ||
|
||||
strpos( $search, 'learn' ) !== false
|
||||
) {
|
||||
$filter_slugs = apply_filters( 'themeisle_sdk_masteriyo_filter_slugs', [ 'learning-management-system' ] );
|
||||
$masteriyo = $this->get_plugins_filtered_from_author( $args, $filter_slugs, 'masteriyo' );
|
||||
|
||||
if ( ! empty( $masteriyo ) ) {
|
||||
// Remove existing LMS plugin if present to avoid duplicates.
|
||||
$plugins = array_filter(
|
||||
$plugins,
|
||||
function( $plugin ) {
|
||||
return ( is_object( $plugin ) && isset( $plugin->slug ) && $plugin->slug !== 'learning-management-system' ) ||
|
||||
( is_array( $plugin ) && isset( $plugin['slug'] ) && $plugin['slug'] !== 'learning-management-system' );
|
||||
}
|
||||
);
|
||||
|
||||
$plugins = array_merge( $masteriyo, $plugins );
|
||||
}
|
||||
}
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query plugins by author.
|
||||
*
|
||||
@ -114,7 +212,7 @@ class Featured_Plugins extends Abstract_Module {
|
||||
$filtered_from_optimole = $this->get_plugins_filtered_from_author( $args, $optimole_filter_slugs, 'Optimole' );
|
||||
$featured = array_merge( $featured, $filtered_from_optimole );
|
||||
|
||||
$themeisle_filter_slugs = apply_filters( 'themeisle_sdk_themeisle_filter_slugs', [ 'otter-blocks' ] );
|
||||
$themeisle_filter_slugs = apply_filters( 'themeisle_sdk_themeisle_filter_slugs', [ 'otter-blocks', 'wp-cloudflare-page-cache' ] );
|
||||
$filtered_from_themeisle = $this->get_plugins_filtered_from_author( $args, $themeisle_filter_slugs );
|
||||
$featured = array_merge( $featured, $filtered_from_themeisle );
|
||||
|
||||
@ -130,7 +228,7 @@ class Featured_Plugins extends Abstract_Module {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_plugins_filtered_from_author( $args, $filter_slugs = [], $author = 'Themeisle' ) {
|
||||
protected function get_plugins_filtered_from_author( $args, $filter_slugs = [], $author = 'Themeisle' ) {
|
||||
|
||||
$cached = get_transient( $this->transient_key . $author );
|
||||
if ( $cached ) {
|
||||
|
||||
@ -380,7 +380,7 @@ class Licenser extends Abstract_Module {
|
||||
$status = $this->get_license_status( true );
|
||||
$no_activations_string = apply_filters( $this->product->get_key() . '_lc_no_activations_string', Loader::$labels['licenser']['no_activations'] );
|
||||
$no_valid_string = apply_filters( $this->product->get_key() . '_lc_no_valid_string', sprintf( Loader::$labels['licenser']['inactive'], '%s', '<a href="%s" target="_blank">', '</a>', '<a href="%s">', '</a>' ) );
|
||||
$expired_license_string = apply_filters( $this->product->get_key() . '_lc_expired_string', sprintf( Loader::$labels['licenser']['expired'], '%s', '<a href="%s" target="_blank">', '</a>' ) );
|
||||
$expired_license_string = apply_filters( $this->product->get_key() . '_lc_expired_heading_string', Loader::$labels['licenser']['expired'] );
|
||||
// No activations left for this license.
|
||||
if ( 'valid' != $status && $this->check_activation() ) {
|
||||
?>
|
||||
@ -403,12 +403,72 @@ class Licenser extends Abstract_Module {
|
||||
|
||||
// Invalid license key.
|
||||
if ( 'active_expired' === $status ) {
|
||||
// Check if the notice was dismissed.
|
||||
$dismiss_option_key = $this->product->get_key() . '_expired_notice_dismissed';
|
||||
if ( get_option( $dismiss_option_key, false ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$license_data = get_option( $this->product->get_key() . '_license_data', '' );
|
||||
$expiration_date = '';
|
||||
if ( is_object( $license_data ) && isset( $license_data->expires ) ) {
|
||||
$timestamp = strtotime( (string) $license_data->expires );
|
||||
if ( false !== $timestamp ) {
|
||||
$expiration_date = gmdate( 'F j, Y', $timestamp );
|
||||
}
|
||||
}
|
||||
|
||||
$discount_config = apply_filters( $this->product->get_key() . '_lc_renew_discount', false );
|
||||
|
||||
if ( is_array( $discount_config ) && isset( $discount_config['url'] ) && isset( $discount_config['renew_button'] ) ) {
|
||||
$renew_url = $discount_config['url'];
|
||||
$renew_button = $discount_config['renew_button'];
|
||||
} else {
|
||||
$renew_url = apply_filters( $this->product->get_key() . '_lc_renew_url', $this->renew_url() );
|
||||
$renew_button = apply_filters( $this->product->get_key() . '_lc_renew_button_string', Loader::$labels['licenser']['renew_license'] );
|
||||
}
|
||||
|
||||
$learn_more_url = apply_filters( $this->product->get_key() . '_lc_learn_more_url', $this->get_api_url() );
|
||||
$learn_more_button = apply_filters( $this->product->get_key() . '_lc_learn_more_button_string', Loader::$labels['licenser']['learn_more'] );
|
||||
$notice_message = apply_filters( $this->product->get_key() . '_lc_expired_notice_message', Loader::$labels['licenser']['expired_notice'] );
|
||||
|
||||
$expired_date_string = apply_filters( $this->product->get_key() . '_lc_expired_date_string', sprintf( Loader::$labels['licenser']['expired_date'], esc_html( $expiration_date ) ) );
|
||||
$heading = apply_filters( $this->product->get_key() . '_lc_expired_heading_string', sprintf( Loader::$labels['licenser']['expired'], $this->product->get_name() ) );
|
||||
$notice_id = $this->product->get_key() . '_expired_notice';
|
||||
?>
|
||||
<div class="error">
|
||||
<p>
|
||||
<strong><?php echo sprintf( wp_kses_data( $expired_license_string ), esc_attr( $this->product->get_name() . ' ' . $this->product->get_type() ), esc_url( $this->get_api_url() . '?license=' . $this->license_key ) ); ?> </strong>
|
||||
<div class="notice notice-warning notice-alt is-dismissible themeisle-sdk-license-notice" id="<?php echo esc_attr( $notice_id ); ?>" data-notice-id="<?php echo esc_attr( $notice_id ); ?>" style="position: relative; border-left: 4px solid #d63638; padding: 12px; background-color: #fff;">
|
||||
<p style="margin: 0.5em 0; font-size: 13px;">
|
||||
<strong><?php echo wp_kses_post( $heading ); ?></strong>
|
||||
· <?php echo esc_html( $expired_date_string ); ?>
|
||||
</p>
|
||||
<p style="margin: 0.5em 0 1em 0; font-size: 13px;">
|
||||
<?php echo esc_html( $notice_message ); ?>
|
||||
</p>
|
||||
<p style="margin: 0.5em 0;">
|
||||
<a href="<?php echo esc_url( $renew_url ); ?>" class="button button-primary" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo esc_html( $renew_button ); ?>
|
||||
</a>
|
||||
<a href="<?php echo esc_url( $learn_more_url ); ?>" class="button" target="_blank" rel="noopener noreferrer" style="border: none; background-color: transparent;">
|
||||
<?php echo esc_html( $learn_more_button ); ?>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function($) {
|
||||
$('#<?php echo esc_js( $notice_id ); ?>').on('click', '.notice-dismiss', function(e) {
|
||||
const noticeId = '<?php echo esc_js( $notice_id ); ?>';
|
||||
$.ajax({
|
||||
url: ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'themeisle_sdk_dismiss_license_notice',
|
||||
notice_id: noticeId,
|
||||
nonce: '<?php echo esc_js( wp_create_nonce( 'themeisle_sdk_dismiss_license_notice' ) ); ?>'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
|
||||
return false;
|
||||
@ -1045,6 +1105,32 @@ class Licenser extends Abstract_Module {
|
||||
add_action( 'admin_init', array( $this, 'product_valid' ), 99999999 );
|
||||
add_action( 'admin_notices', array( $this, 'show_notice' ) );
|
||||
add_filter( $this->product->get_key() . '_license_status', array( $this, 'get_license_status' ) );
|
||||
add_action( 'wp_ajax_themeisle_sdk_dismiss_license_notice', array( $this, 'dismiss_license_notice' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle AJAX request to dismiss the license notice.
|
||||
*/
|
||||
public function dismiss_license_notice() {
|
||||
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['nonce'] ), 'themeisle_sdk_dismiss_license_notice' ) ) {
|
||||
wp_send_json_error( 'Invalid nonce' );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( 'Insufficient permissions' );
|
||||
}
|
||||
|
||||
$notice_id = isset( $_POST['notice_id'] ) ? sanitize_text_field( $_POST['notice_id'] ) : '';
|
||||
|
||||
if ( empty( $notice_id ) ) {
|
||||
wp_send_json_error( 'Missing notice ID' );
|
||||
}
|
||||
|
||||
// Save the dismissal option.
|
||||
$dismiss_option_key = $notice_id . '_dismissed';
|
||||
update_option( $dismiss_option_key, true );
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -55,8 +55,8 @@ class Logger extends Abstract_Module {
|
||||
*/
|
||||
public function load( $product ) {
|
||||
$this->product = $product;
|
||||
$this->setup_notification();
|
||||
$this->setup_actions();
|
||||
add_action( 'wp_loaded', array( $this, 'setup_actions' ) );
|
||||
add_action( 'admin_init', array( $this, 'setup_notification' ) );
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -64,12 +64,10 @@ class Logger extends Abstract_Module {
|
||||
* Setup notification on admin.
|
||||
*/
|
||||
public function setup_notification() {
|
||||
if ( ! $this->product->is_wordpress_available() ) {
|
||||
if ( $this->is_logger_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'themeisle_sdk_registered_notifications', [ $this, 'add_notification' ] );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,7 +77,6 @@ class Logger extends Abstract_Module {
|
||||
if ( ! $this->is_logger_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action(
|
||||
'admin_enqueue_scripts',
|
||||
function() {
|
||||
@ -89,7 +86,7 @@ class Logger extends Abstract_Module {
|
||||
|
||||
$this->load_telemetry();
|
||||
},
|
||||
PHP_INT_MAX
|
||||
PHP_INT_MAX
|
||||
);
|
||||
|
||||
$action_key = $this->product->get_key() . '_log_activity';
|
||||
@ -105,24 +102,26 @@ class Logger extends Abstract_Module {
|
||||
* @return bool Is logger active?
|
||||
*/
|
||||
private function is_logger_active() {
|
||||
if ( apply_filters( 'themeisle_sdk_disable_telemetry', false ) ) {
|
||||
return false;
|
||||
}
|
||||
$default = 'no';
|
||||
|
||||
if ( ! $this->product->is_wordpress_available() ) {
|
||||
$default = 'yes';
|
||||
} else {
|
||||
$pro_slug = $this->product->get_pro_slug();
|
||||
|
||||
if ( ! empty( $pro_slug ) ) {
|
||||
$all_products = Loader::get_products();
|
||||
if ( isset( $all_products[ $pro_slug ] ) ) {
|
||||
$all_products = Loader::get_products();
|
||||
foreach ( $all_products as $product ) {
|
||||
if ( $product->requires_license() ) {
|
||||
$default = 'yes';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ( get_option( $this->product->get_key() . '_logger_flag', $default ) === 'yes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notification to queue.
|
||||
*
|
||||
@ -179,14 +178,15 @@ class Logger extends Abstract_Module {
|
||||
'timeout' => 3,
|
||||
'redirection' => 5,
|
||||
'body' => array(
|
||||
'site' => get_site_url(),
|
||||
'slug' => $this->product->get_slug(),
|
||||
'version' => $this->product->get_version(),
|
||||
'wp_version' => $wp_version,
|
||||
'locale' => get_locale(),
|
||||
'data' => apply_filters( $this->product->get_key() . '_logger_data', array() ),
|
||||
'environment' => $environment,
|
||||
'license' => apply_filters( $this->product->get_key() . '_license_status', '' ),
|
||||
'site' => get_site_url(),
|
||||
'slug' => $this->product->get_slug(),
|
||||
'version' => $this->product->get_version(),
|
||||
'wp_version' => $wp_version,
|
||||
'install_time' => $this->product->get_install_time(),
|
||||
'locale' => get_locale(),
|
||||
'data' => apply_filters( $this->product->get_key() . '_logger_data', array() ),
|
||||
'environment' => $environment,
|
||||
'license' => apply_filters( $this->product->get_key() . '_license_status', '' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
177
wp-content/plugins/menu-icons/vendor/codeinwp/themeisle-sdk/src/Modules/Migrator.php
vendored
Normal file
177
wp-content/plugins/menu-icons/vendor/codeinwp/themeisle-sdk/src/Modules/Migrator.php
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
/**
|
||||
* The migrator module for ThemeIsle SDK.
|
||||
*
|
||||
* @package ThemeIsleSDK
|
||||
* @subpackage Modules
|
||||
* @copyright Copyright (c) 2024, Themeisle
|
||||
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
|
||||
* @since 3.3.50
|
||||
*/
|
||||
|
||||
namespace ThemeisleSDK\Modules;
|
||||
|
||||
use ThemeisleSDK\Common\Abstract_Module;
|
||||
use ThemeisleSDK\Product;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrator module for ThemeIsle SDK.
|
||||
*
|
||||
* Allows products to ship PHP migration files that run automatically on
|
||||
* admin page loads. Each product opts in by registering its migrations
|
||||
* directory via the `{product_slug}_sdk_migrations_path` filter.
|
||||
*/
|
||||
class Migrator extends Abstract_Module {
|
||||
/**
|
||||
* Option key suffix used to store the list of ran migrations.
|
||||
*/
|
||||
const OPTION_SUFFIX = '_ran_migrations';
|
||||
|
||||
/**
|
||||
* Check if we should load the module for this product.
|
||||
*
|
||||
* Always returns true — the actual path check happens lazily at admin_init.
|
||||
*
|
||||
* @param Product $product Product to load the module for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_load( $product ) {
|
||||
return apply_filters( $product->get_slug() . '_sdk_enable_migrator', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load module logic.
|
||||
*
|
||||
* @param Product $product Product to load.
|
||||
*
|
||||
* @return Migrator
|
||||
*/
|
||||
public function load( $product ) {
|
||||
$this->product = $product;
|
||||
add_action( 'admin_init', array( $this, 'run_pending' ) );
|
||||
add_action( 'themeisle_sdk_rollback_migration_' . $product->get_slug(), array( $this, 'rollback' ) );
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover and run any pending migrations for the product.
|
||||
*
|
||||
* Only runs when a version upgrade was detected during this request, indicated
|
||||
* by the themeisle_sdk_update_{slug} action having fired.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run_pending() {
|
||||
if ( ! did_action( 'themeisle_sdk_update_' . $this->product->get_slug() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $this->get_migrations_path();
|
||||
|
||||
if ( empty( $path ) || ! is_dir( $path ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = glob( trailingslashit( $path ) . '*.php' );
|
||||
|
||||
if ( empty( $files ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
sort( $files ); // Alphabetical order = chronological order given timestamp naming.
|
||||
|
||||
$option_key = $this->product->get_key() . self::OPTION_SUFFIX;
|
||||
$ran = get_option( $option_key, array() );
|
||||
|
||||
foreach ( $files as $file ) {
|
||||
$name = basename( $file, '.php' );
|
||||
|
||||
if ( in_array( $name, $ran, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$migration = require $file; // Migration files return an anonymous class instance.
|
||||
|
||||
if ( ! ( $migration instanceof Abstract_Migration ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $migration->should_run() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$migration->up();
|
||||
$ran[] = $name;
|
||||
update_option( $option_key, $ran );
|
||||
} catch ( \Throwable $e ) {
|
||||
// Log and stop — leave the migration unrecorded so it retries next load.
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
error_log( 'ThemeIsle SDK Migrator: failed to run ' . $name . ': ' . $e->getMessage() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll back a single migration by name.
|
||||
*
|
||||
* Calls down() on the migration and removes it from the ran list so it will
|
||||
* be picked up again on the next upgrade. This method is never called
|
||||
* automatically — products invoke it explicitly when needed.
|
||||
*
|
||||
* @param string $migration_name Migration basename without .php extension.
|
||||
*
|
||||
* @return bool True if rolled back successfully, false if not found or not previously run.
|
||||
*/
|
||||
public function rollback( $migration_name ) {
|
||||
$option_key = $this->product->get_key() . self::OPTION_SUFFIX;
|
||||
$ran = get_option( $option_key, array() );
|
||||
|
||||
if ( ! in_array( $migration_name, $ran, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = $this->get_migrations_path();
|
||||
$file = trailingslashit( $path ) . $migration_name . '.php';
|
||||
|
||||
if ( ! is_file( $file ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$migration = require $file;
|
||||
|
||||
if ( ! ( $migration instanceof Abstract_Migration ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$migration->down();
|
||||
update_option( $option_key, array_values( array_diff( $ran, array( $migration_name ) ) ) );
|
||||
|
||||
return true;
|
||||
} catch ( \Throwable $e ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
error_log( 'ThemeIsle SDK Migrator: failed to roll back ' . $migration_name . ': ' . $e->getMessage() );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the migrations directory path for the current product.
|
||||
*
|
||||
* Products register their path via the `{slug}_sdk_migrations_path` filter.
|
||||
*
|
||||
* @return string Absolute path to the migrations directory, or empty string.
|
||||
*/
|
||||
private function get_migrations_path() {
|
||||
return (string) apply_filters( $this->product->get_slug() . '_sdk_migrations_path', '' );
|
||||
}
|
||||
}
|
||||
@ -105,6 +105,13 @@ class Promotions extends Abstract_Module {
|
||||
*/
|
||||
private $option_feedzy = 'themeisle_sdk_promotions_feedzy_installed';
|
||||
|
||||
/**
|
||||
* Option key for Masteriyo promos.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $option_masteriyo = 'themeisle_sdk_promotions_masteriyo_installed';
|
||||
|
||||
/**
|
||||
* Loaded promotion.
|
||||
*
|
||||
@ -152,6 +159,7 @@ class Promotions extends Abstract_Module {
|
||||
$promotions_to_load[] = 'hyve';
|
||||
$promotions_to_load[] = 'wp_full_pay';
|
||||
$promotions_to_load[] = 'feedzy_import';
|
||||
$promotions_to_load[] = 'learning-management-system';
|
||||
|
||||
if ( defined( 'NEVE_VERSION' ) || defined( 'WPMM_PATH' ) || defined( 'OTTER_BLOCKS_VERSION' ) || defined( 'OBFX_URL' ) ) {
|
||||
$promotions_to_load[] = 'feedzy_embed';
|
||||
@ -257,6 +265,7 @@ class Promotions extends Abstract_Module {
|
||||
if ( isset( $_GET['wp_full_pay_reference_key'] ) ) {
|
||||
update_option( 'wp_full_pay_reference_key', sanitize_key( $_GET['wp_full_pay_reference_key'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $_GET['feedzy_reference_key'] ) || ( isset( $_GET['from'], $_GET['plugin'] ) && $_GET['from'] === 'import' && str_starts_with( sanitize_key( $_GET['plugin'] ), 'feedzy' ) ) ) {
|
||||
update_option( 'feedzy_reference_key', sanitize_key( $_GET['feedzy_reference_key'] ?? 'i-' . $this->product->get_key() ) );
|
||||
update_option( $this->option_feedzy, 1 );
|
||||
@ -350,6 +359,16 @@ class Promotions extends Abstract_Module {
|
||||
'default' => false,
|
||||
)
|
||||
);
|
||||
register_setting(
|
||||
'themeisle_sdk_settings',
|
||||
$this->option_masteriyo,
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
'show_in_rest' => true,
|
||||
'default' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -415,13 +434,16 @@ class Promotions extends Abstract_Module {
|
||||
$has_neve_from_promo = get_option( $this->option_neve, false );
|
||||
$has_enough_attachments = $this->has_min_media_attachments();
|
||||
$has_enough_old_posts = $this->has_old_posts();
|
||||
$is_min_php_8_1 = version_compare( PHP_VERSION, '8.1', '>=' );
|
||||
|
||||
$has_feedzy = defined( 'FEEDZY_BASEFILE' ) || $this->is_plugin_installed( 'feedzy-rss-feedss' );
|
||||
$had_feedzy_from_promo = get_option( $this->option_feedzy, false );
|
||||
$is_min_php_7_4 = version_compare( PHP_VERSION, '7.4', '>=' );
|
||||
$has_feedzy = defined( 'FEEDZY_BASEFILE' ) || $this->is_plugin_installed( 'feedzy-rss-feedss' );
|
||||
$had_feedzy_from_promo = get_option( $this->option_feedzy, false );
|
||||
$has_masteriyo = defined( 'MASTERIYO_VERSION' ) || $this->is_plugin_installed( 'learning-management-system' );
|
||||
$had_masteriyo_from_promo = get_option( $this->option_masteriyo, false );
|
||||
$has_masteriyo_conditions = $this->has_lms_tagline();
|
||||
$is_min_php_7_2 = version_compare( PHP_VERSION, '7.2', '>=' );
|
||||
|
||||
$all = [
|
||||
'optimole' => [
|
||||
'optimole' => [
|
||||
'om-editor' => [
|
||||
'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
|
||||
'screen' => 'editor',
|
||||
@ -446,20 +468,20 @@ class Promotions extends Abstract_Module {
|
||||
'delayed' => true,
|
||||
],
|
||||
],
|
||||
'feedzy_import' => [
|
||||
'feedzy_import' => [
|
||||
'feedzy-import' => [
|
||||
'env' => true,
|
||||
'screen' => 'import',
|
||||
'always' => true,
|
||||
],
|
||||
],
|
||||
'feedzy_embed' => [
|
||||
'feedzy_embed' => [
|
||||
'feedzy-editor' => [
|
||||
'env' => ! $has_feedzy && is_main_site() && ! $had_feedzy_from_promo,
|
||||
'screen' => 'editor',
|
||||
],
|
||||
],
|
||||
'otter' => [
|
||||
'otter' => [
|
||||
'blocks-css' => [
|
||||
'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
|
||||
'screen' => 'editor',
|
||||
@ -476,14 +498,14 @@ class Promotions extends Abstract_Module {
|
||||
'delayed' => true,
|
||||
],
|
||||
],
|
||||
'rop' => [
|
||||
'rop' => [
|
||||
'rop-posts' => [
|
||||
'env' => ! $has_rop && ! $had_rop_from_promo && $has_enough_old_posts,
|
||||
'screen' => 'edit-post',
|
||||
'delayed' => true,
|
||||
],
|
||||
],
|
||||
'woo_plugins' => [
|
||||
'woo_plugins' => [
|
||||
'ppom' => [
|
||||
'env' => ! $has_ppom && $has_woocommerce,
|
||||
'screen' => 'edit-product',
|
||||
@ -501,31 +523,37 @@ class Promotions extends Abstract_Module {
|
||||
'screen' => 'edit-product',
|
||||
],
|
||||
],
|
||||
'neve' => [
|
||||
'neve' => [
|
||||
'neve-themes-popular' => [
|
||||
'env' => ! $has_neve && ! $has_neve_from_promo,
|
||||
'screen' => 'themes-install-popular',
|
||||
],
|
||||
],
|
||||
'redirection-cf7' => [
|
||||
'redirection-cf7' => [
|
||||
'wpcf7' => [
|
||||
'env' => ! $has_redirection_cf7 && ! $had_redirection_cf7_promo,
|
||||
'screen' => 'wpcf7',
|
||||
'delayed' => true,
|
||||
],
|
||||
],
|
||||
'hyve' => [
|
||||
'hyve' => [
|
||||
'hyve-plugins-install' => [
|
||||
'env' => $is_min_php_8_1 && ! $has_hyve && ! $had_hyve_from_promo && $has_hyve_conditions,
|
||||
'env' => $is_min_php_7_4 && ! $has_hyve && ! $had_hyve_from_promo && $has_hyve_conditions,
|
||||
'screen' => 'plugin-install',
|
||||
],
|
||||
],
|
||||
'wp_full_pay' => [
|
||||
'wp_full_pay' => [
|
||||
'wp-full-pay-plugins-install' => [
|
||||
'env' => ! $has_wfp_full_pay && ! $had_wfp_from_promo && $has_wfp_conditions,
|
||||
'screen' => 'plugin-install',
|
||||
],
|
||||
],
|
||||
'learning-management-system' => [
|
||||
'masteriyo-plugins-install' => [
|
||||
'env' => $is_min_php_7_2 && ! $has_masteriyo && ! $had_masteriyo_from_promo && $has_masteriyo_conditions,
|
||||
'screen' => 'plugin-install',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ( $all as $slug => $data ) {
|
||||
@ -723,6 +751,10 @@ class Promotions extends Abstract_Module {
|
||||
add_action( 'admin_notices', [ $this, 'render_wp_full_pay_notice' ] );
|
||||
}
|
||||
|
||||
if ( $this->get_upsells_dismiss_time( 'masteriyo-plugins-install' ) === false ) {
|
||||
add_action( 'admin_notices', [ $this, 'render_masteriyo_notice' ] );
|
||||
}
|
||||
|
||||
add_action( 'load-import.php', [ $this, 'add_import' ] );
|
||||
$this->load_woo_promos();
|
||||
|
||||
@ -788,6 +820,10 @@ class Promotions extends Abstract_Module {
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
|
||||
add_action( 'admin_notices', [ $this, 'render_wp_full_pay_notice' ] );
|
||||
break;
|
||||
case 'masteriyo-plugins-install':
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
|
||||
add_action( 'admin_notices', [ $this, 'render_masteriyo_notice' ] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -881,6 +917,8 @@ class Promotions extends Abstract_Module {
|
||||
'hyveDash' => esc_url( add_query_arg( [ 'page' => 'wpfs-settings-stripe' ], admin_url( 'admin.php' ) ) ),
|
||||
'wpFullPayActivationUrl' => $this->get_plugin_activation_link( 'wp-full-stripe-free' ),
|
||||
'wpFullPayDash' => esc_url( add_query_arg( [ 'page' => 'wpfs-settings-stripe' ], admin_url( 'admin.php' ) ) ),
|
||||
'masteriyoActivationUrl' => $this->get_plugin_activation_link( 'masteriyo' ),
|
||||
'masteriyoDash' => esc_url( add_query_arg( [ 'page' => 'masteriyo-onboard' ], admin_url( 'index.php' ) ) ),
|
||||
'nevePreviewURL' => esc_url( add_query_arg( [ 'theme' => 'neve' ], admin_url( 'theme-install.php' ) ) ),
|
||||
'neveAction' => $neve_action,
|
||||
'activateNeveURL' => esc_url(
|
||||
@ -940,6 +978,13 @@ class Promotions extends Abstract_Module {
|
||||
echo '<div id="ti-redirection-cf7-notice" class="notice notice-info ti-sdk-om-notice"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Masteriyo notice.
|
||||
*/
|
||||
public function render_masteriyo_notice() {
|
||||
echo '<div id="ti-masteriyo-notice" class="notice notice-info ti-sdk-om-notice"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add promo to attachment modal.
|
||||
*
|
||||
@ -1406,4 +1451,22 @@ class Promotions extends Abstract_Module {
|
||||
|
||||
return 'yes' === $has_donate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the tagline contains LMS related keywords.
|
||||
*
|
||||
* @return bool True if the tagline contains LMS-related keywords, false otherwise.
|
||||
*/
|
||||
public function has_lms_tagline() {
|
||||
$tagline = strtolower( get_bloginfo( 'description' ) );
|
||||
$lms_keywords = array( 'learning', 'courses' );
|
||||
|
||||
foreach ( $lms_keywords as $keyword ) {
|
||||
if ( strpos( $tagline, $keyword ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ class Script_Loader extends Abstract_Module {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( 'tracking' !== $slug && 'survey' !== $slug && 'banner' !== $slug ) {
|
||||
if ( 'tracking' !== $slug && 'survey' !== $slug ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -120,8 +120,6 @@ class Script_Loader extends Abstract_Module {
|
||||
$this->load_tracking( $handler );
|
||||
} elseif ( 'survey' === $slug ) {
|
||||
$this->load_survey( $handler );
|
||||
} elseif ( 'banner' === $slug ) {
|
||||
$this->load_banner( $handler );
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +172,7 @@ class Script_Loader extends Abstract_Module {
|
||||
|
||||
$common_data = [
|
||||
'userId' => $user_id,
|
||||
'apiHost' => 'https://app.formbricks.com',
|
||||
'appUrl' => 'https://app.formbricks.com',
|
||||
'attributes' => [
|
||||
'language' => $lang_code,
|
||||
],
|
||||
@ -237,33 +235,6 @@ class Script_Loader extends Abstract_Module {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the banner script.
|
||||
*
|
||||
* @param string $handler The script handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load_banner( $handler ) {
|
||||
global $themeisle_sdk_max_path;
|
||||
$asset_file = require $themeisle_sdk_max_path . '/assets/js/build/banner/banner.asset.php';
|
||||
|
||||
wp_enqueue_script(
|
||||
$handler,
|
||||
$this->get_sdk_uri() . 'assets/js/build/banner/banner.js',
|
||||
$asset_file['dependencies'],
|
||||
$asset_file['version'],
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_style(
|
||||
$handler . '_style',
|
||||
$this->get_sdk_uri() . 'assets/css/banner.css',
|
||||
[],
|
||||
$asset_file['version']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask a secret with `*` for half of its length.
|
||||
*
|
||||
|
||||
@ -139,10 +139,36 @@ class Product {
|
||||
$install = get_option( $this->get_key() . '_install', 0 );
|
||||
if ( 0 === $install ) {
|
||||
$install = time();
|
||||
/**
|
||||
* Action to be triggered when the product is first activated.
|
||||
*
|
||||
* @param string $basefile The basefile of the product.
|
||||
*/
|
||||
do_action( 'themeisle_sdk_first_activation', $basefile );
|
||||
|
||||
update_option( $this->get_key() . '_install', time() );
|
||||
}
|
||||
$this->install = $install;
|
||||
self::$cached_products[ crc32( $basefile ) ] = $this;
|
||||
$current_version = get_option( $this->slug . '_version', '' );
|
||||
|
||||
if ( $current_version !== $this->version && wp_cache_get( "{$this->slug}_version_upgrade" ) === false ) {
|
||||
// Set the cache lock to avoid multiple calls.
|
||||
wp_cache_set( "{$this->slug}_version_upgrade", true, HOUR_IN_SECONDS );
|
||||
/**
|
||||
* Action to be triggered when the product is updated.
|
||||
*
|
||||
* @param string $current_version The current version of the product.
|
||||
* @param string $new_version The new version of the product.
|
||||
* @param string $basefile The basefile of the product.
|
||||
*/
|
||||
do_action( "themeisle_sdk_update_{$this->slug}", $current_version, $this->version, $basefile );
|
||||
|
||||
// Update the version of the product.
|
||||
update_option( "{$this->slug}_version", $this->version );
|
||||
// Delete the cache lock.
|
||||
wp_cache_delete( "{$this->slug}_version_upgrade" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -41,6 +41,8 @@ $files_to_load = [
|
||||
$themeisle_library_path . '/src/Modules/Announcements.php',
|
||||
$themeisle_library_path . '/src/Modules/Featured_plugins.php',
|
||||
$themeisle_library_path . '/src/Modules/Float_widget.php',
|
||||
$themeisle_library_path . '/src/Modules/Abstract_Migration.php',
|
||||
$themeisle_library_path . '/src/Modules/Migrator.php',
|
||||
];
|
||||
|
||||
$files_to_load = array_merge( $files_to_load, apply_filters( 'themeisle_sdk_required_files', [] ) );
|
||||
|
||||
Reference in New Issue
Block a user