PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.9
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.9
4.0.9 4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 All 150 releases
← All changes | vendor/codeinwp/themeisle-sdk/src/Modules/Promotions.php +1097 -100 3.10.15 → 4.0.9 View file →
@@ -31,9 +31,9 @@
31 31 * Holds the promotions.
32 32 *
33 33 * @var array
34 34 */
35 - private $promotions = array();
35 + public $promotions = array();
36 36
37 37 /**
38 38 * Holds the values of the promotions that are not allowed to be shown.
39 39 * Can be filtered by each product.
@@ -70,15 +70,57 @@
70 70 */
71 71 private $option_rop = 'themeisle_sdk_promotions_rop_installed';
72 72
73 73 /**
74 - * Option key for Neve FSE promos.
74 + * Option key for Neve promo.
75 75 *
76 76 * @var string
77 77 */
78 - private $option_neve_fse = 'themeisle_sdk_promotions_neve_fse_installed';
78 + private $option_neve = 'themeisle_sdk_promotions_neve_installed';
79 79
80 80 /**
81 + * Option key for Redirection for CF7.
82 + *
83 + * @var string
84 + */
85 + private $option_redirection_cf7 = 'themeisle_sdk_promotions_redirection_cf7_installed';
86 +
87 + /**
88 + * Option key for Hyve.
89 + *
90 + * @var string
91 + */
92 + private $option_hyve = 'themeisle_sdk_promotions_hyve_installed';
93 +
94 + /**
95 + * Option key for WP Full Pay.
96 + *
97 + * @var string
98 + */
99 + private $option_wp_full_pay = 'themeisle_sdk_promotions_wp_full_pay_installed';
100 +
101 + /**
102 + * Option key for Feedzy.
103 + *
104 + * @var string
105 + */
106 + private $option_feedzy = 'themeisle_sdk_promotions_feedzy_installed';
107 +
108 + /**
109 + * Option key for Masteriyo promos.
110 + *
111 + * @var string
112 + */
113 + private $option_masteriyo = 'themeisle_sdk_promotions_masteriyo_installed';
114 +
115 + /**
116 + * Option key for Easy MCP AI promos.
117 + *
118 + * @var string
119 + */
120 + private $option_easy_mcp = 'themeisle_sdk_promotions_easy_mcp_installed';
121 +
122 + /**
81 123 * Loaded promotion.
82 124 *
83 125 * @var string
84 126 */
@@ -84,8 +126,15 @@
84 126 */
85 127 private $loaded_promo;
86 128
87 129 /**
130 + * Whether promotions were processed for the current screen.
131 + *
132 + * @var bool
133 + */
134 + private $available_promotions_loaded = false;
135 +
136 + /**
88 137 * Woo promotions.
89 138 *
90 139 * @var array
91 140 */
@@ -112,15 +161,27 @@
112 161 if ( $this->is_from_partner( $product ) ) {
113 162 return false;
114 163 }
115 164
116 - $this->debug = apply_filters( 'themeisle_sdk_promo_debug', $this->debug );
117 - $promotions_to_load = apply_filters( $product->get_key() . '_load_promotions', array() );
165 + $this->debug = apply_filters( 'themeisle_sdk_promo_debug', $this->debug );
166 + $promotions_to_load = apply_filters( $product->get_key() . '_load_promotions', array() );
167 +
118 168 $promotions_to_load[] = 'optimole';
119 169 $promotions_to_load[] = 'rop';
120 170 $promotions_to_load[] = 'woo_plugins';
121 - $promotions_to_load[] = 'neve-fse';
171 + $promotions_to_load[] = 'neve';
172 + $promotions_to_load[] = 'redirection-cf7';
173 + $promotions_to_load[] = 'hyve';
174 + $promotions_to_load[] = 'wp_full_pay';
175 + $promotions_to_load[] = 'feedzy_import';
176 + $promotions_to_load[] = 'learning-management-system';
177 + $promotions_to_load[] = 'easy-mcp';
122 178
179 + if ( defined( 'NEVE_VERSION' ) || defined( 'WPMM_PATH' ) || defined( 'OTTER_BLOCKS_VERSION' ) || defined( 'OBFX_URL' ) ) {
180 + $promotions_to_load[] = 'feedzy_embed';
181 + }
182 + $promotions_to_load = array_unique( $promotions_to_load );
183 +
123 184 $this->promotions = $this->get_promotions();
124 185
125 186 $this->dissallowed_promotions = apply_filters( $product->get_key() . '_dissallowed_promotions', array() );
126 187
@@ -128,9 +189,8 @@
128 189 if ( ! in_array( $slug, $promotions_to_load, true ) ) {
129 190 unset( $this->promotions[ $slug ] );
130 191 }
131 192 }
132 -
133 193 add_action( 'init', array( $this, 'register_settings' ), 99 );
134 194 add_action( 'admin_init', array( $this, 'register_reference' ), 99 );
135 195
136 196 return ! empty( $this->promotions );
@@ -145,32 +205,236 @@
145 205 if ( ! $this->is_writeable() || ! current_user_can( 'install_plugins' ) ) {
146 206 return;
147 207 }
148 208
149 - $this->product = $product;
209 + $last_dismiss_time = $this->get_last_dismiss_time();
150 210
151 - $last_dismiss = $this->get_last_dismiss_time();
152 -
153 - if ( ! $this->debug && $last_dismiss && ( time() - $last_dismiss ) < 7 * DAY_IN_SECONDS ) {
211 + if ( ! $this->debug && is_int( $last_dismiss_time ) && ( time() - $last_dismiss_time ) < ( 3 * WEEK_IN_SECONDS ) ) {
154 212 return;
155 213 }
156 214
215 + $this->product = $product;
216 +
157 217 add_filter( 'attachment_fields_to_edit', array( $this, 'add_attachment_field' ), 10, 2 );
158 218 add_action( 'current_screen', [ $this, 'load_available' ] );
159 219 add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'enqueue' ) );
220 + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_visualizer_block_editor_shim' ), 100 );
160 221 add_action( 'wp_ajax_tisdk_update_option', array( $this, 'dismiss_promotion' ) );
222 + add_filter( 'plugins_api_result', array( $this, 'inject_visualizer_block_directory_suggestion' ), 10, 3 );
223 + add_filter( 'option_visualizer-activated', array( $this, 'suppress_visualizer_onboarding_in_editor' ) );
161 224 add_filter( 'themeisle_sdk_ran_promos', '__return_true' );
162 225
163 - if ( get_option( $this->option_neve_fse, false ) !== true ) {
164 - add_action( 'wp_ajax_themeisle_sdk_dismiss_notice', 'ThemeisleSDK\Modules\Notification::regular_dismiss' );
226 + if ( get_option( $this->option_neve, false ) !== true ) {
227 + add_action( 'wp_ajax_themeisle_sdk_dismiss_notice', 'ThemeisleSDK\Modules\Notification::dismiss' );
165 228 }
166 229 }
167 230
231 +
168 232 /**
233 + * Inject Visualizer as the first block-directory suggestion for chart queries.
234 + *
235 + * @param object|WP_Error $res Response object or WP_Error.
236 + * @param string $action The API action.
237 + * @param object $args The API arguments.
238 + * @return object|WP_Error
239 + */
240 + public function inject_visualizer_block_directory_suggestion( $res, $action, $args ) {
241 + if ( 'query_plugins' !== $action || ! is_object( $args ) || empty( $args->block ) ) {
242 + return $res;
243 + }
244 +
245 + if ( ! $this->should_suggest_visualizer( $args->block ) ) {
246 + return $res;
247 + }
248 +
249 + if ( $this->is_plugin_installed( 'visualizer' ) ) {
250 + return $res;
251 + }
252 +
253 + $plugin = $this->get_visualizer_block_directory_data();
254 + if ( empty( $plugin ) ) {
255 + return $res;
256 + }
257 +
258 + if ( is_wp_error( $res ) ) {
259 + $res = (object) array( 'plugins' => array() );
260 + }
261 +
262 + if ( ! isset( $res->plugins ) || ! is_array( $res->plugins ) ) {
263 + $res->plugins = array();
264 + }
265 +
266 + $res->plugins = array_values(
267 + array_filter(
268 + $res->plugins,
269 + function ( $existing ) use ( $plugin ) {
270 + return ! isset( $existing['slug'] ) || $existing['slug'] !== $plugin['slug'];
271 + }
272 + )
273 + );
274 +
275 + array_unshift( $res->plugins, $plugin );
276 +
277 + return $res;
278 + }
279 +
280 + /**
281 + * Check if the query should trigger the Visualizer suggestion.
282 + *
283 + * @param string $term Search term.
284 + * @return bool
285 + */
286 + private function should_suggest_visualizer( $term ) {
287 + $term = strtolower( (string) $term );
288 + return false !== strpos( $term, 'chart' ) || false !== strpos( $term, 'visualizer' ) || false !== strpos( $term, 'visualization' ) || false !== strpos( $term, 'graph' );
289 + }
290 +
291 + /**
292 + * Build the plugin data for Visualizer block directory results.
293 + *
294 + * @return array
295 + */
296 + private function get_visualizer_block_directory_data() {
297 + $slug = 'visualizer';
298 + $plugin_info = $this->call_plugin_api( $slug );
299 +
300 + if ( is_wp_error( $plugin_info ) || empty( $plugin_info ) ) {
301 + return array();
302 + }
303 +
304 + $icons = array();
305 + if ( ! empty( $plugin_info->icons ) ) {
306 + if ( ! empty( $plugin_info->icons['1x'] ) ) {
307 + $icons['1x'] = $plugin_info->icons['1x'];
308 + }
309 + if ( ! empty( $plugin_info->icons['2x'] ) ) {
310 + $icons['2x'] = $plugin_info->icons['2x'];
311 + }
312 + }
313 +
314 + $name = isset( $plugin_info->name ) ? $plugin_info->name : 'Visualizer';
315 +
316 + return array(
317 + 'slug' => $slug,
318 + 'name' => $name,
319 + 'short_description' => isset( $plugin_info->short_description ) ? $plugin_info->short_description : '',
320 + 'author' => isset( $plugin_info->author ) ? wp_strip_all_tags( $plugin_info->author ) : '',
321 + 'rating' => isset( $plugin_info->rating ) ? (int) $plugin_info->rating : 0,
322 + 'num_ratings' => isset( $plugin_info->num_ratings ) ? (int) $plugin_info->num_ratings : 0,
323 + 'active_installs' => isset( $plugin_info->active_installs ) ? (int) $plugin_info->active_installs : 0,
324 + 'author_block_rating' => isset( $plugin_info->author_block_rating ) ? (int) $plugin_info->author_block_rating : 0,
325 + 'author_block_count' => isset( $plugin_info->author_block_count ) ? (int) $plugin_info->author_block_count : 0,
326 + 'icons' => $icons,
327 + 'last_updated' => isset( $plugin_info->last_updated ) ? $plugin_info->last_updated : gmdate( 'Y-m-d H:i:s' ),
328 + 'blocks' => array(
329 + array(
330 + 'name' => 'visualizer/chart',
331 + 'title' => $name,
332 + ),
333 + ),
334 + );
335 + }
336 +
337 +
338 + /**
339 + * Prevent Visualizer onboarding redirects while in the block editor.
340 + *
341 + * @param mixed $value Option value.
342 + * @return mixed
343 + */
344 + public function suppress_visualizer_onboarding_in_editor( $value ) {
345 + if ( ! $this->is_block_editor_screen() ) {
346 + return $value;
347 + }
348 +
349 + return false;
350 + }
351 +
352 + /**
353 + * Add a small compatibility shim for Visualizer's block editor bundle.
354 + *
355 + * Visualizer's "Display an existing chart" flow reads
356 + * `google.visualization.Version` before the Google Charts loader has fully
357 + * populated `google.visualization`, which can throw after dynamic install.
358 + *
359 + * @return void
360 + */
361 + public function enqueue_visualizer_block_editor_shim() {
362 + global $themeisle_sdk_max_version;
363 +
364 + if ( ! $this->is_block_editor_screen() ) {
365 + return;
366 + }
367 +
368 + if ( ! wp_script_is( 'visualizer-gutenberg-block', 'enqueued' ) ) {
369 + return;
370 + }
371 +
372 + wp_register_script(
373 + 'ti-sdk-visualizer-editor-shim',
374 + '',
375 + array(),
376 + $themeisle_sdk_max_version,
377 + true
378 + );
379 + wp_enqueue_script( 'ti-sdk-visualizer-editor-shim' );
380 + wp_add_inline_script(
381 + 'ti-sdk-visualizer-editor-shim',
382 + 'window.google = window.google || {}; window.google.visualization = window.google.visualization || {}; window.google.visualization.Version = window.google.visualization.Version || "current";'
383 + );
384 + }
385 +
386 + /**
387 + * Check if the current admin screen is the block editor.
388 + *
389 + * @return bool
390 + */
391 + private function is_block_editor_screen() {
392 + if ( ! function_exists( 'get_current_screen' ) ) {
393 + return $this->is_block_editor_request();
394 + }
395 +
396 + $screen = get_current_screen();
397 + if ( is_object( $screen ) && method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() ) {
398 + return true;
399 + }
400 +
401 + return $this->is_block_editor_request();
402 + }
403 +
404 + /**
405 + * Detect block editor requests before the current screen is available.
406 + *
407 + * @return bool
408 + */
409 + private function is_block_editor_request() {
410 + global $pagenow;
411 +
412 + if ( 'post-new.php' === $pagenow ) {
413 + $post_type = isset( $_GET['post_type'] ) ? sanitize_key( $_GET['post_type'] ) : 'post';
414 +
415 + return function_exists( 'use_block_editor_for_post_type' ) ? use_block_editor_for_post_type( $post_type ) : true;
416 + }
417 +
418 + if ( 'post.php' === $pagenow && isset( $_GET['post'] ) ) {
419 + $post_id = absint( $_GET['post'] );
420 +
421 + return function_exists( 'use_block_editor_for_post' ) ? use_block_editor_for_post( $post_id ) : true;
422 + }
423 +
424 + return false;
425 + }
426 +
427 + /**
169 428 * Load available promotions.
170 429 */
171 430 public function load_available() {
172 - $this->promotions = $this->filter_by_screen_and_merge();
431 + if ( $this->available_promotions_loaded ) {
432 + return;
433 + }
434 +
435 + $this->available_promotions_loaded = true;
436 + $this->promotions = $this->filter_by_screen_and_merge();
173 437 if ( empty( $this->promotions ) ) {
174 438 return;
175 439 }
176 440
@@ -208,11 +472,28 @@
208 472 if ( isset( $_GET['rop_reference_key'] ) ) {
209 473 update_option( 'rop_reference_key', sanitize_key( $_GET['rop_reference_key'] ) );
210 474 }
211 475
212 - if ( isset( $_GET['neve_fse_reference_key'] ) ) {
213 - update_option( 'neve_fse_reference_key', sanitize_key( $_GET['neve_fse_reference_key'] ) );
476 + if ( isset( $_GET['neve_reference_key'] ) ) {
477 + update_option( 'neve_reference_key', sanitize_key( $_GET['neve_reference_key'] ) );
214 478 }
479 +
480 + if ( isset( $_GET['hyve_reference_key'] ) ) {
481 + update_option( 'hyve_reference_key', sanitize_key( $_GET['hyve_reference_key'] ) );
482 + }
483 +
484 + if ( isset( $_GET['wp_full_pay_reference_key'] ) ) {
485 + update_option( 'wp_full_pay_reference_key', sanitize_key( $_GET['wp_full_pay_reference_key'] ) );
486 + }
487 +
488 + if ( isset( $_GET['easy_mcp_reference_key'] ) ) {
489 + update_option( 'easy_mcp_reference_key', sanitize_key( $_GET['easy_mcp_reference_key'] ) );
490 + }
491 +
492 + if ( isset( $_GET['feedzy_reference_key'] ) || ( isset( $_GET['from'], $_GET['plugin'] ) && $_GET['from'] === 'import' && str_starts_with( sanitize_key( $_GET['plugin'] ), 'feedzy' ) ) ) {
493 + update_option( 'feedzy_reference_key', sanitize_key( $_GET['feedzy_reference_key'] ?? 'i-' . $this->product->get_key() ) );
494 + update_option( $this->option_feedzy, 1 );
495 + }
215 496 }
216 497
217 498 /**
218 499 * Register Settings
@@ -262,9 +543,9 @@
262 543 )
263 544 );
264 545 register_setting(
265 546 'themeisle_sdk_settings',
266 - $this->option_neve_fse,
547 + $this->option_neve,
267 548 array(
268 549 'type' => 'boolean',
269 550 'sanitize_callback' => 'rest_sanitize_boolean',
270 551 'show_in_rest' => true,
@@ -270,8 +551,58 @@
270 551 'show_in_rest' => true,
271 552 'default' => false,
272 553 )
273 554 );
555 + register_setting(
556 + 'themeisle_sdk_settings',
557 + $this->option_redirection_cf7,
558 + array(
559 + 'type' => 'boolean',
560 + 'sanitize_callback' => 'rest_sanitize_boolean',
561 + 'show_in_rest' => true,
562 + 'default' => false,
563 + )
564 + );
565 + register_setting(
566 + 'themeisle_sdk_settings',
567 + $this->option_hyve,
568 + array(
569 + 'type' => 'boolean',
570 + 'sanitize_callback' => 'rest_sanitize_boolean',
571 + 'show_in_rest' => true,
572 + 'default' => false,
573 + )
574 + );
575 + register_setting(
576 + 'themeisle_sdk_settings',
577 + $this->option_wp_full_pay,
578 + array(
579 + 'type' => 'boolean',
580 + 'sanitize_callback' => 'rest_sanitize_boolean',
581 + 'show_in_rest' => true,
582 + 'default' => false,
583 + )
584 + );
585 + register_setting(
586 + 'themeisle_sdk_settings',
587 + $this->option_masteriyo,
588 + array(
589 + 'type' => 'boolean',
590 + 'sanitize_callback' => 'rest_sanitize_boolean',
591 + 'show_in_rest' => true,
592 + 'default' => false,
593 + )
594 + );
595 + register_setting(
596 + 'themeisle_sdk_settings',
597 + $this->option_easy_mcp,
598 + array(
599 + 'type' => 'boolean',
600 + 'sanitize_callback' => 'rest_sanitize_boolean',
601 + 'show_in_rest' => true,
602 + 'default' => false,
603 + )
604 + );
274 605 }
275 606
276 607 /**
277 608 * Check if the path is writable.
@@ -312,33 +643,56 @@
312 643 *
313 644 * @return array
314 645 */
315 646 private function get_promotions() {
316 - $has_otter = defined( 'OTTER_BLOCKS_VERSION' ) || $this->is_plugin_installed( 'otter-blocks' );
317 - $had_otter_from_promo = get_option( $this->option_otter, false );
318 - $has_optimole = defined( 'OPTIMOLE_VERSION' ) || $this->is_plugin_installed( 'optimole-wp' );
319 - $had_optimole_from_promo = get_option( $this->option_optimole, false );
320 - $has_rop = defined( 'ROP_LITE_VERSION' ) || $this->is_plugin_installed( 'tweet-old-post' );
321 - $had_rop_from_promo = get_option( $this->option_rop, false );
322 - $has_woocommerce = class_exists( 'WooCommerce' );
323 - $has_sparks = defined( 'SPARKS_WC_VERSION' ) || $this->is_plugin_installed( 'sparks-for-woocommerce' );
324 - $has_ppom = defined( 'PPOM_VERSION' ) || $this->is_plugin_installed( 'woocommerce-product-addon' );
325 - $is_min_req_v = version_compare( get_bloginfo( 'version' ), '5.8', '>=' );
326 - $is_min_fse_v = version_compare( get_bloginfo( 'version' ), '6.2', '>=' );
327 - $current_theme = wp_get_theme();
328 - $has_neve_fse = $current_theme->template === 'neve-fse' || $current_theme->parent() === 'neve-fse';
329 - $has_enough_attachments = $this->has_min_media_attachments();
330 - $has_enough_old_posts = $this->has_old_posts();
647 + $has_otter = defined( 'OTTER_BLOCKS_VERSION' ) || $this->is_plugin_installed( 'otter-blocks' );
648 + $had_otter_from_promo = get_option( $this->option_otter, false );
649 + $has_optimole = defined( 'OPTIMOLE_VERSION' ) || $this->is_plugin_installed( 'optimole-wp' );
650 + $had_optimole_from_promo = get_option( $this->option_optimole, false );
651 + $has_rop = defined( 'ROP_LITE_VERSION' ) || $this->is_plugin_installed( 'tweet-old-post' );
652 + $had_rop_from_promo = get_option( $this->option_rop, false );
653 + $has_woocommerce = class_exists( 'WooCommerce' );
654 + $has_sparks = defined( 'SPARKS_WC_VERSION' ) || $this->is_plugin_installed( 'sparks-for-woocommerce' );
655 + $has_ppom = defined( 'PPOM_VERSION' ) || $this->is_plugin_installed( 'woocommerce-product-addon' );
656 + $has_redirection_cf7 = defined( 'WPCF7_PRO_REDIRECT_PLUGIN_VERSION' ) || $this->is_plugin_installed( 'wpcf7-redirect' );
657 + $had_redirection_cf7_promo = get_option( $this->option_redirection_cf7, false );
658 + $is_min_php_7_4 = version_compare( PHP_VERSION, '7.4', '>=' );
659 + $is_min_php_7_2 = version_compare( PHP_VERSION, '7.2', '>=' );
660 + $can_check_plugin_install = $this->can_check_plugin_install_promo();
661 + $has_hyve = defined( 'HYVE_LITE_VERSION' ) || $this->is_plugin_installed( 'hyve' ) || $this->is_plugin_installed( 'hyve-lite' );
662 + $had_hyve_from_promo = get_option( $this->option_hyve, false );
663 + $has_hyve_conditions = $is_min_php_7_4 && ! $has_hyve && ! $had_hyve_from_promo && version_compare( get_bloginfo( 'version' ), '6.2', '>=' ) && $can_check_plugin_install && $this->has_support_page();
664 + $has_wfp_full_pay = defined( 'WP_FULL_STRIPE_BASENAME' ) || $this->is_plugin_installed( 'wp-full-stripe-free' );
665 + $had_wfp_from_promo = get_option( $this->option_wp_full_pay, false );
666 + $has_wfp_conditions = ! $has_wfp_full_pay && ! $had_wfp_from_promo && $can_check_plugin_install && $this->has_donate_page();
667 + $is_min_req_v = version_compare( get_bloginfo( 'version' ), '5.8', '>=' );
668 + $current_theme = wp_get_theme();
669 + $has_neve = $current_theme->template === 'neve' || $current_theme->parent() === 'neve';
670 + $has_neve_from_promo = get_option( $this->option_neve, false );
671 + $has_enough_attachments = $this->has_min_media_attachments();
672 + $has_enough_old_posts = $this->has_old_posts();
673 + $has_feedzy = defined( 'FEEDZY_BASEFILE' ) || $this->is_plugin_installed( 'feedzy-rss-feedss' );
674 + $had_feedzy_from_promo = get_option( $this->option_feedzy, false );
675 + $had_masteriyo_from_promo = get_option( $this->option_masteriyo, false );
676 + $has_masteriyo_conditions = $is_min_php_7_2 && ! $had_masteriyo_from_promo && ! $this->has_active_lms_plugin() && $can_check_plugin_install && $this->has_lms_tagline();
677 + $has_easy_mcp = defined( 'EASY_MCP_AI_VERSION' ) || $this->any_plugin_dir_exists( array( 'easy-mcp-ai' ) );
678 + $had_easy_mcp_from_promo = get_option( $this->option_easy_mcp, false );
679 + $has_easy_mcp_base = $is_min_php_7_4 && ! $has_easy_mcp && ! $had_easy_mcp_from_promo && version_compare( get_bloginfo( 'version' ), '7.0', '>=' );
680 + $has_easy_mcp_conditions = $has_easy_mcp_base && $can_check_plugin_install && ! $this->has_mcp_plugin_installed() && $this->has_ai_usage_signal();
681 + // On the own-profile screen the Application Passwords section itself is the intent signal.
682 + $has_easy_mcp_profile_conditions = $this->can_check_profile_promo() && $has_easy_mcp_base && wp_is_application_passwords_available_for_user( wp_get_current_user() ) && ! $this->has_mcp_plugin_installed();
331 683
332 684 $all = [
333 - 'optimole' => [
685 + 'optimole' => [
334 686 'om-editor' => [
335 - 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
336 - 'screen' => 'editor',
687 + 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
688 + 'screen' => 'editor',
689 + 'delayed' => true,
337 690 ],
338 691 'om-image-block' => [
339 - 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
340 - 'screen' => 'editor',
692 + 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
693 + 'screen' => 'editor',
694 + 'delayed' => true,
341 695 ],
342 696 'om-attachment' => [
343 697 'env' => ! $has_optimole && ! $had_optimole_from_promo,
344 698 'screen' => 'media-editor',
@@ -347,33 +701,51 @@
347 701 'env' => ! $has_optimole && ! $had_optimole_from_promo && $has_enough_attachments,
348 702 'screen' => 'media',
349 703 ],
350 704 'om-elementor' => [
351 - 'env' => ! $has_optimole && ! $had_optimole_from_promo && defined( 'ELEMENTOR_VERSION' ),
352 - 'screen' => 'elementor',
705 + 'env' => ! $has_optimole && ! $had_optimole_from_promo && defined( 'ELEMENTOR_VERSION' ),
706 + 'screen' => 'elementor',
707 + 'delayed' => true,
353 708 ],
354 709 ],
355 - 'otter' => [
710 + 'feedzy_import' => [
711 + 'feedzy-import' => [
712 + 'env' => true,
713 + 'screen' => 'import',
714 + 'always' => true,
715 + ],
716 + ],
717 + 'feedzy_embed' => [
718 + 'feedzy-editor' => [
719 + 'env' => ! $has_feedzy && is_main_site() && ! $had_feedzy_from_promo,
720 + 'screen' => 'editor',
721 + ],
722 + ],
723 + 'otter' => [
356 724 'blocks-css' => [
357 - 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
358 - 'screen' => 'editor',
725 + 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
726 + 'screen' => 'editor',
727 + 'delayed' => true,
359 728 ],
360 729 'blocks-animation' => [
361 - 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
362 - 'screen' => 'editor',
730 + 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
731 + 'screen' => 'editor',
732 + 'delayed' => true,
363 733 ],
364 734 'blocks-conditions' => [
365 - 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
366 - 'screen' => 'editor',
735 + 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
736 + 'screen' => 'editor',
737 + 'delayed' => true,
367 738 ],
368 739 ],
369 - 'rop' => [
740 + 'rop' => [
370 741 'rop-posts' => [
371 - 'env' => ! $has_rop && ! $had_rop_from_promo && $has_enough_old_posts,
372 - 'screen' => 'edit-post',
742 + 'env' => ! $has_rop && ! $had_rop_from_promo && $has_enough_old_posts,
743 + 'screen' => 'edit-post',
744 + 'delayed' => true,
373 745 ],
374 746 ],
375 - 'woo_plugins' => [
747 + 'woo_plugins' => [
376 748 'ppom' => [
377 749 'env' => ! $has_ppom && $has_woocommerce,
378 750 'screen' => 'edit-product',
379 751 ],
@@ -389,14 +761,49 @@
389 761 'env' => ! $has_sparks && $has_woocommerce,
390 762 'screen' => 'edit-product',
391 763 ],
392 764 ],
393 - 'neve-fse' => [
394 - 'neve-fse-themes-popular' => [
395 - 'env' => ! $has_neve_fse && $is_min_fse_v,
765 + 'neve' => [
766 + 'neve-themes-popular' => [
767 + 'env' => ! $has_neve && ! $has_neve_from_promo,
396 768 'screen' => 'themes-install-popular',
397 769 ],
398 770 ],
771 + 'redirection-cf7' => [
772 + 'wpcf7' => [
773 + 'env' => ! $has_redirection_cf7 && ! $had_redirection_cf7_promo,
774 + 'screen' => 'wpcf7',
775 + 'delayed' => true,
776 + ],
777 + ],
778 + 'hyve' => [
779 + 'hyve-plugins-install' => [
780 + 'env' => $has_hyve_conditions,
781 + 'screen' => 'plugin-install',
782 + ],
783 + ],
784 + 'wp_full_pay' => [
785 + 'wp-full-pay-plugins-install' => [
786 + 'env' => $has_wfp_conditions,
787 + 'screen' => 'plugin-install',
788 + ],
789 + ],
790 + 'learning-management-system' => [
791 + 'masteriyo-plugins-install' => [
792 + 'env' => $has_masteriyo_conditions,
793 + 'screen' => 'plugin-install',
794 + ],
795 + ],
796 + 'easy-mcp' => [
797 + 'easy-mcp-plugins-install' => [
798 + 'env' => $has_easy_mcp_conditions,
799 + 'screen' => 'plugin-install',
800 + ],
801 + 'easy-mcp-profile' => [
802 + 'env' => $has_easy_mcp_profile_conditions,
803 + 'screen' => 'profile',
804 + ],
805 + ],
399 806 ];
400 807
401 808 foreach ( $all as $slug => $data ) {
402 809 foreach ( $data as $key => $conditions ) {
@@ -414,9 +821,8 @@
414 821 if ( empty( $all[ $slug ] ) ) {
415 822 unset( $all[ $slug ] );
416 823 }
417 824 }
418 -
419 825 return $all;
420 826 }
421 827
422 828 /**
@@ -441,20 +847,14 @@
441 847
442 848 /**
443 849 * Get the last dismiss time of a promotion.
444 850 *
445 - * @return int The timestamp of last dismiss, or install time - 4 days.
851 + * @return int | false The timestamp of last dismiss or false.
446 852 */
447 853 private function get_last_dismiss_time() {
448 854 $dismissed = $this->get_upsells_dismiss_time();
449 855
450 - if ( empty( $dismissed ) ) {
451 - // we return the product install time - 4 days because we want to show the upsell after 3 days,
452 - // and we move the product install time 4 days in the past.
453 - return $this->product->get_install_time() - 4 * DAY_IN_SECONDS;
454 - }
455 -
456 - return max( array_values( $dismissed ) );
856 + return empty( $dismissed ) ? false : max( array_values( $dismissed ) );
457 857 }
458 858
459 859 /**
460 860 * Filter by screen & merge into single array of keys.
@@ -463,19 +863,55 @@
463 863 */
464 864 private function filter_by_screen_and_merge() {
465 865 $current_screen = get_current_screen();
466 866
467 - $is_elementor = isset( $_GET['action'] ) && $_GET['action'] === 'elementor';
468 - $is_media = isset( $current_screen->id ) && $current_screen->id === 'upload';
469 - $is_posts = isset( $current_screen->id ) && $current_screen->id === 'edit-post';
470 - $is_editor = method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor();
471 - $is_theme_install = isset( $current_screen->id ) && ( $current_screen->id === 'theme-install' || $current_screen->id === 'themes' );
472 - $is_product = isset( $current_screen->id ) && $current_screen->id === 'product';
867 + $is_elementor = isset( $_GET['action'] ) && $_GET['action'] === 'elementor';
868 + $is_media = isset( $current_screen->id ) && $current_screen->id === 'upload';
869 + $is_posts = isset( $current_screen->id ) && $current_screen->id === 'edit-post';
870 + $is_editor = method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor();
871 + $is_theme_install = isset( $current_screen->id ) && ( $current_screen->id === 'theme-install' );
872 + $is_plugin_install = isset( $current_screen->id ) && ( $current_screen->id === 'plugin-install' );
873 + $is_profile = isset( $current_screen->id ) && $current_screen->id === 'profile';
874 + $is_product = isset( $current_screen->id ) && $current_screen->id === 'product';
875 + $is_import = isset( $current_screen->id ) && $current_screen->id === 'import';
876 + $is_cf7_install = isset( $current_screen->id ) && function_exists( 'str_contains' ) ? str_contains( $current_screen->id, 'page_wpcf7' ) : false;
473 877
474 - $return = [];
878 + $return = [];
879 + $product_install_time = (int) $this->product->get_install_time();
880 + $is_older = time() > ( $product_install_time + ( 3 * DAY_IN_SECONDS ) );
881 + $is_newer = time() < ( $product_install_time + ( 6 * HOUR_IN_SECONDS ) );
882 + foreach ( $this->promotions as $slug => $promos ) {
883 + if ( ! is_array( $promos ) ) {
884 + continue;
885 + }
475 886
476 - foreach ( $this->promotions as $slug => $promos ) {
477 887 foreach ( $promos as $key => $data ) {
888 + if ( ! is_array( $data ) || ! array_key_exists( 'screen', $data ) ) {
889 + unset( $this->promotions[ $slug ][ $key ] );
890 +
891 + continue;
892 + }
893 +
894 + $data = wp_parse_args(
895 + $data,
896 + [
897 + 'delayed' => false,
898 + 'always' => false,
899 + ]
900 + );
901 +
902 + if (
903 + ! $this->debug &&
904 + (
905 + ( $data['delayed'] === true && ! $is_older ) || // Skip promotions that are delayed for 3 days.
906 + $is_newer // Skip promotions for the first 6 hours after install.
907 + )
908 + && ! $data['always']
909 + ) {
910 + unset( $this->promotions[ $slug ][ $key ] );
911 +
912 + continue;
913 + }
478 914 switch ( $data['screen'] ) {
479 915 case 'media-editor':
480 916 if ( ! $is_media && ! $is_editor ) {
481 917 unset( $this->promotions[ $slug ][ $key ] );
@@ -490,8 +926,13 @@
490 926 if ( ! $is_editor || $is_elementor ) {
491 927 unset( $this->promotions[ $slug ][ $key ] );
492 928 }
493 929 break;
930 + case 'import':
931 + if ( ! $is_import ) {
932 + unset( $this->promotions[ $slug ][ $key ] );
933 + }
934 + break;
494 935 case 'elementor':
495 936 if ( ! $is_elementor ) {
496 937 unset( $this->promotions[ $slug ][ $key ] );
497 938 }
@@ -510,8 +951,23 @@
510 951 if ( ! $is_theme_install ) {
511 952 unset( $this->promotions[ $slug ][ $key ] );
512 953 }
513 954 break;
955 + case 'wpcf7':
956 + if ( ! $is_cf7_install ) {
957 + unset( $this->promotions[ $slug ][ $key ] );
958 + }
959 + break;
960 + case 'plugin-install':
961 + if ( ! $is_plugin_install ) {
962 + unset( $this->promotions[ $slug ][ $key ] );
963 + }
964 + break;
965 + case 'profile':
966 + if ( ! $is_profile ) {
967 + unset( $this->promotions[ $slug ][ $key ] );
968 + }
969 + break;
514 970 }
515 971 }
516 972
517 973 $return = array_merge( $return, $this->promotions[ $slug ] );
@@ -544,17 +1000,35 @@
544 1000 }
545 1001 if ( $this->get_upsells_dismiss_time( 'rop-posts' ) === false ) {
546 1002 add_action( 'admin_notices', [ $this, 'render_rop_dash_notice' ] );
547 1003 }
548 - if ( $this->get_upsells_dismiss_time( 'neve-fse-themes-popular' ) === false ) {
549 - add_action( 'admin_notices', [ $this, 'render_neve_fse_themes_notice' ] );
1004 + if ( $this->get_upsells_dismiss_time( 'neve-themes-popular' ) === false ) {
1005 + add_action( 'admin_notices', [ $this, 'render_neve_themes_notice' ] );
550 1006 }
1007 + if ( $this->get_upsells_dismiss_time( 'redirection-cf7' ) === false ) {
1008 + add_action( 'admin_notices', [ $this, 'render_redirection_cf7_notice' ] );
1009 + }
1010 + if ( $this->get_upsells_dismiss_time( 'hyve-plugins-install' ) === false ) {
1011 + add_action( 'admin_notices', [ $this, 'render_hyve_notice' ] );
1012 + }
551 1013
1014 + if ( $this->get_upsells_dismiss_time( 'wp-full-pay-plugins-install' ) === false ) {
1015 + add_action( 'admin_notices', [ $this, 'render_wp_full_pay_notice' ] );
1016 + }
1017 +
1018 + if ( $this->get_upsells_dismiss_time( 'masteriyo-plugins-install' ) === false ) {
1019 + add_action( 'admin_notices', [ $this, 'render_masteriyo_notice' ] );
1020 + }
1021 +
1022 + if ( $this->get_upsells_dismiss_time( 'easy-mcp-plugins-install' ) === false || $this->get_upsells_dismiss_time( 'easy-mcp-profile' ) === false ) {
1023 + add_action( 'admin_notices', [ $this, 'render_easy_mcp_notice' ] );
1024 + }
1025 +
1026 + add_action( 'load-import.php', [ $this, 'add_import' ] );
552 1027 $this->load_woo_promos();
553 1028
554 1029 return;
555 1030 }
556 -
557 1031 switch ( $slug ) {
558 1032 case 'om-editor':
559 1033 case 'om-image-block':
560 1034 case 'blocks-css':
@@ -572,8 +1046,15 @@
572 1046 case 'rop-posts':
573 1047 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
574 1048 add_action( 'admin_notices', [ $this, 'render_rop_dash_notice' ] );
575 1049 break;
1050 + case 'feedzy-import':
1051 + add_action( 'load-import.php', [ $this, 'add_import' ] );
1052 +
1053 + break;
1054 + case 'feedzy-editor':
1055 + add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue' ] );
1056 + break;
576 1057 case 'ppom':
577 1058 case 'sparks-wishlist':
578 1059 case 'sparks-announcement':
579 1060 case 'sparks-product-reviews':
@@ -578,10 +1059,10 @@
578 1059 case 'sparks-announcement':
579 1060 case 'sparks-product-reviews':
580 1061 $this->load_woo_promos();
581 1062 break;
582 - case 'neve-fse-themes-popular':
583 - // Remove any other notifications if Neve FSE promotion is showing
1063 + case 'neve-themes-popular':
1064 + // Remove any other notifications if Neve promotion is showing
584 1065 remove_action( 'admin_notices', array( 'ThemeisleSDK\Modules\Notification', 'show_notification' ) );
585 1066 remove_action(
586 1067 'wp_ajax_themeisle_sdk_dismiss_notice',
587 1068 array(
@@ -592,14 +1073,54 @@
592 1073 remove_action( 'admin_head', array( 'ThemeisleSDK\Modules\Notification', 'dismiss_get' ) );
593 1074 remove_action( 'admin_head', array( 'ThemeisleSDK\Modules\Notification', 'setup_notifications' ) );
594 1075 // Add required actions to display this notification
595 1076 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
596 - add_action( 'admin_notices', [ $this, 'render_neve_fse_themes_notice' ] );
1077 + add_action( 'admin_notices', [ $this, 'render_neve_themes_notice' ] );
597 1078 break;
1079 + case 'wpcf7':
1080 + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
1081 + add_action( 'admin_notices', [ $this, 'render_redirection_cf7_notice' ] );
1082 + break;
1083 + case 'hyve-plugins-install':
1084 + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
1085 + add_action( 'admin_notices', [ $this, 'render_hyve_notice' ] );
1086 + break;
1087 + case 'wp-full-pay-plugins-install':
1088 + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
1089 + add_action( 'admin_notices', [ $this, 'render_wp_full_pay_notice' ] );
1090 + break;
1091 + case 'masteriyo-plugins-install':
1092 + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
1093 + add_action( 'admin_notices', [ $this, 'render_masteriyo_notice' ] );
1094 + break;
1095 + case 'easy-mcp-plugins-install':
1096 + case 'easy-mcp-profile':
1097 + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
1098 + add_action( 'admin_notices', [ $this, 'render_easy_mcp_notice' ] );
1099 + break;
598 1100 }
599 1101 }
600 1102
601 1103 /**
1104 + * Add import row.
1105 + *
1106 + * @return void
1107 + */
1108 + public function add_import() {
1109 + global $wp_importers;
1110 + if ( isset( $wp_importers['feedzy-rss-feeds'] ) ) {
1111 + return;
1112 + }
1113 + $wp_importers['feedzy-rss-feeds'] = array( // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1114 + 'Feedzy',
1115 + sprintf( Loader::$labels['promotions']['feedzy']['import_desc'], '<span style="float: left; font-style: italic;margin-top:0.4em;">', $this->product->get_friendly_name(), '</span>' ),
1116 + 'install' => 'feedzy-rss-feeds',
1117 + );
1118 + if ( defined( 'FEEDZY_BASEFILE' ) ) {
1119 + unset( $wp_importers['feedzy-rss-feeds']['install'] );
1120 + }
1121 + }
1122 + /**
602 1123 * Render dashboard notice.
603 1124 */
604 1125 public function render_optimole_dash_notice() {
605 1126 $screen = get_current_screen();
@@ -622,33 +1143,70 @@
622 1143 $user = wp_get_current_user();
623 1144 $asset_file = require $themeisle_sdk_max_path . '/assets/js/build/promos/index.asset.php';
624 1145 $deps = array_merge( $asset_file['dependencies'], [ 'updates' ] );
625 1146
1147 + $themes = wp_get_themes();
1148 + $neve_action = isset( $themes['neve'] ) ? 'activate' : 'install';
1149 + $labels = Loader::$labels['promotions'];
1150 + $labels['feedzy']['editor_recommends'] = sprintf(
1151 + $labels['feedzy']['editor_recommends'],
1152 + $this->product->get_friendly_name(),
1153 + '<a target="_blank" href="' . add_query_arg(
1154 + array(
1155 + 'tab' => 'plugin-information',
1156 + 'plugin' => 'feedzy-rss-feeds',
1157 + '_wpnonce' => wp_create_nonce( 'activate-plugin_feedzy-rss-feeds' ),
1158 + 'feedzy_reference_key' => 'e-' . $this->product->get_key(),
1159 + ),
1160 + network_admin_url( 'plugin-install.php' )
1161 + ) . '">',
1162 + '</a>'
1163 + );
626 1164 wp_register_script( $handle, $themeisle_sdk_src . 'assets/js/build/promos/index.js', $deps, $asset_file['version'], true );
627 1165 wp_localize_script(
628 1166 $handle,
629 1167 'themeisleSDKPromotions',
630 1168 [
631 - 'debug' => $this->debug,
632 - 'labels' => [
633 - 'optimole' => Loader::$labels['promotions']['optimole'],
634 - ],
635 - 'email' => $user->user_email,
636 - 'showPromotion' => $this->loaded_promo,
637 - 'optionKey' => $this->option_main,
638 - 'product' => $this->product->get_name(),
639 - 'option' => empty( $saved ) ? new \stdClass() : $saved,
640 - 'nonce' => wp_create_nonce( 'wp_rest' ),
641 - 'assets' => $themeisle_sdk_src . 'assets/images/',
642 - 'optimoleApi' => esc_url( rest_url( 'optml/v1/register_service' ) ),
643 - 'optimoleActivationUrl' => $this->get_plugin_activation_link( 'optimole-wp' ),
644 - 'otterActivationUrl' => $this->get_plugin_activation_link( 'otter-blocks' ),
645 - 'ropActivationUrl' => $this->get_plugin_activation_link( 'tweet-old-post' ),
646 - 'optimoleDash' => esc_url( add_query_arg( [ 'page' => 'optimole' ], admin_url( 'upload.php' ) ) ),
647 - 'ropDash' => esc_url( add_query_arg( [ 'page' => 'TweetOldPost' ], admin_url( 'admin.php' ) ) ),
648 - 'neveFSEMoreUrl' => tsdk_utmify( 'https://themeisle.com/themes/neve-fse/', 'neve-fse-themes-popular', 'theme-install' ),
1169 + 'debug' => $this->debug,
1170 + 'labels' => $labels,
1171 + 'email' => $user->user_email,
1172 + 'showPromotion' => $this->loaded_promo,
1173 + 'optionKey' => $this->option_main,
1174 + 'product' => $this->product->get_name(),
1175 + 'option' => empty( $saved ) ? new \stdClass() : $saved,
1176 + 'nonce' => wp_create_nonce( 'wp_rest' ),
1177 + 'assets' => $themeisle_sdk_src . 'assets/images/',
1178 + 'optimoleApi' => esc_url( rest_url( 'optml/v1/register_service' ) ),
1179 + 'optimoleActivationUrl' => $this->get_plugin_activation_link( 'optimole-wp' ),
1180 + 'otterActivationUrl' => $this->get_plugin_activation_link( 'otter-blocks' ),
1181 + 'ropActivationUrl' => $this->get_plugin_activation_link( 'tweet-old-post' ),
1182 + 'optimoleDash' => esc_url( add_query_arg( [ 'page' => 'optimole' ], admin_url( 'upload.php' ) ) ),
1183 + 'ropDash' => esc_url( add_query_arg( [ 'page' => 'TweetOldPost' ], admin_url( 'admin.php' ) ) ),
649 1184 // translators: %s is the product name.
650 - 'title' => esc_html( sprintf( Loader::$labels['promotions']['recommended'], $this->product->get_name() ) ),
1185 + 'title' => esc_html( sprintf( Loader::$labels['promotions']['recommended'], $this->product->get_name() ) ),
1186 + 'redirectionCF7MoreUrl' => tsdk_utmify( 'https://docs.themeisle.com/collection/2014-redirection-for-contact-form-7', 'redirection-for-contact-form-7', 'plugin-install' ),
1187 + 'rfCF7ActivationUrl' => $this->get_plugin_activation_link( 'wpcf7-redirect' ),
1188 + 'cf7Dash' => esc_url( add_query_arg( [ 'page' => 'wpcf7-new' ], admin_url( 'admin.php' ) ) ),
1189 + 'hyveActivationUrl' => $this->get_plugin_activation_link( 'hyve-lite' ),
1190 + 'hyveDash' => esc_url( add_query_arg( [ 'page' => 'wpfs-settings-stripe' ], admin_url( 'admin.php' ) ) ),
1191 + 'wpFullPayActivationUrl' => $this->get_plugin_activation_link( 'wp-full-stripe-free' ),
1192 + 'wpFullPayDash' => esc_url( add_query_arg( [ 'page' => 'wpfs-settings-stripe' ], admin_url( 'admin.php' ) ) ),
1193 + 'masteriyoActivationUrl' => $this->get_plugin_activation_link( 'masteriyo' ),
1194 + 'masteriyoDash' => esc_url( add_query_arg( [ 'page' => 'masteriyo-onboard' ], admin_url( 'index.php' ) ) ),
1195 + 'easyMcpActivationUrl' => add_query_arg( 'easy_mcp_reference_key', 'n-' . $this->product->get_key(), remove_query_arg( 'optimole_reference_key', $this->get_plugin_activation_link( 'easy-mcp-ai' ) ) ),
1196 + 'easyMcpDash' => esc_url( add_query_arg( [ 'page' => 'easy-mcp-ai' ], admin_url( 'admin.php' ) ) ),
1197 + 'nevePreviewURL' => esc_url( add_query_arg( [ 'theme' => 'neve' ], admin_url( 'theme-install.php' ) ) ),
1198 + 'neveAction' => $neve_action,
1199 + 'activateNeveURL' => esc_url(
1200 + add_query_arg(
1201 + [
1202 + 'action' => 'activate',
1203 + 'stylesheet' => 'neve',
1204 + '_wpnonce' => wp_create_nonce( 'switch-theme_neve' ),
1205 + ],
1206 + admin_url( 'themes.php' )
1207 + )
1208 + ),
651 1209 ]
652 1210 );
653 1211 wp_enqueue_script( $handle );
654 1212 wp_enqueue_style( $handle, $themeisle_sdk_src . 'assets/js/build/promos/style-index.css', [ 'wp-components' ], $asset_file['version'] );
@@ -667,15 +1225,59 @@
667 1225 echo '<div id="ti-rop-notice" class="notice notice-info ti-sdk-rop-notice"></div>';
668 1226 }
669 1227
670 1228 /**
671 - * Render Neve FSE Themes notice.
1229 + * Render Neve Themes notice.
672 1230 */
673 - public function render_neve_fse_themes_notice() {
674 - echo '<div id="ti-neve-fse-notice" class="notice notice-info ti-sdk-neve-fse-notice"></div>';
1231 + public function render_neve_themes_notice() {
1232 + echo '<div id="ti-neve-notice" class="notice notice-info ti-sdk-om-notice"></div>';
675 1233 }
676 1234
677 1235 /**
1236 + * Render Hyve notice.
1237 + */
1238 + public function render_hyve_notice() {
1239 + echo '<div id="ti-hyve-notice" class="notice notice-info ti-sdk-om-notice"></div>';
1240 + }
1241 +
1242 + /**
1243 + * Render WP Full Pay notice.
1244 + */
1245 + public function render_wp_full_pay_notice() {
1246 + echo '<div id="ti-wp-full-pay-notice" class="notice notice-info ti-sdk-om-notice"></div>';
1247 + }
1248 +
1249 + /**
1250 + * Render Redirection for CF7 notice.
1251 + */
1252 + public function render_redirection_cf7_notice() {
1253 + echo '<div id="ti-redirection-cf7-notice" class="notice notice-info ti-sdk-om-notice"></div>';
1254 + }
1255 +
1256 + /**
1257 + * Render Masteriyo notice.
1258 + */
1259 + public function render_masteriyo_notice() {
1260 + echo '<div id="ti-masteriyo-notice" class="notice notice-info ti-sdk-om-notice"></div>';
1261 + }
1262 +
1263 + /**
1264 + * Render Easy MCP AI notice.
1265 + */
1266 + public function render_easy_mcp_notice() {
1267 + // A product on this site already offers the connector in its own words.
1268 + if ( class_exists( Ai_Connect::class ) && Ai_Connect::has_products() ) {
1269 + return;
1270 + }
1271 + // Cards are injected only when a paid product loaded Featured_plugins; only then can this request already show one.
1272 + if ( apply_filters( 'themeisle_sdk_plugin_api_filter_registered', false ) && $this->is_easy_mcp_card_request() ) {
1273 + return;
1274 + }
1275 +
1276 + echo '<div id="ti-easy-mcp-notice" class="notice notice-info ti-sdk-om-notice"></div>';
1277 + }
1278 +
1279 + /**
678 1280 * Add promo to attachment modal.
679 1281 *
680 1282 * @param array $fields Fields array.
681 1283 * @param \WP_Post $post Post object.
@@ -692,9 +1294,9 @@
692 1294 }
693 1295
694 1296 $meta = wp_get_attachment_metadata( $post->ID );
695 1297
696 - if ( isset( $meta['filesize'] ) && $meta['filesize'] < 200000 ) {
1298 + if ( isset( $meta['filesize'] ) && $meta['filesize'] < 100000 ) {
697 1299 return $fields;
698 1300 }
699 1301
700 1302 $fields['optimole'] = array(
@@ -922,9 +1524,8 @@
922 1524 .tisdk-suggestions_options a::before {
923 1525 content: url("data:image/svg+xml,%3Csvg fill='%23135e96' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='<?php echo esc_attr( $icon ); ?>'/%3E%3C/svg%3E") !important;
924 1526 min-width: 13px;
925 1527 max-width: 13px;
926 - margin: auto;
927 1528 }
928 1529
929 1530 .tisdk-suggestions_options.active a::before {
930 1531 content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='<?php echo esc_attr( $icon ); ?>'/%3E%3C/svg%3E") !important;
@@ -1082,6 +1683,402 @@
1082 1683 );
1083 1684
1084 1685 wp_send_json( $response );
1085 1686 wp_die();
1687 + }
1688 +
1689 + /**
1690 + * Check if the user has a support page.
1691 + */
1692 + public function has_support_page() {
1693 + $page_title_matches = $this->get_page_title_keyword_matches();
1694 +
1695 + return 'yes' === $page_title_matches['support'];
1696 + }
1697 +
1698 + /**
1699 + * Check if the user has a donate page.
1700 + */
1701 + public function has_donate_page() {
1702 + $page_title_matches = $this->get_page_title_keyword_matches();
1703 +
1704 + return 'yes' === $page_title_matches['donate'];
1705 + }
1706 +
1707 + /**
1708 + * Check if the tagline or a published page title contains LMS related keywords.
1709 + *
1710 + * @return bool True if LMS-related keywords are found, false otherwise.
1711 + */
1712 + public function has_lms_tagline() {
1713 + $tagline = strtolower( get_bloginfo( 'description' ) );
1714 +
1715 + foreach ( $this->get_lms_keywords() as $keyword ) {
1716 + if ( $this->has_lms_keyword_match( $tagline, $keyword ) ) {
1717 + return true;
1718 + }
1719 + }
1720 +
1721 + return $this->has_lms_page_title();
1722 + }
1723 +
1724 + /**
1725 + * Check if a supported LMS plugin is active.
1726 + *
1727 + * @return bool True if an LMS plugin is active, false otherwise.
1728 + */
1729 + private function has_active_lms_plugin() {
1730 + if ( defined( 'MASTERIYO_VERSION' ) ) {
1731 + return true;
1732 + }
1733 +
1734 + include_once ABSPATH . 'wp-admin/includes/plugin.php';
1735 +
1736 + $lms_plugins = array(
1737 + 'tutor/tutor.php',
1738 + 'sfwd-lms/sfwd_lms.php',
1739 + 'lifterlms/lifterlms.php',
1740 + 'sensei-lms/sensei-lms.php',
1741 + 'learnpress/learnpress.php',
1742 + 'masterstudy-lms-learning-management-system/masterstudy-lms-learning-management-system.php',
1743 + 'learning-management-system/lms.php',
1744 + );
1745 +
1746 + foreach ( $lms_plugins as $plugin ) {
1747 + if ( is_plugin_active( $plugin ) || ( is_multisite() && is_plugin_active_for_network( $plugin ) ) ) {
1748 + return true;
1749 + }
1750 + }
1751 +
1752 + return false;
1753 + }
1754 +
1755 + /**
1756 + * Check if the site shows signs of AI usage.
1757 + *
1758 + * @return bool True if an AI plugin is installed or a REST API application password is in use.
1759 + */
1760 + public function has_ai_usage_signal() {
1761 + return $this->has_ai_plugin_installed() || $this->has_application_password_in_use();
1762 + }
1763 +
1764 + /**
1765 + * Check if the current plugin-install request already surfaces the Easy MCP AI card,
1766 + * so the notice is not stacked on top of it: the Featured tab injects the card on
1767 + * WordPress 6.9+ and AI-related searches prepend it.
1768 + *
1769 + * @return bool True if the Easy MCP AI card is expected on the current request.
1770 + */
1771 + private function is_easy_mcp_card_request() {
1772 + $current_screen = get_current_screen();
1773 + if ( ! isset( $current_screen->id ) || 'plugin-install' !== $current_screen->id ) {
1774 + return false;
1775 + }
1776 +
1777 + // phpcs:disable WordPress.Security.NonceVerification.Recommended
1778 + $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : '';
1779 + $search = isset( $_GET['s'] ) ? strtolower( sanitize_text_field( wp_unslash( $_GET['s'] ) ) ) : '';
1780 + $paged = isset( $_GET['paged'] ) ? (int) $_GET['paged'] : 1;
1781 + // phpcs:enable WordPress.Security.NonceVerification.Recommended
1782 +
1783 + // Cards are only injected on the first results page.
1784 + if ( $paged > 1 ) {
1785 + return false;
1786 + }
1787 +
1788 + if ( '' === $tab || 'featured' === $tab ) {
1789 + return version_compare( get_bloginfo( 'version' ), '6.9', '>=' );
1790 + }
1791 +
1792 + if ( '' !== $search ) {
1793 + return Featured_Plugins::matches_ai_search_keywords( $search ) && ! Featured_Plugins::matches_lms_search_keywords( $search );
1794 + }
1795 +
1796 + return false;
1797 + }
1798 +
1799 + /**
1800 + * Check if a known AI plugin is installed, active or not.
1801 + *
1802 + * @return bool True if an AI plugin is installed, false otherwise.
1803 + */
1804 + private function has_ai_plugin_installed() {
1805 + return $this->any_plugin_dir_exists(
1806 + array(
1807 + 'ai-engine',
1808 + 'gpt3-ai-content-generator',
1809 + 'getgenie',
1810 + 'bertha-ai',
1811 + 'hyve',
1812 + 'hyve-lite',
1813 + )
1814 + );
1815 + }
1816 +
1817 + /**
1818 + * Check if any of the given plugin slugs has a directory in the plugins folder.
1819 + *
1820 + * @param array $slugs Plugin slugs.
1821 + *
1822 + * @return bool True if at least one plugin directory exists, false otherwise.
1823 + */
1824 + private function any_plugin_dir_exists( $slugs ) {
1825 + foreach ( $slugs as $slug ) {
1826 + if ( is_dir( WP_PLUGIN_DIR . '/' . $slug ) ) {
1827 + return true;
1828 + }
1829 + }
1830 +
1831 + return false;
1832 + }
1833 +
1834 + /**
1835 + * Check if a REST API application password is in use on the site.
1836 + *
1837 + * @return bool True if an application password is in use, false otherwise.
1838 + */
1839 + private function has_application_password_in_use() {
1840 + return class_exists( 'WP_Application_Passwords' ) && method_exists( 'WP_Application_Passwords', 'is_in_use' ) && \WP_Application_Passwords::is_in_use();
1841 + }
1842 +
1843 + /**
1844 + * Check if the current request is a profile screen where the Application
1845 + * Passwords section lives — a user wiring up an external REST client.
1846 + *
1847 + * @return bool True if a profile promo can be displayed, false otherwise.
1848 + */
1849 + private function can_check_profile_promo() {
1850 + if ( ! is_admin() || ! current_user_can( 'install_plugins' ) ) {
1851 + return false;
1852 + }
1853 +
1854 + $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
1855 + if ( isset( $current_screen->id ) ) {
1856 + return 'profile' === $current_screen->id;
1857 + }
1858 +
1859 + global $pagenow;
1860 +
1861 + return isset( $pagenow ) && 'profile.php' === $pagenow;
1862 + }
1863 +
1864 + /**
1865 + * Check if a competing MCP server plugin is installed, active or not.
1866 + *
1867 + * AI Engine ships an optional MCP feature but is intentionally kept in the
1868 + * AI-usage signals instead — its primary identity is an AI framework.
1869 + *
1870 + * @return bool True if an MCP server plugin is installed, false otherwise.
1871 + */
1872 + private function has_mcp_plugin_installed() {
1873 + return $this->any_plugin_dir_exists(
1874 + array(
1875 + 'vibe-ai',
1876 + 'royal-mcp',
1877 + 'stifli-flex-mcp',
1878 + 'miniorange-secure-mcp-server',
1879 + 'wsp-mcp-ai-agents-connector',
1880 + 'cowboy-mcp',
1881 + 'nibwp',
1882 + )
1883 + );
1884 + }
1885 +
1886 + /**
1887 + * Check if the current request can display a plugin install promo.
1888 + *
1889 + * @return bool True if a plugin install promo can be displayed, false otherwise.
1890 + */
1891 + private function can_check_plugin_install_promo() {
1892 + if ( ! is_admin() || ! current_user_can( 'install_plugins' ) ) {
1893 + return false;
1894 + }
1895 +
1896 + $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
1897 + if ( isset( $current_screen->id ) ) {
1898 + return $current_screen->id === 'plugin-install';
1899 + }
1900 +
1901 + global $pagenow;
1902 +
1903 + return isset( $pagenow ) && $pagenow === 'plugin-install.php';
1904 + }
1905 +
1906 + /**
1907 + * Check if a published page title contains LMS related keywords.
1908 + *
1909 + * @return bool True if an LMS-related page title is found, false otherwise.
1910 + */
1911 + private function has_lms_page_title() {
1912 + $lms_page_title_signal = get_transient( 'tisdk_lms_page_title_signal_v1' );
1913 +
1914 + if ( in_array( $lms_page_title_signal, array( 'yes', 'no' ), true ) ) {
1915 + return 'yes' === $lms_page_title_signal;
1916 + }
1917 +
1918 + $has_lms_page_title = $this->has_published_page_title_with_keywords( $this->get_lms_page_title_keywords() );
1919 +
1920 + set_transient( 'tisdk_lms_page_title_signal_v1', $has_lms_page_title ? 'yes' : 'no', 7 * DAY_IN_SECONDS );
1921 +
1922 + return $has_lms_page_title;
1923 + }
1924 +
1925 + /**
1926 + * Get cached keyword matches for published page titles.
1927 + *
1928 + * @return array Page title keyword matches.
1929 + */
1930 + private function get_page_title_keyword_matches() {
1931 + $default_matches = array(
1932 + 'support' => 'no',
1933 + 'donate' => 'no',
1934 + );
1935 +
1936 + $page_title_matches = get_transient( 'tisdk_page_title_signals_v1' );
1937 +
1938 + if ( is_array( $page_title_matches ) && empty( array_diff_key( $default_matches, $page_title_matches ) ) ) {
1939 + return array_merge( $default_matches, $page_title_matches );
1940 + }
1941 +
1942 + global $wpdb;
1943 +
1944 + $select_clauses = array();
1945 + $query_values = array();
1946 + $page_checks = $this->get_page_title_checks();
1947 +
1948 + foreach ( $page_checks as $match_key => $keywords ) {
1949 + $match_clauses = array();
1950 +
1951 + $query_values[] = 'page';
1952 + $query_values[] = 'publish';
1953 +
1954 + foreach ( $keywords as $keyword ) {
1955 + $match_clauses[] = 'post_title LIKE %s';
1956 + $query_values[] = '%' . $wpdb->esc_like( $keyword ) . '%';
1957 + }
1958 +
1959 + $select_clauses[] = 'EXISTS( SELECT 1 FROM ' . $wpdb->posts . ' WHERE post_type = %s AND post_status = %s AND ( ' . implode( ' OR ', $match_clauses ) . ' ) LIMIT 1 ) AS has_' . $match_key;
1960 + }
1961 +
1962 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1963 + $query = $wpdb->get_row( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1964 + $wpdb->prepare(
1965 + 'SELECT ' . implode( ', ', $select_clauses ),
1966 + $query_values
1967 + ),
1968 + ARRAY_A
1969 + );
1970 + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1971 +
1972 + $page_title_matches = $default_matches;
1973 +
1974 + if ( is_array( $query ) ) {
1975 + foreach ( array_keys( $page_checks ) as $match_key ) {
1976 + $page_title_matches[ $match_key ] = ! empty( $query[ 'has_' . $match_key ] ) ? 'yes' : 'no';
1977 + }
1978 + }
1979 +
1980 + set_transient( 'tisdk_page_title_signals_v1', $page_title_matches, 7 * DAY_IN_SECONDS );
1981 +
1982 + return $page_title_matches;
1983 + }
1984 +
1985 + /**
1986 + * Check if a keyword matches content.
1987 + *
1988 + * @param string $content Content to check.
1989 + * @param string $keyword Keyword to look for.
1990 + *
1991 + * @return bool True if the keyword matches, false otherwise.
1992 + */
1993 + private function has_lms_keyword_match( $content, $keyword ) {
1994 + if ( in_array( $keyword, array( 'lms', 'course', 'class' ), true ) ) {
1995 + return preg_match( '/(^|[^a-z0-9])' . preg_quote( $keyword, '/' ) . '([^a-z0-9]|$)/', $content ) === 1;
1996 + }
1997 +
1998 + return strpos( $content, $keyword ) !== false;
1999 + }
2000 +
2001 + /**
2002 + * Get LMS related keywords.
2003 + *
2004 + * @return array LMS related keywords.
2005 + */
2006 + private function get_lms_keywords() {
2007 + return array(
2008 + 'lms',
2009 + 'learning',
2010 + 'course',
2011 + 'courses',
2012 + 'academy',
2013 + 'training',
2014 + 'lesson',
2015 + 'lessons',
2016 + 'class',
2017 + 'classes',
2018 + 'student',
2019 + 'students',
2020 + 'teach',
2021 + 'teaching',
2022 + 'tutor',
2023 + 'quiz',
2024 + 'education',
2025 + 'online course',
2026 + 'online courses',
2027 + );
2028 + }
2029 +
2030 + /**
2031 + * Get LMS related keywords for page title matching.
2032 + *
2033 + * @return array LMS related keywords.
2034 + */
2035 + private function get_lms_page_title_keywords() {
2036 + return array_values( array_diff( $this->get_lms_keywords(), array( 'lms', 'course', 'class' ) ) );
2037 + }
2038 +
2039 + /**
2040 + * Check if a published page title contains any of the provided keywords.
2041 + *
2042 + * @param array $keywords Keywords to look for.
2043 + *
2044 + * @return bool True if a matching page title is found, false otherwise.
2045 + */
2046 + private function has_published_page_title_with_keywords( $keywords ) {
2047 + if ( empty( $keywords ) ) {
2048 + return false;
2049 + }
2050 +
2051 + global $wpdb;
2052 +
2053 + $query_values = array( 'page', 'publish' );
2054 + $clauses = array();
2055 +
2056 + foreach ( $keywords as $keyword ) {
2057 + $clauses[] = 'post_title LIKE %s';
2058 + $query_values[] = '%' . $wpdb->esc_like( $keyword ) . '%';
2059 + }
2060 +
2061 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
2062 + $page_title_match = $wpdb->get_var( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2063 + $wpdb->prepare(
2064 + 'SELECT 1 FROM ' . $wpdb->posts . ' WHERE post_type = %s AND post_status = %s AND ( ' . implode( ' OR ', $clauses ) . ' ) LIMIT 1',
2065 + $query_values
2066 + )
2067 + );
2068 + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
2069 +
2070 + return ! empty( $page_title_match );
2071 + }
2072 +
2073 + /**
2074 + * Get page title keyword checks.
2075 + *
2076 + * @return array Page title keyword checks.
2077 + */
2078 + private function get_page_title_checks() {
2079 + return array(
2080 + 'support' => array( 'support' ),
2081 + 'donate' => array( 'donate' ),
2082 + );
1086 2083 }
1087 2084 }