PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / trunk
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization vtrunk
4.2.14 4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 2.5.5 2.5.6 2.5.7 3.0.0 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.11.0 3.11.1 3.11.2 3.11.3 3.12.0 All 135 releases
← All changes | vendor/codeinwp/themeisle-sdk/src/Modules/Promotions.php +1173 -122 3.11.3 → trunk View file →
@@ -13,8 +13,9 @@
13 13
14 14 namespace ThemeisleSDK\Modules;
15 15
16 16 use ThemeisleSDK\Common\Abstract_Module;
17 +use ThemeisleSDK\Loader;
17 18 use ThemeisleSDK\Product;
18 19
19 20 // Exit if accessed directly.
20 21 if ( ! defined( 'ABSPATH' ) ) {
@@ -30,11 +31,19 @@
30 31 * Holds the promotions.
31 32 *
32 33 * @var array
33 34 */
34 - private $promotions = array();
35 + public $promotions = array();
35 36
36 37 /**
38 + * Holds the values of the promotions that are not allowed to be shown.
39 + * Can be filtered by each product.
40 + *
41 + * @var array
42 + */
43 + private $dissallowed_promotions = array();
44 +
45 + /**
37 46 * Option key for promos.
38 47 *
39 48 * @var string
40 49 */
@@ -61,15 +70,57 @@
61 70 */
62 71 private $option_rop = 'themeisle_sdk_promotions_rop_installed';
63 72
64 73 /**
65 - * Option key for Neve FSE promos.
74 + * Option key for Neve promo.
66 75 *
67 76 * @var string
68 77 */
69 - private $option_neve_fse = 'themeisle_sdk_promotions_neve_fse_installed';
78 + private $option_neve = 'themeisle_sdk_promotions_neve_installed';
70 79
71 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 + /**
72 123 * Loaded promotion.
73 124 *
74 125 * @var string
75 126 */
@@ -75,8 +126,15 @@
75 126 */
76 127 private $loaded_promo;
77 128
78 129 /**
130 + * Whether promotions were processed for the current screen.
131 + *
132 + * @var bool
133 + */
134 + private $available_promotions_loaded = false;
135 +
136 + /**
79 137 * Woo promotions.
80 138 *
81 139 * @var array
82 140 */
@@ -103,23 +161,36 @@
103 161 if ( $this->is_from_partner( $product ) ) {
104 162 return false;
105 163 }
106 164
107 - $this->debug = apply_filters( 'themeisle_sdk_promo_debug', $this->debug );
108 - $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 +
109 168 $promotions_to_load[] = 'optimole';
110 169 $promotions_to_load[] = 'rop';
111 170 $promotions_to_load[] = 'woo_plugins';
112 - $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';
113 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 +
114 184 $this->promotions = $this->get_promotions();
115 185
186 + $this->dissallowed_promotions = apply_filters( $product->get_key() . '_dissallowed_promotions', array() );
187 +
116 188 foreach ( $this->promotions as $slug => $data ) {
117 189 if ( ! in_array( $slug, $promotions_to_load, true ) ) {
118 190 unset( $this->promotions[ $slug ] );
119 191 }
120 192 }
121 -
122 193 add_action( 'init', array( $this, 'register_settings' ), 99 );
123 194 add_action( 'admin_init', array( $this, 'register_reference' ), 99 );
124 195
125 196 return ! empty( $this->promotions );
@@ -134,32 +205,236 @@
134 205 if ( ! $this->is_writeable() || ! current_user_can( 'install_plugins' ) ) {
135 206 return;
136 207 }
137 208
138 - $this->product = $product;
209 + $last_dismiss_time = $this->get_last_dismiss_time();
139 210
140 - $last_dismiss = $this->get_last_dismiss_time();
141 -
142 - 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 ) ) {
143 212 return;
144 213 }
145 214
215 + $this->product = $product;
216 +
146 217 add_filter( 'attachment_fields_to_edit', array( $this, 'add_attachment_field' ), 10, 2 );
147 218 add_action( 'current_screen', [ $this, 'load_available' ] );
148 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 );
149 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' ) );
150 224 add_filter( 'themeisle_sdk_ran_promos', '__return_true' );
151 225
152 - if ( get_option( $this->option_neve_fse, false ) !== true ) {
153 - 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' );
154 228 }
155 229 }
156 230
231 +
157 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 + /**
158 428 * Load available promotions.
159 429 */
160 430 public function load_available() {
161 - $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();
162 437 if ( empty( $this->promotions ) ) {
163 438 return;
164 439 }
165 440
@@ -172,8 +447,21 @@
172 447 *
173 448 * @return void
174 449 */
175 450 public function register_reference() {
451 + if ( ! current_user_can( 'activate_plugins' ) ) {
452 + return;
453 + }
454 +
455 + if ( ! isset( $_GET['plugin'] ) || ! isset( $_GET['_wpnonce'] ) ) {
456 + return;
457 + }
458 +
459 + $plugin = rawurldecode( $_GET['plugin'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
460 + if ( wp_verify_nonce( $_GET['_wpnonce'], 'activate-plugin_' . $plugin ) === false ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
461 + return;
462 + }
463 +
176 464 if ( isset( $_GET['reference_key'] ) ) {
177 465 update_option( 'otter_reference_key', sanitize_key( $_GET['reference_key'] ) );
178 466 }
179 467
@@ -184,11 +472,28 @@
184 472 if ( isset( $_GET['rop_reference_key'] ) ) {
185 473 update_option( 'rop_reference_key', sanitize_key( $_GET['rop_reference_key'] ) );
186 474 }
187 475
188 - if ( isset( $_GET['neve_fse_reference_key'] ) ) {
189 - 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'] ) );
190 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 + }
191 496 }
192 497
193 498 /**
194 499 * Register Settings
@@ -238,9 +543,9 @@
238 543 )
239 544 );
240 545 register_setting(
241 546 'themeisle_sdk_settings',
242 - $this->option_neve_fse,
547 + $this->option_neve,
243 548 array(
244 549 'type' => 'boolean',
245 550 'sanitize_callback' => 'rest_sanitize_boolean',
246 551 'show_in_rest' => true,
@@ -246,8 +551,58 @@
246 551 'show_in_rest' => true,
247 552 'default' => false,
248 553 )
249 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 + );
250 605 }
251 606
252 607 /**
253 608 * Check if the path is writable.
@@ -268,14 +623,14 @@
268 623 }
269 624
270 625 /**
271 626 * Third-party compatibility.
272 - *
627 + *
273 628 * @return boolean
274 629 */
275 630 private function has_conflicts() {
276 631 global $pagenow;
277 -
632 +
278 633 // Editor notices aren't compatible with Enfold theme.
279 634 if ( defined( 'AV_FRAMEWORK_VERSION' ) && in_array( $pagenow, array( 'post.php', 'post-new.php' ) ) ) {
280 635 return true;
281 636 }
@@ -288,33 +643,56 @@
288 643 *
289 644 * @return array
290 645 */
291 646 private function get_promotions() {
292 - $has_otter = defined( 'OTTER_BLOCKS_VERSION' ) || $this->is_plugin_installed( 'otter-blocks' );
293 - $had_otter_from_promo = get_option( $this->option_otter, false );
294 - $has_optimole = defined( 'OPTIMOLE_VERSION' ) || $this->is_plugin_installed( 'optimole-wp' );
295 - $had_optimole_from_promo = get_option( $this->option_optimole, false );
296 - $has_rop = defined( 'ROP_LITE_VERSION' ) || $this->is_plugin_installed( 'tweet-old-post' );
297 - $had_rop_from_promo = get_option( $this->option_rop, false );
298 - $has_woocommerce = class_exists( 'WooCommerce' );
299 - $has_sparks = defined( 'SPARKS_WC_VERSION' ) || $this->is_plugin_installed( 'sparks-for-woocommerce' );
300 - $has_ppom = defined( 'PPOM_VERSION' ) || $this->is_plugin_installed( 'woocommerce-product-addon' );
301 - $is_min_req_v = version_compare( get_bloginfo( 'version' ), '5.8', '>=' );
302 - $is_min_fse_v = version_compare( get_bloginfo( 'version' ), '6.2', '>=' );
303 - $current_theme = wp_get_theme();
304 - $has_neve_fse = $current_theme->template === 'neve-fse' || $current_theme->parent() === 'neve-fse';
305 - $has_enough_attachments = $this->has_min_media_attachments();
306 - $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();
307 683
308 684 $all = [
309 - 'optimole' => [
685 + 'optimole' => [
310 686 'om-editor' => [
311 - 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
312 - 'screen' => 'editor',
687 + 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
688 + 'screen' => 'editor',
689 + 'delayed' => true,
313 690 ],
314 691 'om-image-block' => [
315 - 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
316 - 'screen' => 'editor',
692 + 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
693 + 'screen' => 'editor',
694 + 'delayed' => true,
317 695 ],
318 696 'om-attachment' => [
319 697 'env' => ! $has_optimole && ! $had_optimole_from_promo,
320 698 'screen' => 'media-editor',
@@ -323,33 +701,51 @@
323 701 'env' => ! $has_optimole && ! $had_optimole_from_promo && $has_enough_attachments,
324 702 'screen' => 'media',
325 703 ],
326 704 'om-elementor' => [
327 - 'env' => ! $has_optimole && ! $had_optimole_from_promo && defined( 'ELEMENTOR_VERSION' ),
328 - 'screen' => 'elementor',
705 + 'env' => ! $has_optimole && ! $had_optimole_from_promo && defined( 'ELEMENTOR_VERSION' ),
706 + 'screen' => 'elementor',
707 + 'delayed' => true,
329 708 ],
330 709 ],
331 - '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' => [
332 724 'blocks-css' => [
333 - 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
334 - 'screen' => 'editor',
725 + 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
726 + 'screen' => 'editor',
727 + 'delayed' => true,
335 728 ],
336 729 'blocks-animation' => [
337 - 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
338 - 'screen' => 'editor',
730 + 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
731 + 'screen' => 'editor',
732 + 'delayed' => true,
339 733 ],
340 734 'blocks-conditions' => [
341 - 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
342 - 'screen' => 'editor',
735 + 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
736 + 'screen' => 'editor',
737 + 'delayed' => true,
343 738 ],
344 739 ],
345 - 'rop' => [
740 + 'rop' => [
346 741 'rop-posts' => [
347 - 'env' => ! $has_rop && ! $had_rop_from_promo && $has_enough_old_posts,
348 - 'screen' => 'edit-post',
742 + 'env' => ! $has_rop && ! $had_rop_from_promo && $has_enough_old_posts,
743 + 'screen' => 'edit-post',
744 + 'delayed' => true,
349 745 ],
350 746 ],
351 - 'woo_plugins' => [
747 + 'woo_plugins' => [
352 748 'ppom' => [
353 749 'env' => ! $has_ppom && $has_woocommerce,
354 750 'screen' => 'edit-product',
355 751 ],
@@ -365,14 +761,49 @@
365 761 'env' => ! $has_sparks && $has_woocommerce,
366 762 'screen' => 'edit-product',
367 763 ],
368 764 ],
369 - 'neve-fse' => [
370 - 'neve-fse-themes-popular' => [
371 - 'env' => ! $has_neve_fse && $is_min_fse_v,
765 + 'neve' => [
766 + 'neve-themes-popular' => [
767 + 'env' => ! $has_neve && ! $has_neve_from_promo,
372 768 'screen' => 'themes-install-popular',
373 769 ],
374 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 + ],
375 806 ];
376 807
377 808 foreach ( $all as $slug => $data ) {
378 809 foreach ( $data as $key => $conditions ) {
@@ -390,9 +821,8 @@
390 821 if ( empty( $all[ $slug ] ) ) {
391 822 unset( $all[ $slug ] );
392 823 }
393 824 }
394 -
395 825 return $all;
396 826 }
397 827
398 828 /**
@@ -417,20 +847,14 @@
417 847
418 848 /**
419 849 * Get the last dismiss time of a promotion.
420 850 *
421 - * @return int The timestamp of last dismiss, or install time - 4 days.
851 + * @return int | false The timestamp of last dismiss or false.
422 852 */
423 853 private function get_last_dismiss_time() {
424 854 $dismissed = $this->get_upsells_dismiss_time();
425 855
426 - if ( empty( $dismissed ) ) {
427 - // we return the product install time - 4 days because we want to show the upsell after 3 days,
428 - // and we move the product install time 4 days in the past.
429 - return $this->product->get_install_time() - 4 * DAY_IN_SECONDS;
430 - }
431 -
432 - return max( array_values( $dismissed ) );
856 + return empty( $dismissed ) ? false : max( array_values( $dismissed ) );
433 857 }
434 858
435 859 /**
436 860 * Filter by screen & merge into single array of keys.
@@ -439,19 +863,55 @@
439 863 */
440 864 private function filter_by_screen_and_merge() {
441 865 $current_screen = get_current_screen();
442 866
443 - $is_elementor = isset( $_GET['action'] ) && $_GET['action'] === 'elementor';
444 - $is_media = isset( $current_screen->id ) && $current_screen->id === 'upload';
445 - $is_posts = isset( $current_screen->id ) && $current_screen->id === 'edit-post';
446 - $is_editor = method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor();
447 - $is_theme_install = isset( $current_screen->id ) && ( $current_screen->id === 'theme-install' || $current_screen->id === 'themes' );
448 - $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;
449 877
450 - $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 + }
451 886
452 - foreach ( $this->promotions as $slug => $promos ) {
453 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 + }
454 914 switch ( $data['screen'] ) {
455 915 case 'media-editor':
456 916 if ( ! $is_media && ! $is_editor ) {
457 917 unset( $this->promotions[ $slug ][ $key ] );
@@ -466,8 +926,13 @@
466 926 if ( ! $is_editor || $is_elementor ) {
467 927 unset( $this->promotions[ $slug ][ $key ] );
468 928 }
469 929 break;
930 + case 'import':
931 + if ( ! $is_import ) {
932 + unset( $this->promotions[ $slug ][ $key ] );
933 + }
934 + break;
470 935 case 'elementor':
471 936 if ( ! $is_elementor ) {
472 937 unset( $this->promotions[ $slug ][ $key ] );
473 938 }
@@ -486,8 +951,23 @@
486 951 if ( ! $is_theme_install ) {
487 952 unset( $this->promotions[ $slug ][ $key ] );
488 953 }
489 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;
490 970 }
491 971 }
492 972
493 973 $return = array_merge( $return, $this->promotions[ $slug ] );
@@ -492,8 +972,16 @@
492 972
493 973 $return = array_merge( $return, $this->promotions[ $slug ] );
494 974 }
495 975
976 + $return = array_filter(
977 + $return,
978 + function ( $value, $key ) {
979 + return ! in_array( $key, $this->dissallowed_promotions, true );
980 + },
981 + ARRAY_FILTER_USE_BOTH
982 + );
983 +
496 984 return array_keys( $return );
497 985 }
498 986
499 987 /**
@@ -512,16 +1000,35 @@
512 1000 }
513 1001 if ( $this->get_upsells_dismiss_time( 'rop-posts' ) === false ) {
514 1002 add_action( 'admin_notices', [ $this, 'render_rop_dash_notice' ] );
515 1003 }
516 - if ( $this->get_upsells_dismiss_time( 'neve-fse-themes-popular' ) === false ) {
517 - 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' ] );
518 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 + }
519 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' ] );
520 1027 $this->load_woo_promos();
1028 +
521 1029 return;
522 1030 }
523 -
524 1031 switch ( $slug ) {
525 1032 case 'om-editor':
526 1033 case 'om-image-block':
527 1034 case 'blocks-css':
@@ -539,8 +1046,15 @@
539 1046 case 'rop-posts':
540 1047 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
541 1048 add_action( 'admin_notices', [ $this, 'render_rop_dash_notice' ] );
542 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;
543 1057 case 'ppom':
544 1058 case 'sparks-wishlist':
545 1059 case 'sparks-announcement':
546 1060 case 'sparks-product-reviews':
@@ -545,22 +1059,68 @@
545 1059 case 'sparks-announcement':
546 1060 case 'sparks-product-reviews':
547 1061 $this->load_woo_promos();
548 1062 break;
549 - case 'neve-fse-themes-popular':
550 - // 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
551 1065 remove_action( 'admin_notices', array( 'ThemeisleSDK\Modules\Notification', 'show_notification' ) );
552 - remove_action( 'wp_ajax_themeisle_sdk_dismiss_notice', array( 'ThemeisleSDK\Modules\Notification', 'dismiss' ) );
1066 + remove_action(
1067 + 'wp_ajax_themeisle_sdk_dismiss_notice',
1068 + array(
1069 + 'ThemeisleSDK\Modules\Notification',
1070 + 'dismiss',
1071 + )
1072 + );
553 1073 remove_action( 'admin_head', array( 'ThemeisleSDK\Modules\Notification', 'dismiss_get' ) );
554 1074 remove_action( 'admin_head', array( 'ThemeisleSDK\Modules\Notification', 'setup_notifications' ) );
555 1075 // Add required actions to display this notification
556 1076 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
557 - add_action( 'admin_notices', [ $this, 'render_neve_fse_themes_notice' ] );
1077 + add_action( 'admin_notices', [ $this, 'render_neve_themes_notice' ] );
558 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;
559 1100 }
560 1101 }
561 1102
562 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 + /**
563 1123 * Render dashboard notice.
564 1124 */
565 1125 public function render_optimole_dash_notice() {
566 1126 $screen = get_current_screen();
@@ -583,30 +1143,70 @@
583 1143 $user = wp_get_current_user();
584 1144 $asset_file = require $themeisle_sdk_max_path . '/assets/js/build/promos/index.asset.php';
585 1145 $deps = array_merge( $asset_file['dependencies'], [ 'updates' ] );
586 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 + );
587 1164 wp_register_script( $handle, $themeisle_sdk_src . 'assets/js/build/promos/index.js', $deps, $asset_file['version'], true );
588 1165 wp_localize_script(
589 1166 $handle,
590 1167 'themeisleSDKPromotions',
591 1168 [
592 - 'debug' => $this->debug,
593 - 'email' => $user->user_email,
594 - 'showPromotion' => $this->loaded_promo,
595 - 'optionKey' => $this->option_main,
596 - 'product' => $this->product->get_name(),
597 - 'option' => empty( $saved ) ? new \stdClass() : $saved,
598 - 'nonce' => wp_create_nonce( 'wp_rest' ),
599 - 'assets' => $themeisle_sdk_src . 'assets/images/',
600 - 'optimoleApi' => esc_url( rest_url( 'optml/v1/register_service' ) ),
601 - 'optimoleActivationUrl' => $this->get_plugin_activation_link( 'optimole-wp' ),
602 - 'otterActivationUrl' => $this->get_plugin_activation_link( 'otter-blocks' ),
603 - 'ropActivationUrl' => $this->get_plugin_activation_link( 'tweet-old-post' ),
604 - 'optimoleDash' => esc_url( add_query_arg( [ 'page' => 'optimole' ], admin_url( 'upload.php' ) ) ),
605 - 'ropDash' => esc_url( add_query_arg( [ 'page' => 'TweetOldPost' ], admin_url( 'admin.php' ) ) ),
606 - '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' ) ) ),
607 1184 // translators: %s is the product name.
608 - 'title' => esc_html( sprintf( __( 'Recommended by %s', 'textdomain' ), $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 + ),
609 1209 ]
610 1210 );
611 1211 wp_enqueue_script( $handle );
612 1212 wp_enqueue_style( $handle, $themeisle_sdk_src . 'assets/js/build/promos/style-index.css', [ 'wp-components' ], $asset_file['version'] );
@@ -625,15 +1225,55 @@
625 1225 echo '<div id="ti-rop-notice" class="notice notice-info ti-sdk-rop-notice"></div>';
626 1226 }
627 1227
628 1228 /**
629 - * Render Neve FSE Themes notice.
1229 + * Render Neve Themes notice.
630 1230 */
631 - public function render_neve_fse_themes_notice() {
632 - 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>';
633 1233 }
634 1234
635 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 + // Cards are injected only when a paid product loaded Featured_plugins; only then can this request already show one.
1268 + if ( apply_filters( 'themeisle_sdk_plugin_api_filter_registered', false ) && $this->is_easy_mcp_card_request() ) {
1269 + return;
1270 + }
1271 +
1272 + echo '<div id="ti-easy-mcp-notice" class="notice notice-info ti-sdk-om-notice"></div>';
1273 + }
1274 +
1275 + /**
636 1276 * Add promo to attachment modal.
637 1277 *
638 1278 * @param array $fields Fields array.
639 1279 * @param \WP_Post $post Post object.
@@ -650,9 +1290,9 @@
650 1290 }
651 1291
652 1292 $meta = wp_get_attachment_metadata( $post->ID );
653 1293
654 - if ( isset( $meta['filesize'] ) && $meta['filesize'] < 200000 ) {
1294 + if ( isset( $meta['filesize'] ) && $meta['filesize'] < 100000 ) {
655 1295 return $fields;
656 1296 }
657 1297
658 1298 $fields['optimole'] = array(
@@ -692,8 +1332,9 @@
692 1332
693 1333
694 1334 set_transient( 'tsk_attachment_count', $attachment_count, DAY_IN_SECONDS );
695 1335 }
1336 +
696 1337 return $attachment_count > 50;
697 1338 }
698 1339
699 1340 /**
@@ -753,10 +1394,10 @@
753 1394 */
754 1395 private function load_woo_promos() {
755 1396 $this->woo_promos = array(
756 1397 'ppom' => array(
757 - 'title' => 'Product Add-Ons',
758 - 'description' => 'Add extra custom fields & add-ons on your product pages, like sizes, colors & more.',
1398 + 'title' => Loader::$labels['promotions']['woo']['ppom_title'],
1399 + 'description' => Loader::$labels['promotions']['woo']['ppom_desc'],
759 1400 'icon' => '<svg width="25" height="25" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 1800"><path d="M241.023,324.818c0.252,0,0.505,0.035,0.758,0.035h465.68c17.266,0,31.256-13.99,31.256-31.252 c0-17.262-13.99-31.247-31.256-31.247H351.021h-109.24c-17.258,0-31.252,13.985-31.252,31.247 C210.529,310.605,224.121,324.412,241.023,324.818z"/><path d="M210.529,450.306c0,17.257,13.994,31.252,31.252,31.252h769.451c17.262,0,31.256-13.995,31.256-31.252 c0-17.266-13.994-31.252-31.256-31.252H241.781C224.523,419.054,210.529,433.04,210.529,450.306z"/><path d="M1011.232,575.751H241.781c-8.149,0-15.549,3.147-21.116,8.261c-6.213,5.712-10.136,13.879-10.136,22.987 c0,17.262,13.994,31.26,31.252,31.26h769.451c17.262,0,31.256-13.999,31.256-31.26c0-9.108-3.923-17.275-10.141-22.987 C1026.781,578.898,1019.386,575.751,1011.232,575.751z"/><path d="M1011.232,732.461H241.781c-17.258,0-31.252,13.99-31.252,31.247c0,17.262,13.994,31.257,31.252,31.257 h769.451c17.262,0,31.256-13.995,31.256-31.257C1042.488,746.451,1028.494,732.461,1011.232,732.461z"/><path d="M1011.232,889.157H241.781c-8.149,0-15.549,3.147-21.116,8.261c-6.213,5.713-10.136,13.879-10.136,22.987 c0,17.257,13.994,31.261,31.252,31.261h769.451c17.262,0,31.256-14.004,31.256-31.261c0-9.108-3.923-17.274-10.141-22.987 C1026.781,892.305,1019.386,889.157,1011.232,889.157z"/><path d="M1011.232,1045.867H241.781c-17.258,0-31.252,13.99-31.252,31.243c0,17.271,13.994,31.265,31.252,31.265 h769.451c17.262,0,31.256-13.994,31.256-31.265C1042.488,1059.857,1028.494,1045.867,1011.232,1045.867z"/><path d="M1011.232,1202.576H241.781c-17.258,0-31.252,13.995-31.252,31.252c0,17.258,13.994,31.252,31.252,31.252 h769.451c17.262,0,31.256-13.994,31.256-31.252C1042.488,1216.571,1028.494,1202.576,1011.232,1202.576z"/><path d="M1011.232,1359.273H241.781c-8.149,0-15.549,3.151-21.116,8.265c-6.213,5.713-10.136,13.875-10.136,22.987 c0,17.258,13.994,31.261,31.252,31.261h769.451c17.262,0,31.256-14.003,31.256-31.261c0-9.112-3.923-17.274-10.141-22.987 C1026.781,1362.425,1019.386,1359.273,1011.232,1359.273z"/><path d="M1233.542,251.228l-49.851-45.109L1052.136,87.076l-59.185-53.554c-5.293-4.792-11.947-7.421-18.786-7.836 h-3.49H83.676c-45.688,0-82.858,37.375-82.858,83.316v1583.612c0,45.94,37.17,83.316,82.858,83.316h1078.562 c45.68,0,82.845-37.376,82.845-83.316V277.08v-3.182C1244.646,264.73,1240.261,256.589,1233.542,251.228z M1003.117,125.864 l131.119,118.657h-131.119V125.864z M1183.691,1692.613c0,12.094-9.622,21.926-21.454,21.926H83.676 c-11.836,0-21.467-9.832-21.467-21.926V109.001c0-12.089,9.631-21.925,21.467-21.925h857.857V275.38 c0,17.052,13.785,30.862,30.786,30.862h211.372V1692.613z"/><path d="M1798.578,180.737c-7.049-88.305-81.114-158.02-171.205-158.02c-0.004,0-0.004,0-0.004,0 c-45.889,0-89.033,17.874-121.479,50.32c-29.18,29.175-46.519,67.005-49.73,107.699h-0.586v13.609c0,0.06-0.005,0.115-0.005,0.175 c0,0.026,0.005,0.056,0.005,0.082l-0.005,1369.26h0.197c0.557,5.404,2.522,10.731,6.047,15.373l141.135,185.91 c5.803,7.648,14.851,12.136,24.447,12.136c9.601-0.004,18.646-4.496,24.447-12.14l141.093-185.897 c3.528-4.65,5.494-9.982,6.051-15.391h0.197V180.737H1798.578z M1549.299,116.448c20.854-20.855,48.578-32.339,78.07-32.339h0.004 c50.24,0,92.746,33.723,106.076,79.718h-212.19C1526.358,146.098,1535.896,129.852,1549.299,116.448z M1595.372,1502.468 l-78.413,0.005l0.005-1260.345h220.828v1260.336h-81.103l0.009-1016.486l-61.335,0.004L1595.372,1502.468z M1627.382,1695.821 l-100.171-131.963l200.338-0.004L1627.382,1695.821z"/></svg>',
760 1401 'has_install' => true,
761 1402 'link' => wp_nonce_url(
762 1403 add_query_arg(
@@ -769,22 +1410,22 @@
769 1410 'install-plugin_woocommerce-product-addon'
770 1411 ),
771 1412 ),
772 1413 'sparks-wishlist' => array(
773 - 'title' => 'Wishlist',
774 - 'description' => 'Loyalize your customers by allowing them to save their favorite products.',
1414 + 'title' => Loader::$labels['promotions']['woo']['spark_title1'],
1415 + 'description' => Loader::$labels['promotions']['woo']['spark_desc1'],
775 1416 'icon' => '<svg width="25" height="25" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/SVG" aria-hidden="true"><path d="M60 21.25H57.5V40.125H60V21.25Z"></path><path d="M2.5 40H0V8.75C0 6.625 1.625 5 3.75 5H25V7.5H3.75C3 7.5 2.5 8 2.5 8.75V40Z"></path><path d="M56.25 51.25H3.75C1.625 51.25 0 49.625 0 47.5V42.5H25V45H2.5V47.5C2.5 48.25 3 48.75 3.75 48.75H56.25C57 48.75 57.5 48.25 57.5 47.5V43.75H60V47.5C60 49.625 58.375 51.25 56.25 51.25Z"></path><path d="M23.75 58.75H21.25V57.25L22.5 51.125V50H25V51.5L23.75 57.625V58.75Z"></path><path d="M38.75 58.75H36.25V57.625L35 51.25V50H37.5V51.125L38.75 57.5V58.75Z"></path><path d="M41.25 57.5H18.75V60H41.25V57.5Z"></path><path d="M56.25 32.5H43.75C41.625 32.5 40 30.875 40 28.75V3.75C40 1.625 41.625 0 43.75 0H56.25C58.375 0 60 1.625 60 3.75V28.75C60 30.875 58.375 32.5 56.25 32.5ZM43.75 2.5C43 2.5 42.5 3 42.5 3.75V28.75C42.5 29.5 43 30 43.75 30H56.25C57 30 57.5 29.5 57.5 28.75V3.75C57.5 3 57 2.5 56.25 2.5H43.75Z"></path><path d="M50 27.5C50.6904 27.5 51.25 26.9404 51.25 26.25C51.25 25.5596 50.6904 25 50 25C49.3096 25 48.75 25.5596 48.75 26.25C48.75 26.9404 49.3096 27.5 50 27.5Z"></path><path d="M51.25 45H31.25C29.125 45 27.5 43.375 27.5 41.25V8.75C27.5 6.625 29.125 5 31.25 5H37.5V7.5H31.25C30.5 7.5 30 8 30 8.75V41.25C30 42 30.5 42.5 31.25 42.5H51.25C52 42.5 52.5 42 52.5 41.25V35H55V41.25C55 43.375 53.375 45 51.25 45Z"></path><path d="M41.25 40C41.9404 40 42.5 39.4404 42.5 38.75C42.5 38.0596 41.9404 37.5 41.25 37.5C40.5596 37.5 40 38.0596 40 38.75C40 39.4404 40.5596 40 41.25 40Z"></path><path d="M21.75 40H18.25L13.25 35H11.75L6.75 40H0V37.5H5.75L10.75 32.5H14.25L19.25 37.5H20.75L52.875 5.375L54.625 7.125L21.75 40Z"></path><path d="M55 11.25H52.5V7.5H48.75V5H55V11.25Z"></path><defs><clip-path id="clip0"><rect width="60" height="60" fill="white"></rect></clip-path></defs></svg>',
776 1417 'link' => tsdk_utmify( 'https://themeisle.com/plugins/sparks-for-woocommerce/', 'promo', 'products-tabs' ),
777 1418 ),
778 1419 'sparks-announcement' => array(
779 - 'title' => 'Multi-Announcement Bars',
780 - 'description' => 'Add a top notification bar on your website to highlight the latest products, offers, or upcoming events.',
1420 + 'title' => Loader::$labels['promotions']['woo']['spark_title2'],
1421 + 'description' => Loader::$labels['promotions']['woo']['spark_desc2'],
781 1422 'icon' => '<svg width="25" height="25" viewBox="0 0 60 61" fill="none" xmlns="http://www.w3.org/2000/SVG" aria-hidden="true"><path d="M30 8.89282C29.6685 8.89282 29.3505 8.76113 29.1161 8.52671C28.8817 8.29228 28.75 7.97434 28.75 7.64282V1.39282C28.75 1.0613 28.8817 0.743359 29.1161 0.508939C29.3505 0.274518 29.6685 0.142822 30 0.142822C30.3315 0.142822 30.6495 0.274518 30.8839 0.508939C31.1183 0.743359 31.25 1.0613 31.25 1.39282V7.64282C31.25 7.97434 31.1183 8.29228 30.8839 8.52671C30.6495 8.76113 30.3315 8.89282 30 8.89282Z"></path><path d="M30 21.9105L26.25 18.1605V7.82598L26.9409 7.47992C27.8914 7.00723 28.9385 6.76123 30 6.76123C31.0615 6.76123 32.1086 7.00723 33.0591 7.47992L33.75 7.82598V18.1605L30 21.9105ZM28.75 17.1253L30 18.3753L31.25 17.1253V9.44219C30.4344 9.19928 29.5656 9.19928 28.75 9.44219V17.1253Z"></path><path d="M60 60.1428H0V22.6428H17.5C17.8315 22.6428 18.1495 22.7745 18.3839 23.0089C18.6183 23.2434 18.75 23.5613 18.75 23.8928C18.75 24.2243 18.6183 24.5423 18.3839 24.7767C18.1495 25.0111 17.8315 25.1428 17.5 25.1428H2.5V57.6428H57.5V25.1428H42.5C42.1685 25.1428 41.8505 25.0111 41.6161 24.7767C41.3817 24.5423 41.25 24.2243 41.25 23.8928C41.25 23.5613 41.3817 23.2434 41.6161 23.0089C41.8505 22.7745 42.1685 22.6428 42.5 22.6428H60V60.1428Z"></path><path d="M11.2493 53.8933C11.0421 53.8929 10.8383 53.841 10.6561 53.7424C10.474 53.6438 10.3191 53.5015 10.2055 53.3283C10.0919 53.1551 10.0231 52.9564 10.0052 52.75C9.98727 52.5436 10.0209 52.336 10.103 52.1458L26.353 14.6459C26.4182 14.4953 26.5125 14.359 26.6304 14.2448C26.7483 14.1306 26.8876 14.0408 27.0402 13.9804C27.1928 13.9201 27.3559 13.8903 27.52 13.893C27.6841 13.8956 27.8461 13.9306 27.9967 13.9958C28.1473 14.0611 28.2836 14.1553 28.3978 14.2732C28.5119 14.3912 28.6018 14.5304 28.6621 14.683C28.7225 14.8357 28.7522 14.9987 28.7496 15.1628C28.7469 15.3269 28.712 15.4889 28.6467 15.6395L12.3967 53.1395C12.2999 53.3634 12.1397 53.5541 11.9358 53.6881C11.7319 53.822 11.4932 53.8934 11.2493 53.8933Z"></path><path d="M48.7505 53.8935C48.5065 53.8935 48.2679 53.8222 48.064 53.6883C47.8601 53.5543 47.6999 53.3637 47.603 53.1398L31.353 15.6398C31.2212 15.3356 31.2157 14.9915 31.3376 14.6833C31.4595 14.375 31.6989 14.1278 32.003 13.9961C32.3072 13.8643 32.6513 13.8588 32.9595 13.9807C33.2678 14.1026 33.515 14.3419 33.6467 14.6461L49.8967 52.1461C49.9789 52.3363 50.0125 52.5439 49.9946 52.7503C49.9767 52.9566 49.9078 53.1553 49.7942 53.3285C49.6806 53.5018 49.5258 53.6441 49.3436 53.7427C49.1614 53.8413 48.9576 53.8932 48.7505 53.8936V53.8935Z"></path><path d="M30 33.8928C29.6685 33.8928 29.3505 33.7611 29.1161 33.5267C28.8817 33.2923 28.75 32.9743 28.75 32.6428V25.1428C28.75 24.8113 28.8817 24.4934 29.1161 24.2589C29.3505 24.0245 29.6685 23.8928 30 23.8928C30.3315 23.8928 30.6495 24.0245 30.8839 24.2589C31.1183 24.4934 31.25 24.8113 31.25 25.1428V32.6428C31.25 32.9743 31.1183 33.2923 30.8839 33.5267C30.6495 33.7611 30.3315 33.8928 30 33.8928Z"></path><path d="M45 30.1428H15C14.6685 30.1428 14.3505 30.0111 14.1161 29.7767C13.8817 29.5423 13.75 29.2243 13.75 28.8928C13.75 28.5613 13.8817 28.2434 14.1161 28.0089C14.3505 27.7745 14.6685 27.6428 15 27.6428H45C45.3315 27.6428 45.6495 27.7745 45.8839 28.0089C46.1183 28.2434 46.25 28.5613 46.25 28.8928C46.25 29.2243 46.1183 29.5423 45.8839 29.7767C45.6495 30.0111 45.3315 30.1428 45 30.1428Z"></path><defs><clip-path id="clip0"><rect width="60" height="60" fill="white" transform="translate(0 0.142822)"></rect></clip-path></defs></svg>',
782 1423 'link' => tsdk_utmify( 'https://themeisle.com/plugins/sparks-for-woocommerce/', 'promo', 'products-tabs' ),
783 1424 ),
784 1425 'sparks-product-review' => array(
785 - 'title' => 'Advanced Product Review',
786 - 'description' => 'Enable an advanced review section, enlarging the basic review options with lots of capabilities.',
1426 + 'title' => Loader::$labels['promotions']['woo']['spark_title3'],
1427 + 'description' => Loader::$labels['promotions']['woo']['spark_desc3'],
787 1428 'icon' => '<svg width="25" height="25" viewBox="0 0 60 61" fill="none" xmlns="http://www.w3.org/2000/SVG" aria-hidden="true"><path d="M58.75 54.1797H1.25C1.08584 54.1797 0.923271 54.1474 0.771595 54.0846C0.619919 54.0218 0.482103 53.9297 0.366021 53.8137C0.24994 53.6976 0.157867 53.5598 0.0950637 53.4081C0.0322604 53.2564 -4.26571e-05 53.0939 4.22759e-08 52.9297V6.67969C-4.26571e-05 6.51552 0.0322604 6.35296 0.0950637 6.20128C0.157867 6.04961 0.24994 5.91179 0.366021 5.79571C0.482103 5.67963 0.619919 5.58755 0.771595 5.52475C0.923271 5.46195 1.08584 5.42964 1.25 5.42969H58.75C58.9142 5.42964 59.0767 5.46195 59.2284 5.52475C59.3801 5.58755 59.5179 5.67963 59.634 5.79571C59.7501 5.91179 59.8421 6.04961 59.9049 6.20128C59.9677 6.35296 60 6.51552 60 6.67969V52.9297C60 53.0939 59.9677 53.2564 59.9049 53.4081C59.8421 53.5598 59.7501 53.6976 59.634 53.8137C59.5179 53.9297 59.3801 54.0218 59.2284 54.0846C59.0767 54.1474 58.9142 54.1797 58.75 54.1797ZM2.5 51.6797H57.5V7.92969H2.5V51.6797Z"></path><path d="M6.25 15.4297C6.94036 15.4297 7.5 14.87 7.5 14.1797C7.5 13.4893 6.94036 12.9297 6.25 12.9297C5.55964 12.9297 5 13.4893 5 14.1797C5 14.87 5.55964 15.4297 6.25 15.4297Z"></path><path d="M10 15.4297C10.6904 15.4297 11.25 14.87 11.25 14.1797C11.25 13.4893 10.6904 12.9297 10 12.9297C9.30964 12.9297 8.75 13.4893 8.75 14.1797C8.75 14.87 9.30964 15.4297 10 15.4297Z"></path><path d="M13.75 15.4297C14.4404 15.4297 15 14.87 15 14.1797C15 13.4893 14.4404 12.9297 13.75 12.9297C13.0596 12.9297 12.5 13.4893 12.5 14.1797C12.5 14.87 13.0596 15.4297 13.75 15.4297Z"></path><path d="M58.75 15.4297H18.75C18.4185 15.4297 18.1005 15.298 17.8661 15.0636C17.6317 14.8292 17.5 14.5112 17.5 14.1797C17.5 13.8482 17.6317 13.5302 17.8661 13.2958C18.1005 13.0614 18.4185 12.9297 18.75 12.9297H58.75C59.0815 12.9297 59.3995 13.0614 59.6339 13.2958C59.8683 13.5302 60 13.8482 60 14.1797C60 14.5112 59.8683 14.8292 59.6339 15.0636C59.3995 15.298 59.0815 15.4297 58.75 15.4297Z"></path><path d="M28.7502 37.9297C28.4187 37.9295 28.1009 37.7978 27.8664 37.5634L24.4289 34.1259C24.198 33.8908 24.0693 33.574 24.0708 33.2445C24.0723 32.915 24.2039 32.5994 24.4369 32.3664C24.6699 32.1334 24.9855 32.0018 25.315 32.0003C25.6445 31.9988 25.9613 32.1275 26.1964 32.3584L28.6977 34.8597L38.8588 23.4522C38.968 23.3296 39.1002 23.2298 39.2479 23.1583C39.3957 23.0869 39.5561 23.0452 39.7199 23.0358C39.8838 23.0263 40.0479 23.0492 40.2029 23.1032C40.3579 23.1571 40.5008 23.2411 40.6233 23.3503C40.7459 23.4594 40.8457 23.5917 40.9172 23.7394C40.9886 23.8872 41.0303 24.0476 41.0397 24.2114C41.0492 24.3753 41.0263 24.5394 40.9723 24.6944C40.9184 24.8494 40.8344 24.9922 40.7253 25.1148L29.6834 37.511C29.5702 37.6382 29.4322 37.7409 29.2779 37.8129C29.1237 37.8849 28.9563 37.9247 28.7862 37.9298L28.7502 37.9297Z"></path><path d="M29.977 44.1812C28.3217 44.1775 26.6876 43.8085 25.1912 43.1007C23.6948 42.3928 22.3731 41.3635 21.3203 40.0861C20.2675 38.8087 19.5095 37.3148 19.1004 35.7108C18.6913 34.1068 18.6413 32.4322 18.9537 30.8067C19.2662 29.1811 19.9335 27.6445 20.9081 26.3065C21.8827 24.9684 23.1406 23.862 24.592 23.0659C26.0433 22.2699 27.6525 21.804 29.3046 21.7013C30.9568 21.5987 32.6113 21.8619 34.15 22.4722C34.4579 22.5949 34.7044 22.8349 34.8354 23.1393C34.9663 23.4438 34.9709 23.7878 34.8482 24.0957C34.7255 24.4036 34.4856 24.6501 34.1811 24.7811C33.8766 24.912 33.5326 24.9166 33.2247 24.7939C31.44 24.0862 29.472 23.985 27.6241 24.5059C25.7762 25.0269 24.1508 26.141 22.9985 27.6767C21.8462 29.2124 21.2308 31.0844 21.2473 33.0043C21.2637 34.9242 21.9111 36.7854 23.0895 38.3011C24.268 39.8168 25.9122 40.903 27.7688 41.3922C29.6254 41.8813 31.5913 41.7464 33.3637 41.0082C35.136 40.27 36.6164 38.9694 37.5768 37.3069C38.5372 35.6444 38.9242 33.7122 38.6782 31.8081C38.6568 31.6451 38.6678 31.4795 38.7104 31.3208C38.7531 31.1621 38.8267 31.0133 38.927 30.8831C39.0272 30.7528 39.1522 30.6436 39.2947 30.5617C39.4373 30.4798 39.5945 30.4268 39.7575 30.4058C39.9206 30.3848 40.0861 30.3961 40.2448 30.4391C40.4034 30.4822 40.552 30.5561 40.682 30.6566C40.8121 30.7572 40.921 30.8824 41.0026 31.0251C41.0842 31.1678 41.1368 31.3252 41.1574 31.4883C41.3469 32.9535 41.2459 34.4417 40.8602 35.8679C40.4745 37.294 39.8116 38.6303 38.9094 39.8002C38.0072 40.9702 36.8834 41.9509 35.6021 42.6865C34.3208 43.4221 32.9071 43.898 31.4419 44.0872C30.9561 44.1494 30.4668 44.1807 29.977 44.1812Z"></path><defs><clip-path id="clip0"><rect width="60" height="60" fill="white" transform="translate(0 0.429688)"></rect></clip-path></defs></svg>',
788 1429 'link' => tsdk_utmify( 'https://themeisle.com/plugins/sparks-for-woocommerce/', 'promo', 'products-tabs' ),
789 1430 ),
790 1431 );
@@ -797,11 +1438,11 @@
797 1438 }
798 1439
799 1440 add_action(
800 1441 'woocommerce_product_data_tabs',
801 - function( $tabs ) {
1442 + function ( $tabs ) {
802 1443 $tabs['tisdk-suggestions'] = array(
803 - 'label' => 'More extensions from Themeisle',
1444 + 'label' => Loader::$labels['promotions']['woo']['title'],
804 1445 'target' => 'tisdk_suggestions',
805 1446 'class' => array(),
806 1447 'priority' => 1000,
807 1448 );
@@ -819,9 +1460,9 @@
819 1460 public function woocommerce_tab_content() {
820 1461 // Filter content based on if the key exists in $this->promotions array.
821 1462 $content = array_filter(
822 1463 $this->woo_promos,
823 - function( $key ) {
1464 + function ( $key ) {
824 1465 return in_array( $key, $this->promotions, true );
825 1466 },
826 1467 ARRAY_FILTER_USE_KEY
827 1468 );
@@ -833,9 +1474,9 @@
833 1474 ?>
834 1475
835 1476 <div id="tisdk_suggestions" class="panel woocommerce_options_panel hidden">
836 1477 <div class="tisdk-suggestions-header">
837 - <h4>Recommended extensions</h4>
1478 + <h4><?php echo esc_html( Loader::$labels['promotions']['woo']['title2'] ); ?></h4>
838 1479 </div>
839 1480 <div class="tisdk-suggestions-content">
840 1481 <?php foreach ( $content as $key => $item ) : ?>
841 1482 <div class="tisdk-suggestion" id="<?php echo esc_attr( $key ); ?>">
@@ -849,11 +1490,13 @@
849 1490 <p><?php echo esc_html( $item['description'] ); ?></p>
850 1491 </div>
851 1492 <div class="tisdk-suggestion-cta">
852 1493 <a href="<?php echo esc_url( $item['link'] ); ?>" target="blank" class="button">
853 - <?php echo ( ( isset( $item['has_install'] ) && $item['has_install'] ) ? 'Install' : 'Learn More' ); ?>
1494 + <?php echo( ( isset( $item['has_install'] ) && $item['has_install'] ) ? esc_html( Loader::$labels['promotions']['woo']['cta_install'] ) : esc_html( Loader::$labels['promotions']['woo']['learn_more'] ) ); ?>
854 1495 </a>
855 - <a class="suggestion-dismiss" title="Dismiss this suggestion" href="#"></a>
1496 + <a class="suggestion-dismiss"
1497 + title="<?php echo esc_attr( Loader::$labels['promotions']['woo']['dismiss'] ); ?>"
1498 + href="#"></a>
856 1499 </div>
857 1500 </div>
858 1501 <?php endforeach; ?>
859 1502 </div>
@@ -872,25 +1515,29 @@
872 1515 .tisdk-suggestions_options a {
873 1516 display: flex !important;
874 1517 align-items: center;
875 1518 }
1519 +
876 1520 .tisdk-suggestions_options a::before {
877 1521 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;
878 1522 min-width: 13px;
879 1523 max-width: 13px;
880 - margin: auto;
881 1524 }
1525 +
882 1526 .tisdk-suggestions_options.active a::before {
883 1527 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;
884 1528 }
1529 +
885 1530 .tisdk-suggestions-header {
886 1531 padding: 1em 1.5em;
887 1532 border-bottom: 1px solid #eee;
888 1533 }
1534 +
889 1535 .tisdk-suggestions-header h4 {
890 1536 font-size: 1.1em;
891 1537 margin: 0;
892 1538 }
1539 +
893 1540 .tisdk-suggestion {
894 1541 display: flex;
895 1542 align-items: center;
896 1543 flex-direction: row;
@@ -895,8 +1542,9 @@
895 1542 align-items: center;
896 1543 flex-direction: row;
897 1544 padding: 1em 1.5em;
898 1545 }
1546 +
899 1547 .tisdk-suggestion-icon {
900 1548 height: 40px;
901 1549 margin: 0;
902 1550 margin-right: 0px;
@@ -908,17 +1556,21 @@
908 1556 border-radius: 100%;
909 1557 align-items: center;
910 1558 padding: 5px;
911 1559 }
1560 +
912 1561 .tisdk-suggestion-icon svg {
913 1562 fill: #fff;
914 1563 }
1564 +
915 1565 .tisdk-suggestion-content {
916 1566 flex: 1 1 60%;
917 1567 }
1568 +
918 1569 .tisdk-suggestion-content h4 {
919 1570 margin: 0;
920 1571 }
1572 +
921 1573 .tisdk-suggestion-content p {
922 1574 margin: 0;
923 1575 margin-top: 4px;
924 1576 padding: 0;
@@ -923,13 +1575,15 @@
923 1575 margin-top: 4px;
924 1576 padding: 0;
925 1577 line-height: 1.5;
926 1578 }
1579 +
927 1580 .tisdk-suggestion-cta {
928 1581 flex: 1 1 30%;
929 1582 min-width: 160px;
930 1583 text-align: right;
931 1584 }
1585 +
932 1586 .tisdk-suggestion-cta .button {
933 1587 display: inline-block;
934 1588 min-width: 120px;
935 1589 text-align: center;
@@ -934,8 +1588,9 @@
934 1588 min-width: 120px;
935 1589 text-align: center;
936 1590 margin: 0;
937 1591 }
1592 +
938 1593 .tisdk-suggestion-cta .suggestion-dismiss {
939 1594 position: relative;
940 1595 top: 5px;
941 1596 right: auto;
@@ -951,13 +1606,13 @@
951 1606 */
952 1607 public static function render_notice_dismiss_ajax() {
953 1608 ?>
954 1609 <script>
955 - jQuery(document).ready(function($) {
1610 + jQuery(document).ready(function ($) {
956 1611 // AJAX request to update the option value
957 - $( '.tisdk-suggestion .suggestion-dismiss' ).click(function(e) {
1612 + $('.tisdk-suggestion .suggestion-dismiss').click(function (e) {
958 1613 e.preventDefault();
959 - var suggestion = $(this).closest( '.tisdk-suggestion' );
1614 + var suggestion = $(this).closest('.tisdk-suggestion');
960 1615 var value = suggestion.attr('id');
961 1616
962 1617 var nonce = '<?php echo esc_attr( wp_create_nonce( 'tisdk_update_option' ) ); ?>';
963 1618
@@ -972,13 +1627,13 @@
972 1627 complete() {
973 1628 suggestion.remove();
974 1629
975 1630 // If element with .tisdk-suggestions-content has no children, hide the whole panel. Skip if the selector doesn't exist.
976 - if ( $( '.tisdk-suggestions-content' ).length && ! $( '.tisdk-suggestions-content' ).children().length ) {
977 - $( '.tisdk-suggestions_options' ).remove();
978 - $( '#tisdk_suggestions' ).remove();
979 - $( '.general_options' ).addClass( 'active' );
980 - $( '#general_product_data' ).css( 'display', 'block' );
1631 + if ($('.tisdk-suggestions-content').length && !$('.tisdk-suggestions-content').children().length) {
1632 + $('.tisdk-suggestions_options').remove();
1633 + $('#tisdk_suggestions').remove();
1634 + $('.general_options').addClass('active');
1635 + $('#general_product_data').css('display', 'block');
981 1636 }
982 1637 }
983 1638 });
984 1639 });
@@ -1024,6 +1679,402 @@
1024 1679 );
1025 1680
1026 1681 wp_send_json( $response );
1027 1682 wp_die();
1683 + }
1684 +
1685 + /**
1686 + * Check if the user has a support page.
1687 + */
1688 + public function has_support_page() {
1689 + $page_title_matches = $this->get_page_title_keyword_matches();
1690 +
1691 + return 'yes' === $page_title_matches['support'];
1692 + }
1693 +
1694 + /**
1695 + * Check if the user has a donate page.
1696 + */
1697 + public function has_donate_page() {
1698 + $page_title_matches = $this->get_page_title_keyword_matches();
1699 +
1700 + return 'yes' === $page_title_matches['donate'];
1701 + }
1702 +
1703 + /**
1704 + * Check if the tagline or a published page title contains LMS related keywords.
1705 + *
1706 + * @return bool True if LMS-related keywords are found, false otherwise.
1707 + */
1708 + public function has_lms_tagline() {
1709 + $tagline = strtolower( get_bloginfo( 'description' ) );
1710 +
1711 + foreach ( $this->get_lms_keywords() as $keyword ) {
1712 + if ( $this->has_lms_keyword_match( $tagline, $keyword ) ) {
1713 + return true;
1714 + }
1715 + }
1716 +
1717 + return $this->has_lms_page_title();
1718 + }
1719 +
1720 + /**
1721 + * Check if a supported LMS plugin is active.
1722 + *
1723 + * @return bool True if an LMS plugin is active, false otherwise.
1724 + */
1725 + private function has_active_lms_plugin() {
1726 + if ( defined( 'MASTERIYO_VERSION' ) ) {
1727 + return true;
1728 + }
1729 +
1730 + include_once ABSPATH . 'wp-admin/includes/plugin.php';
1731 +
1732 + $lms_plugins = array(
1733 + 'tutor/tutor.php',
1734 + 'sfwd-lms/sfwd_lms.php',
1735 + 'lifterlms/lifterlms.php',
1736 + 'sensei-lms/sensei-lms.php',
1737 + 'learnpress/learnpress.php',
1738 + 'masterstudy-lms-learning-management-system/masterstudy-lms-learning-management-system.php',
1739 + 'learning-management-system/lms.php',
1740 + );
1741 +
1742 + foreach ( $lms_plugins as $plugin ) {
1743 + if ( is_plugin_active( $plugin ) || ( is_multisite() && is_plugin_active_for_network( $plugin ) ) ) {
1744 + return true;
1745 + }
1746 + }
1747 +
1748 + return false;
1749 + }
1750 +
1751 + /**
1752 + * Check if the site shows signs of AI usage.
1753 + *
1754 + * @return bool True if an AI plugin is installed or a REST API application password is in use.
1755 + */
1756 + public function has_ai_usage_signal() {
1757 + return $this->has_ai_plugin_installed() || $this->has_application_password_in_use();
1758 + }
1759 +
1760 + /**
1761 + * Check if the current plugin-install request already surfaces the Easy MCP AI card,
1762 + * so the notice is not stacked on top of it: the Featured tab injects the card on
1763 + * WordPress 6.9+ and AI-related searches prepend it.
1764 + *
1765 + * @return bool True if the Easy MCP AI card is expected on the current request.
1766 + */
1767 + private function is_easy_mcp_card_request() {
1768 + $current_screen = get_current_screen();
1769 + if ( ! isset( $current_screen->id ) || 'plugin-install' !== $current_screen->id ) {
1770 + return false;
1771 + }
1772 +
1773 + // phpcs:disable WordPress.Security.NonceVerification.Recommended
1774 + $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : '';
1775 + $search = isset( $_GET['s'] ) ? strtolower( sanitize_text_field( wp_unslash( $_GET['s'] ) ) ) : '';
1776 + $paged = isset( $_GET['paged'] ) ? (int) $_GET['paged'] : 1;
1777 + // phpcs:enable WordPress.Security.NonceVerification.Recommended
1778 +
1779 + // Cards are only injected on the first results page.
1780 + if ( $paged > 1 ) {
1781 + return false;
1782 + }
1783 +
1784 + if ( '' === $tab || 'featured' === $tab ) {
1785 + return version_compare( get_bloginfo( 'version' ), '6.9', '>=' );
1786 + }
1787 +
1788 + if ( '' !== $search ) {
1789 + return Featured_Plugins::matches_ai_search_keywords( $search ) && ! Featured_Plugins::matches_lms_search_keywords( $search );
1790 + }
1791 +
1792 + return false;
1793 + }
1794 +
1795 + /**
1796 + * Check if a known AI plugin is installed, active or not.
1797 + *
1798 + * @return bool True if an AI plugin is installed, false otherwise.
1799 + */
1800 + private function has_ai_plugin_installed() {
1801 + return $this->any_plugin_dir_exists(
1802 + array(
1803 + 'ai-engine',
1804 + 'gpt3-ai-content-generator',
1805 + 'getgenie',
1806 + 'bertha-ai',
1807 + 'hyve',
1808 + 'hyve-lite',
1809 + )
1810 + );
1811 + }
1812 +
1813 + /**
1814 + * Check if any of the given plugin slugs has a directory in the plugins folder.
1815 + *
1816 + * @param array $slugs Plugin slugs.
1817 + *
1818 + * @return bool True if at least one plugin directory exists, false otherwise.
1819 + */
1820 + private function any_plugin_dir_exists( $slugs ) {
1821 + foreach ( $slugs as $slug ) {
1822 + if ( is_dir( WP_PLUGIN_DIR . '/' . $slug ) ) {
1823 + return true;
1824 + }
1825 + }
1826 +
1827 + return false;
1828 + }
1829 +
1830 + /**
1831 + * Check if a REST API application password is in use on the site.
1832 + *
1833 + * @return bool True if an application password is in use, false otherwise.
1834 + */
1835 + private function has_application_password_in_use() {
1836 + return class_exists( 'WP_Application_Passwords' ) && method_exists( 'WP_Application_Passwords', 'is_in_use' ) && \WP_Application_Passwords::is_in_use();
1837 + }
1838 +
1839 + /**
1840 + * Check if the current request is a profile screen where the Application
1841 + * Passwords section lives — a user wiring up an external REST client.
1842 + *
1843 + * @return bool True if a profile promo can be displayed, false otherwise.
1844 + */
1845 + private function can_check_profile_promo() {
1846 + if ( ! is_admin() || ! current_user_can( 'install_plugins' ) ) {
1847 + return false;
1848 + }
1849 +
1850 + $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
1851 + if ( isset( $current_screen->id ) ) {
1852 + return 'profile' === $current_screen->id;
1853 + }
1854 +
1855 + global $pagenow;
1856 +
1857 + return isset( $pagenow ) && 'profile.php' === $pagenow;
1858 + }
1859 +
1860 + /**
1861 + * Check if a competing MCP server plugin is installed, active or not.
1862 + *
1863 + * AI Engine ships an optional MCP feature but is intentionally kept in the
1864 + * AI-usage signals instead — its primary identity is an AI framework.
1865 + *
1866 + * @return bool True if an MCP server plugin is installed, false otherwise.
1867 + */
1868 + private function has_mcp_plugin_installed() {
1869 + return $this->any_plugin_dir_exists(
1870 + array(
1871 + 'vibe-ai',
1872 + 'royal-mcp',
1873 + 'stifli-flex-mcp',
1874 + 'miniorange-secure-mcp-server',
1875 + 'wsp-mcp-ai-agents-connector',
1876 + 'cowboy-mcp',
1877 + 'nibwp',
1878 + )
1879 + );
1880 + }
1881 +
1882 + /**
1883 + * Check if the current request can display a plugin install promo.
1884 + *
1885 + * @return bool True if a plugin install promo can be displayed, false otherwise.
1886 + */
1887 + private function can_check_plugin_install_promo() {
1888 + if ( ! is_admin() || ! current_user_can( 'install_plugins' ) ) {
1889 + return false;
1890 + }
1891 +
1892 + $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
1893 + if ( isset( $current_screen->id ) ) {
1894 + return $current_screen->id === 'plugin-install';
1895 + }
1896 +
1897 + global $pagenow;
1898 +
1899 + return isset( $pagenow ) && $pagenow === 'plugin-install.php';
1900 + }
1901 +
1902 + /**
1903 + * Check if a published page title contains LMS related keywords.
1904 + *
1905 + * @return bool True if an LMS-related page title is found, false otherwise.
1906 + */
1907 + private function has_lms_page_title() {
1908 + $lms_page_title_signal = get_transient( 'tisdk_lms_page_title_signal_v1' );
1909 +
1910 + if ( in_array( $lms_page_title_signal, array( 'yes', 'no' ), true ) ) {
1911 + return 'yes' === $lms_page_title_signal;
1912 + }
1913 +
1914 + $has_lms_page_title = $this->has_published_page_title_with_keywords( $this->get_lms_page_title_keywords() );
1915 +
1916 + set_transient( 'tisdk_lms_page_title_signal_v1', $has_lms_page_title ? 'yes' : 'no', 7 * DAY_IN_SECONDS );
1917 +
1918 + return $has_lms_page_title;
1919 + }
1920 +
1921 + /**
1922 + * Get cached keyword matches for published page titles.
1923 + *
1924 + * @return array Page title keyword matches.
1925 + */
1926 + private function get_page_title_keyword_matches() {
1927 + $default_matches = array(
1928 + 'support' => 'no',
1929 + 'donate' => 'no',
1930 + );
1931 +
1932 + $page_title_matches = get_transient( 'tisdk_page_title_signals_v1' );
1933 +
1934 + if ( is_array( $page_title_matches ) && empty( array_diff_key( $default_matches, $page_title_matches ) ) ) {
1935 + return array_merge( $default_matches, $page_title_matches );
1936 + }
1937 +
1938 + global $wpdb;
1939 +
1940 + $select_clauses = array();
1941 + $query_values = array();
1942 + $page_checks = $this->get_page_title_checks();
1943 +
1944 + foreach ( $page_checks as $match_key => $keywords ) {
1945 + $match_clauses = array();
1946 +
1947 + $query_values[] = 'page';
1948 + $query_values[] = 'publish';
1949 +
1950 + foreach ( $keywords as $keyword ) {
1951 + $match_clauses[] = 'post_title LIKE %s';
1952 + $query_values[] = '%' . $wpdb->esc_like( $keyword ) . '%';
1953 + }
1954 +
1955 + $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;
1956 + }
1957 +
1958 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1959 + $query = $wpdb->get_row( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1960 + $wpdb->prepare(
1961 + 'SELECT ' . implode( ', ', $select_clauses ),
1962 + $query_values
1963 + ),
1964 + ARRAY_A
1965 + );
1966 + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1967 +
1968 + $page_title_matches = $default_matches;
1969 +
1970 + if ( is_array( $query ) ) {
1971 + foreach ( array_keys( $page_checks ) as $match_key ) {
1972 + $page_title_matches[ $match_key ] = ! empty( $query[ 'has_' . $match_key ] ) ? 'yes' : 'no';
1973 + }
1974 + }
1975 +
1976 + set_transient( 'tisdk_page_title_signals_v1', $page_title_matches, 7 * DAY_IN_SECONDS );
1977 +
1978 + return $page_title_matches;
1979 + }
1980 +
1981 + /**
1982 + * Check if a keyword matches content.
1983 + *
1984 + * @param string $content Content to check.
1985 + * @param string $keyword Keyword to look for.
1986 + *
1987 + * @return bool True if the keyword matches, false otherwise.
1988 + */
1989 + private function has_lms_keyword_match( $content, $keyword ) {
1990 + if ( in_array( $keyword, array( 'lms', 'course', 'class' ), true ) ) {
1991 + return preg_match( '/(^|[^a-z0-9])' . preg_quote( $keyword, '/' ) . '([^a-z0-9]|$)/', $content ) === 1;
1992 + }
1993 +
1994 + return strpos( $content, $keyword ) !== false;
1995 + }
1996 +
1997 + /**
1998 + * Get LMS related keywords.
1999 + *
2000 + * @return array LMS related keywords.
2001 + */
2002 + private function get_lms_keywords() {
2003 + return array(
2004 + 'lms',
2005 + 'learning',
2006 + 'course',
2007 + 'courses',
2008 + 'academy',
2009 + 'training',
2010 + 'lesson',
2011 + 'lessons',
2012 + 'class',
2013 + 'classes',
2014 + 'student',
2015 + 'students',
2016 + 'teach',
2017 + 'teaching',
2018 + 'tutor',
2019 + 'quiz',
2020 + 'education',
2021 + 'online course',
2022 + 'online courses',
2023 + );
2024 + }
2025 +
2026 + /**
2027 + * Get LMS related keywords for page title matching.
2028 + *
2029 + * @return array LMS related keywords.
2030 + */
2031 + private function get_lms_page_title_keywords() {
2032 + return array_values( array_diff( $this->get_lms_keywords(), array( 'lms', 'course', 'class' ) ) );
2033 + }
2034 +
2035 + /**
2036 + * Check if a published page title contains any of the provided keywords.
2037 + *
2038 + * @param array $keywords Keywords to look for.
2039 + *
2040 + * @return bool True if a matching page title is found, false otherwise.
2041 + */
2042 + private function has_published_page_title_with_keywords( $keywords ) {
2043 + if ( empty( $keywords ) ) {
2044 + return false;
2045 + }
2046 +
2047 + global $wpdb;
2048 +
2049 + $query_values = array( 'page', 'publish' );
2050 + $clauses = array();
2051 +
2052 + foreach ( $keywords as $keyword ) {
2053 + $clauses[] = 'post_title LIKE %s';
2054 + $query_values[] = '%' . $wpdb->esc_like( $keyword ) . '%';
2055 + }
2056 +
2057 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
2058 + $page_title_match = $wpdb->get_var( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
2059 + $wpdb->prepare(
2060 + 'SELECT 1 FROM ' . $wpdb->posts . ' WHERE post_type = %s AND post_status = %s AND ( ' . implode( ' OR ', $clauses ) . ' ) LIMIT 1',
2061 + $query_values
2062 + )
2063 + );
2064 + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
2065 +
2066 + return ! empty( $page_title_match );
2067 + }
2068 +
2069 + /**
2070 + * Get page title keyword checks.
2071 + *
2072 + * @return array Page title keyword checks.
2073 + */
2074 + private function get_page_title_checks() {
2075 + return array(
2076 + 'support' => array( 'support' ),
2077 + 'donate' => array( 'donate' ),
2078 + );
1028 2079 }
1029 2080 }