PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.11.10
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.11.10
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 3.10.3 All 149 releases
← All changes | vendor/codeinwp/themeisle-sdk/src/Modules/Promotions.php +55 -528 4.0.83.11.10 View file →
@@ -105,15 +105,8 @@
105 105 */
106 106 private $option_feedzy = 'themeisle_sdk_promotions_feedzy_installed';
107 107
108 108 /**
109 - * Option key for Masteriyo promos.
110 - *
111 - * @var string
112 - */
113 - private $option_masteriyo = 'themeisle_sdk_promotions_masteriyo_installed';
114 -
115 - /**
116 109 * Loaded promotion.
117 110 *
118 111 * @var string
119 112 */
@@ -119,15 +112,8 @@
119 112 */
120 113 private $loaded_promo;
121 114
122 115 /**
123 - * Whether promotions were processed for the current screen.
124 - *
125 - * @var bool
126 - */
127 - private $available_promotions_loaded = false;
128 -
129 - /**
130 116 * Woo promotions.
131 117 *
132 118 * @var array
133 119 */
@@ -165,9 +151,8 @@
165 151 $promotions_to_load[] = 'redirection-cf7';
166 152 $promotions_to_load[] = 'hyve';
167 153 $promotions_to_load[] = 'wp_full_pay';
168 154 $promotions_to_load[] = 'feedzy_import';
169 - $promotions_to_load[] = 'learning-management-system';
170 155
171 156 if ( defined( 'NEVE_VERSION' ) || defined( 'WPMM_PATH' ) || defined( 'OTTER_BLOCKS_VERSION' ) || defined( 'OBFX_URL' ) ) {
172 157 $promotions_to_load[] = 'feedzy_embed';
173 158 }
@@ -208,225 +193,21 @@
208 193
209 194 add_filter( 'attachment_fields_to_edit', array( $this, 'add_attachment_field' ), 10, 2 );
210 195 add_action( 'current_screen', [ $this, 'load_available' ] );
211 196 add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'enqueue' ) );
212 - add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_visualizer_block_editor_shim' ), 100 );
213 197 add_action( 'wp_ajax_tisdk_update_option', array( $this, 'dismiss_promotion' ) );
214 - add_filter( 'plugins_api_result', array( $this, 'inject_visualizer_block_directory_suggestion' ), 10, 3 );
215 - add_filter( 'option_visualizer-activated', array( $this, 'suppress_visualizer_onboarding_in_editor' ) );
216 198 add_filter( 'themeisle_sdk_ran_promos', '__return_true' );
217 199
218 200 if ( get_option( $this->option_neve, false ) !== true ) {
219 - add_action( 'wp_ajax_themeisle_sdk_dismiss_notice', 'ThemeisleSDK\Modules\Notification::dismiss' );
201 + add_action( 'wp_ajax_themeisle_sdk_dismiss_notice', 'ThemeisleSDK\Modules\Notification::regular_dismiss' );
220 202 }
221 203 }
222 204
223 -
224 205 /**
225 - * Inject Visualizer as the first block-directory suggestion for chart queries.
226 - *
227 - * @param object|WP_Error $res Response object or WP_Error.
228 - * @param string $action The API action.
229 - * @param object $args The API arguments.
230 - * @return object|WP_Error
231 - */
232 - public function inject_visualizer_block_directory_suggestion( $res, $action, $args ) {
233 - if ( 'query_plugins' !== $action || ! is_object( $args ) || empty( $args->block ) ) {
234 - return $res;
235 - }
236 -
237 - if ( ! $this->should_suggest_visualizer( $args->block ) ) {
238 - return $res;
239 - }
240 -
241 - if ( $this->is_plugin_installed( 'visualizer' ) ) {
242 - return $res;
243 - }
244 -
245 - $plugin = $this->get_visualizer_block_directory_data();
246 - if ( empty( $plugin ) ) {
247 - return $res;
248 - }
249 -
250 - if ( is_wp_error( $res ) ) {
251 - $res = (object) array( 'plugins' => array() );
252 - }
253 -
254 - if ( ! isset( $res->plugins ) || ! is_array( $res->plugins ) ) {
255 - $res->plugins = array();
256 - }
257 -
258 - $res->plugins = array_values(
259 - array_filter(
260 - $res->plugins,
261 - function ( $existing ) use ( $plugin ) {
262 - return ! isset( $existing['slug'] ) || $existing['slug'] !== $plugin['slug'];
263 - }
264 - )
265 - );
266 -
267 - array_unshift( $res->plugins, $plugin );
268 -
269 - return $res;
270 - }
271 -
272 - /**
273 - * Check if the query should trigger the Visualizer suggestion.
274 - *
275 - * @param string $term Search term.
276 - * @return bool
277 - */
278 - private function should_suggest_visualizer( $term ) {
279 - $term = strtolower( (string) $term );
280 - return false !== strpos( $term, 'chart' ) || false !== strpos( $term, 'visualizer' ) || false !== strpos( $term, 'visualization' ) || false !== strpos( $term, 'graph' );
281 - }
282 -
283 - /**
284 - * Build the plugin data for Visualizer block directory results.
285 - *
286 - * @return array
287 - */
288 - private function get_visualizer_block_directory_data() {
289 - $slug = 'visualizer';
290 - $plugin_info = $this->call_plugin_api( $slug );
291 -
292 - if ( is_wp_error( $plugin_info ) || empty( $plugin_info ) ) {
293 - return array();
294 - }
295 -
296 - $icons = array();
297 - if ( ! empty( $plugin_info->icons ) ) {
298 - if ( ! empty( $plugin_info->icons['1x'] ) ) {
299 - $icons['1x'] = $plugin_info->icons['1x'];
300 - }
301 - if ( ! empty( $plugin_info->icons['2x'] ) ) {
302 - $icons['2x'] = $plugin_info->icons['2x'];
303 - }
304 - }
305 -
306 - $name = isset( $plugin_info->name ) ? $plugin_info->name : 'Visualizer';
307 -
308 - return array(
309 - 'slug' => $slug,
310 - 'name' => $name,
311 - 'short_description' => isset( $plugin_info->short_description ) ? $plugin_info->short_description : '',
312 - 'author' => isset( $plugin_info->author ) ? wp_strip_all_tags( $plugin_info->author ) : '',
313 - 'rating' => isset( $plugin_info->rating ) ? (int) $plugin_info->rating : 0,
314 - 'num_ratings' => isset( $plugin_info->num_ratings ) ? (int) $plugin_info->num_ratings : 0,
315 - 'active_installs' => isset( $plugin_info->active_installs ) ? (int) $plugin_info->active_installs : 0,
316 - 'author_block_rating' => isset( $plugin_info->author_block_rating ) ? (int) $plugin_info->author_block_rating : 0,
317 - 'author_block_count' => isset( $plugin_info->author_block_count ) ? (int) $plugin_info->author_block_count : 0,
318 - 'icons' => $icons,
319 - 'last_updated' => isset( $plugin_info->last_updated ) ? $plugin_info->last_updated : gmdate( 'Y-m-d H:i:s' ),
320 - 'blocks' => array(
321 - array(
322 - 'name' => 'visualizer/chart',
323 - 'title' => $name,
324 - ),
325 - ),
326 - );
327 - }
328 -
329 -
330 - /**
331 - * Prevent Visualizer onboarding redirects while in the block editor.
332 - *
333 - * @param mixed $value Option value.
334 - * @return mixed
335 - */
336 - public function suppress_visualizer_onboarding_in_editor( $value ) {
337 - if ( ! $this->is_block_editor_screen() ) {
338 - return $value;
339 - }
340 -
341 - return false;
342 - }
343 -
344 - /**
345 - * Add a small compatibility shim for Visualizer's block editor bundle.
346 - *
347 - * Visualizer's "Display an existing chart" flow reads
348 - * `google.visualization.Version` before the Google Charts loader has fully
349 - * populated `google.visualization`, which can throw after dynamic install.
350 - *
351 - * @return void
352 - */
353 - public function enqueue_visualizer_block_editor_shim() {
354 - global $themeisle_sdk_max_version;
355 -
356 - if ( ! $this->is_block_editor_screen() ) {
357 - return;
358 - }
359 -
360 - if ( ! wp_script_is( 'visualizer-gutenberg-block', 'enqueued' ) ) {
361 - return;
362 - }
363 -
364 - wp_register_script(
365 - 'ti-sdk-visualizer-editor-shim',
366 - '',
367 - array(),
368 - $themeisle_sdk_max_version,
369 - true
370 - );
371 - wp_enqueue_script( 'ti-sdk-visualizer-editor-shim' );
372 - wp_add_inline_script(
373 - 'ti-sdk-visualizer-editor-shim',
374 - 'window.google = window.google || {}; window.google.visualization = window.google.visualization || {}; window.google.visualization.Version = window.google.visualization.Version || "current";'
375 - );
376 - }
377 -
378 - /**
379 - * Check if the current admin screen is the block editor.
380 - *
381 - * @return bool
382 - */
383 - private function is_block_editor_screen() {
384 - if ( ! function_exists( 'get_current_screen' ) ) {
385 - return $this->is_block_editor_request();
386 - }
387 -
388 - $screen = get_current_screen();
389 - if ( is_object( $screen ) && method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() ) {
390 - return true;
391 - }
392 -
393 - return $this->is_block_editor_request();
394 - }
395 -
396 - /**
397 - * Detect block editor requests before the current screen is available.
398 - *
399 - * @return bool
400 - */
401 - private function is_block_editor_request() {
402 - global $pagenow;
403 -
404 - if ( 'post-new.php' === $pagenow ) {
405 - $post_type = isset( $_GET['post_type'] ) ? sanitize_key( $_GET['post_type'] ) : 'post';
406 -
407 - return function_exists( 'use_block_editor_for_post_type' ) ? use_block_editor_for_post_type( $post_type ) : true;
408 - }
409 -
410 - if ( 'post.php' === $pagenow && isset( $_GET['post'] ) ) {
411 - $post_id = absint( $_GET['post'] );
412 -
413 - return function_exists( 'use_block_editor_for_post' ) ? use_block_editor_for_post( $post_id ) : true;
414 - }
415 -
416 - return false;
417 - }
418 -
419 - /**
420 206 * Load available promotions.
421 207 */
422 208 public function load_available() {
423 - if ( $this->available_promotions_loaded ) {
424 - return;
425 - }
426 -
427 - $this->available_promotions_loaded = true;
428 - $this->promotions = $this->filter_by_screen_and_merge();
209 + $this->promotions = $this->filter_by_screen_and_merge();
429 210 if ( empty( $this->promotions ) ) {
430 211 return;
431 212 }
432 213
@@ -475,9 +256,8 @@
475 256
476 257 if ( isset( $_GET['wp_full_pay_reference_key'] ) ) {
477 258 update_option( 'wp_full_pay_reference_key', sanitize_key( $_GET['wp_full_pay_reference_key'] ) );
478 259 }
479 -
480 260 if ( isset( $_GET['feedzy_reference_key'] ) || ( isset( $_GET['from'], $_GET['plugin'] ) && $_GET['from'] === 'import' && str_starts_with( sanitize_key( $_GET['plugin'] ), 'feedzy' ) ) ) {
481 261 update_option( 'feedzy_reference_key', sanitize_key( $_GET['feedzy_reference_key'] ?? 'i-' . $this->product->get_key() ) );
482 262 update_option( $this->option_feedzy, 1 );
483 263 }
@@ -569,18 +349,8 @@
569 349 'show_in_rest' => true,
570 350 'default' => false,
571 351 )
572 352 );
573 - register_setting(
574 - 'themeisle_sdk_settings',
575 - $this->option_masteriyo,
576 - array(
577 - 'type' => 'boolean',
578 - 'sanitize_callback' => 'rest_sanitize_boolean',
579 - 'show_in_rest' => true,
580 - 'default' => false,
581 - )
582 - );
583 353 }
584 354
585 355 /**
586 356 * Check if the path is writable.
@@ -632,17 +402,14 @@
632 402 $has_sparks = defined( 'SPARKS_WC_VERSION' ) || $this->is_plugin_installed( 'sparks-for-woocommerce' );
633 403 $has_ppom = defined( 'PPOM_VERSION' ) || $this->is_plugin_installed( 'woocommerce-product-addon' );
634 404 $has_redirection_cf7 = defined( 'WPCF7_PRO_REDIRECT_PLUGIN_VERSION' ) || $this->is_plugin_installed( 'wpcf7-redirect' );
635 405 $had_redirection_cf7_promo = get_option( $this->option_redirection_cf7, false );
636 - $is_min_php_7_4 = version_compare( PHP_VERSION, '7.4', '>=' );
637 - $is_min_php_7_2 = version_compare( PHP_VERSION, '7.2', '>=' );
638 - $can_check_plugin_install = $this->can_check_plugin_install_promo();
639 406 $has_hyve = defined( 'HYVE_LITE_VERSION' ) || $this->is_plugin_installed( 'hyve' ) || $this->is_plugin_installed( 'hyve-lite' );
640 407 $had_hyve_from_promo = get_option( $this->option_hyve, false );
641 - $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();
408 + $has_hyve_conditions = version_compare( get_bloginfo( 'version' ), '6.2', '>=' ) && $this->has_support_page();
642 409 $has_wfp_full_pay = defined( 'WP_FULL_STRIPE_BASENAME' ) || $this->is_plugin_installed( 'wp-full-stripe-free' );
643 410 $had_wfp_from_promo = get_option( $this->option_wp_full_pay, false );
644 - $has_wfp_conditions = ! $has_wfp_full_pay && ! $had_wfp_from_promo && $can_check_plugin_install && $this->has_donate_page();
411 + $has_wfp_conditions = $this->has_donate_page();
645 412 $is_min_req_v = version_compare( get_bloginfo( 'version' ), '5.8', '>=' );
646 413 $current_theme = wp_get_theme();
647 414 $has_neve = $current_theme->template === 'neve' || $current_theme->parent() === 'neve';
648 415 $has_neve_from_promo = get_option( $this->option_neve, false );
@@ -647,15 +414,15 @@
647 414 $has_neve = $current_theme->template === 'neve' || $current_theme->parent() === 'neve';
648 415 $has_neve_from_promo = get_option( $this->option_neve, false );
649 416 $has_enough_attachments = $this->has_min_media_attachments();
650 417 $has_enough_old_posts = $this->has_old_posts();
651 - $has_feedzy = defined( 'FEEDZY_BASEFILE' ) || $this->is_plugin_installed( 'feedzy-rss-feedss' );
652 - $had_feedzy_from_promo = get_option( $this->option_feedzy, false );
653 - $had_masteriyo_from_promo = get_option( $this->option_masteriyo, false );
654 - $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();
418 + $is_min_php_8_1 = version_compare( PHP_VERSION, '8.1', '>=' );
655 419
420 + $has_feedzy = defined( 'FEEDZY_BASEFILE' ) || $this->is_plugin_installed( 'feedzy-rss-feedss' );
421 + $had_feedzy_from_promo = get_option( $this->option_feedzy, false );
422 +
656 423 $all = [
657 - 'optimole' => [
424 + 'optimole' => [
658 425 'om-editor' => [
659 426 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
660 427 'screen' => 'editor',
661 428 'delayed' => true,
@@ -678,9 +445,9 @@
678 445 'screen' => 'elementor',
679 446 'delayed' => true,
680 447 ],
681 448 ],
682 - 'feedzy_import' => [
449 + 'feedzy_import' => [
683 450 'feedzy-import' => [
684 451 'env' => true,
685 452 'screen' => 'import',
686 453 'always' => true,
@@ -685,15 +452,15 @@
685 452 'screen' => 'import',
686 453 'always' => true,
687 454 ],
688 455 ],
689 - 'feedzy_embed' => [
456 + 'feedzy_embed' => [
690 457 'feedzy-editor' => [
691 458 'env' => ! $has_feedzy && is_main_site() && ! $had_feedzy_from_promo,
692 459 'screen' => 'editor',
693 460 ],
694 461 ],
695 - 'otter' => [
462 + 'otter' => [
696 463 'blocks-css' => [
697 464 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
698 465 'screen' => 'editor',
699 466 'delayed' => true,
@@ -708,9 +475,9 @@
708 475 'screen' => 'editor',
709 476 'delayed' => true,
710 477 ],
711 478 ],
712 - 'rop' => [
479 + 'rop' => [
713 480 'rop-posts' => [
714 481 'env' => ! $has_rop && ! $had_rop_from_promo && $has_enough_old_posts,
715 482 'screen' => 'edit-post',
716 483 'delayed' => true,
@@ -715,9 +482,9 @@
715 482 'screen' => 'edit-post',
716 483 'delayed' => true,
717 484 ],
718 485 ],
719 - 'woo_plugins' => [
486 + 'woo_plugins' => [
720 487 'ppom' => [
721 488 'env' => ! $has_ppom && $has_woocommerce,
722 489 'screen' => 'edit-product',
723 490 ],
@@ -733,15 +500,15 @@
733 500 'env' => ! $has_sparks && $has_woocommerce,
734 501 'screen' => 'edit-product',
735 502 ],
736 503 ],
737 - 'neve' => [
504 + 'neve' => [
738 505 'neve-themes-popular' => [
739 506 'env' => ! $has_neve && ! $has_neve_from_promo,
740 507 'screen' => 'themes-install-popular',
741 508 ],
742 509 ],
743 - 'redirection-cf7' => [
510 + 'redirection-cf7' => [
744 511 'wpcf7' => [
745 512 'env' => ! $has_redirection_cf7 && ! $had_redirection_cf7_promo,
746 513 'screen' => 'wpcf7',
747 514 'delayed' => true,
@@ -746,26 +513,20 @@
746 513 'screen' => 'wpcf7',
747 514 'delayed' => true,
748 515 ],
749 516 ],
750 - 'hyve' => [
517 + 'hyve' => [
751 518 'hyve-plugins-install' => [
752 - 'env' => $has_hyve_conditions,
519 + 'env' => $is_min_php_8_1 && ! $has_hyve && ! $had_hyve_from_promo && $has_hyve_conditions,
753 520 'screen' => 'plugin-install',
754 521 ],
755 522 ],
756 - 'wp_full_pay' => [
523 + 'wp_full_pay' => [
757 524 'wp-full-pay-plugins-install' => [
758 - 'env' => $has_wfp_conditions,
525 + 'env' => ! $has_wfp_full_pay && ! $had_wfp_from_promo && $has_wfp_conditions,
759 526 'screen' => 'plugin-install',
760 527 ],
761 528 ],
762 - 'learning-management-system' => [
763 - 'masteriyo-plugins-install' => [
764 - 'env' => $has_masteriyo_conditions,
765 - 'screen' => 'plugin-install',
766 - ],
767 - ],
768 529 ];
769 530
770 531 foreach ( $all as $slug => $data ) {
771 532 foreach ( $data as $key => $conditions ) {
@@ -840,19 +601,10 @@
840 601 $product_install_time = (int) $this->product->get_install_time();
841 602 $is_older = time() > ( $product_install_time + ( 3 * DAY_IN_SECONDS ) );
842 603 $is_newer = time() < ( $product_install_time + ( 6 * HOUR_IN_SECONDS ) );
843 604 foreach ( $this->promotions as $slug => $promos ) {
844 - if ( ! is_array( $promos ) ) {
845 - continue;
846 - }
847 -
848 605 foreach ( $promos as $key => $data ) {
849 - if ( ! is_array( $data ) || ! array_key_exists( 'screen', $data ) ) {
850 - unset( $this->promotions[ $slug ][ $key ] );
851 606
852 - continue;
853 - }
854 -
855 607 $data = wp_parse_args(
856 608 $data,
857 609 [
858 610 'delayed' => false,
@@ -970,12 +722,8 @@
970 722 if ( $this->get_upsells_dismiss_time( 'wp-full-pay-plugins-install' ) === false ) {
971 723 add_action( 'admin_notices', [ $this, 'render_wp_full_pay_notice' ] );
972 724 }
973 725
974 - if ( $this->get_upsells_dismiss_time( 'masteriyo-plugins-install' ) === false ) {
975 - add_action( 'admin_notices', [ $this, 'render_masteriyo_notice' ] );
976 - }
977 -
978 726 add_action( 'load-import.php', [ $this, 'add_import' ] );
979 727 $this->load_woo_promos();
980 728
981 729 return;
@@ -1039,12 +787,8 @@
1039 787 case 'wp-full-pay-plugins-install':
1040 788 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
1041 789 add_action( 'admin_notices', [ $this, 'render_wp_full_pay_notice' ] );
1042 790 break;
1043 - case 'masteriyo-plugins-install':
1044 - add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
1045 - add_action( 'admin_notices', [ $this, 'render_masteriyo_notice' ] );
1046 - break;
1047 791 }
1048 792 }
1049 793
1050 794 /**
@@ -1136,10 +880,8 @@
1136 880 'hyveActivationUrl' => $this->get_plugin_activation_link( 'hyve-lite' ),
1137 881 'hyveDash' => esc_url( add_query_arg( [ 'page' => 'wpfs-settings-stripe' ], admin_url( 'admin.php' ) ) ),
1138 882 'wpFullPayActivationUrl' => $this->get_plugin_activation_link( 'wp-full-stripe-free' ),
1139 883 'wpFullPayDash' => esc_url( add_query_arg( [ 'page' => 'wpfs-settings-stripe' ], admin_url( 'admin.php' ) ) ),
1140 - 'masteriyoActivationUrl' => $this->get_plugin_activation_link( 'masteriyo' ),
1141 - 'masteriyoDash' => esc_url( add_query_arg( [ 'page' => 'masteriyo-onboard' ], admin_url( 'index.php' ) ) ),
1142 884 'nevePreviewURL' => esc_url( add_query_arg( [ 'theme' => 'neve' ], admin_url( 'theme-install.php' ) ) ),
1143 885 'neveAction' => $neve_action,
1144 886 'activateNeveURL' => esc_url(
1145 887 add_query_arg(
@@ -1198,15 +940,8 @@
1198 940 echo '<div id="ti-redirection-cf7-notice" class="notice notice-info ti-sdk-om-notice"></div>';
1199 941 }
1200 942
1201 943 /**
1202 - * Render Masteriyo notice.
1203 - */
1204 - public function render_masteriyo_notice() {
1205 - echo '<div id="ti-masteriyo-notice" class="notice notice-info ti-sdk-om-notice"></div>';
1206 - }
1207 -
1208 - /**
1209 944 * Add promo to attachment modal.
1210 945 *
1211 946 * @param array $fields Fields array.
1212 947 * @param \WP_Post $post Post object.
@@ -1618,265 +1353,57 @@
1618 1353 /**
1619 1354 * Check if the user has a support page.
1620 1355 */
1621 1356 public function has_support_page() {
1622 - $page_title_matches = $this->get_page_title_keyword_matches();
1357 + $transient_name = 'tisdk_has_support_page';
1358 + $has_support = get_transient( $transient_name );
1623 1359
1624 - return 'yes' === $page_title_matches['support'];
1625 - }
1360 + if ( false === $has_support ) {
1361 + global $wpdb;
1626 1362
1627 - /**
1628 - * Check if the user has a donate page.
1629 - */
1630 - public function has_donate_page() {
1631 - $page_title_matches = $this->get_page_title_keyword_matches();
1363 + // We use %i escape identifier that was added in WP 6.2.0, hence need to ignore PHPCS warning.
1364 + // We only show this notice to users on higher version as that is the minimum for Hyve as well.
1365 + $query = $wpdb->get_var( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1366 + $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
1367 + 'SELECT ID FROM %i WHERE post_type = %s AND post_status = %s AND post_title LIKE %s LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnsupportedPlaceholder
1368 + $wpdb->posts,
1369 + 'page',
1370 + 'publish',
1371 + '%support%'
1372 + )
1373 + );
1632 1374
1633 - return 'yes' === $page_title_matches['donate'];
1634 - }
1375 + $has_support = $query ? 'yes' : 'no';
1635 1376
1636 - /**
1637 - * Check if the tagline or a published page title contains LMS related keywords.
1638 - *
1639 - * @return bool True if LMS-related keywords are found, false otherwise.
1640 - */
1641 - public function has_lms_tagline() {
1642 - $tagline = strtolower( get_bloginfo( 'description' ) );
1643 -
1644 - foreach ( $this->get_lms_keywords() as $keyword ) {
1645 - if ( $this->has_lms_keyword_match( $tagline, $keyword ) ) {
1646 - return true;
1647 - }
1377 + set_transient( $transient_name, $has_support, 7 * DAY_IN_SECONDS );
1648 1378 }
1649 1379
1650 - return $this->has_lms_page_title();
1380 + return 'yes' === $has_support;
1651 1381 }
1652 1382
1653 1383 /**
1654 - * Check if a supported LMS plugin is active.
1655 - *
1656 - * @return bool True if an LMS plugin is active, false otherwise.
1384 + * Check if the user has a donate page.
1657 1385 */
1658 - private function has_active_lms_plugin() {
1659 - if ( defined( 'MASTERIYO_VERSION' ) ) {
1660 - return true;
1661 - }
1386 + public function has_donate_page() {
1387 + $transient_name = 'tisdk_has_donate_page';
1388 + $has_donate = get_transient( $transient_name );
1662 1389
1663 - include_once ABSPATH . 'wp-admin/includes/plugin.php';
1390 + if ( false === $has_donate ) {
1391 + global $wpdb;
1664 1392
1665 - $lms_plugins = array(
1666 - 'tutor/tutor.php',
1667 - 'sfwd-lms/sfwd_lms.php',
1668 - 'lifterlms/lifterlms.php',
1669 - 'sensei-lms/sensei-lms.php',
1670 - 'learnpress/learnpress.php',
1671 - 'masterstudy-lms-learning-management-system/masterstudy-lms-learning-management-system.php',
1672 - 'learning-management-system/lms.php',
1673 - );
1393 + $query = $wpdb->get_var( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1394 + $wpdb->prepare(
1395 + 'SELECT ID FROM ' . $wpdb->posts . ' WHERE post_type = %s AND post_status = %s AND post_title LIKE %s LIMIT 1',
1396 + 'page',
1397 + 'publish',
1398 + '%donate%'
1399 + )
1400 + );
1674 1401
1675 - foreach ( $lms_plugins as $plugin ) {
1676 - if ( is_plugin_active( $plugin ) || ( is_multisite() && is_plugin_active_for_network( $plugin ) ) ) {
1677 - return true;
1678 - }
1679 - }
1402 + $has_donate = $query ? 'yes' : 'no';
1680 1403
1681 - return false;
1682 - }
1683 -
1684 - /**
1685 - * Check if the current request can display a plugin install promo.
1686 - *
1687 - * @return bool True if a plugin install promo can be displayed, false otherwise.
1688 - */
1689 - private function can_check_plugin_install_promo() {
1690 - if ( ! is_admin() || ! current_user_can( 'install_plugins' ) ) {
1691 - return false;
1404 + set_transient( $transient_name, $has_donate, 7 * DAY_IN_SECONDS );
1692 1405 }
1693 1406
1694 - $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
1695 - if ( isset( $current_screen->id ) ) {
1696 - return $current_screen->id === 'plugin-install';
1697 - }
1698 -
1699 - global $pagenow;
1700 -
1701 - return isset( $pagenow ) && $pagenow === 'plugin-install.php';
1702 - }
1703 -
1704 - /**
1705 - * Check if a published page title contains LMS related keywords.
1706 - *
1707 - * @return bool True if an LMS-related page title is found, false otherwise.
1708 - */
1709 - private function has_lms_page_title() {
1710 - $lms_page_title_signal = get_transient( 'tisdk_lms_page_title_signal_v1' );
1711 -
1712 - if ( in_array( $lms_page_title_signal, array( 'yes', 'no' ), true ) ) {
1713 - return 'yes' === $lms_page_title_signal;
1714 - }
1715 -
1716 - $has_lms_page_title = $this->has_published_page_title_with_keywords( $this->get_lms_page_title_keywords() );
1717 -
1718 - set_transient( 'tisdk_lms_page_title_signal_v1', $has_lms_page_title ? 'yes' : 'no', 7 * DAY_IN_SECONDS );
1719 -
1720 - return $has_lms_page_title;
1721 - }
1722 -
1723 - /**
1724 - * Get cached keyword matches for published page titles.
1725 - *
1726 - * @return array Page title keyword matches.
1727 - */
1728 - private function get_page_title_keyword_matches() {
1729 - $default_matches = array(
1730 - 'support' => 'no',
1731 - 'donate' => 'no',
1732 - );
1733 -
1734 - $page_title_matches = get_transient( 'tisdk_page_title_signals_v1' );
1735 -
1736 - if ( is_array( $page_title_matches ) && empty( array_diff_key( $default_matches, $page_title_matches ) ) ) {
1737 - return array_merge( $default_matches, $page_title_matches );
1738 - }
1739 -
1740 - global $wpdb;
1741 -
1742 - $select_clauses = array();
1743 - $query_values = array();
1744 - $page_checks = $this->get_page_title_checks();
1745 -
1746 - foreach ( $page_checks as $match_key => $keywords ) {
1747 - $match_clauses = array();
1748 -
1749 - $query_values[] = 'page';
1750 - $query_values[] = 'publish';
1751 -
1752 - foreach ( $keywords as $keyword ) {
1753 - $match_clauses[] = 'post_title LIKE %s';
1754 - $query_values[] = '%' . $wpdb->esc_like( $keyword ) . '%';
1755 - }
1756 -
1757 - $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;
1758 - }
1759 -
1760 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1761 - $query = $wpdb->get_row( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1762 - $wpdb->prepare(
1763 - 'SELECT ' . implode( ', ', $select_clauses ),
1764 - $query_values
1765 - ),
1766 - ARRAY_A
1767 - );
1768 - // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1769 -
1770 - $page_title_matches = $default_matches;
1771 -
1772 - if ( is_array( $query ) ) {
1773 - foreach ( array_keys( $page_checks ) as $match_key ) {
1774 - $page_title_matches[ $match_key ] = ! empty( $query[ 'has_' . $match_key ] ) ? 'yes' : 'no';
1775 - }
1776 - }
1777 -
1778 - set_transient( 'tisdk_page_title_signals_v1', $page_title_matches, 7 * DAY_IN_SECONDS );
1779 -
1780 - return $page_title_matches;
1781 - }
1782 -
1783 - /**
1784 - * Check if a keyword matches content.
1785 - *
1786 - * @param string $content Content to check.
1787 - * @param string $keyword Keyword to look for.
1788 - *
1789 - * @return bool True if the keyword matches, false otherwise.
1790 - */
1791 - private function has_lms_keyword_match( $content, $keyword ) {
1792 - if ( in_array( $keyword, array( 'lms', 'course', 'class' ), true ) ) {
1793 - return preg_match( '/(^|[^a-z0-9])' . preg_quote( $keyword, '/' ) . '([^a-z0-9]|$)/', $content ) === 1;
1794 - }
1795 -
1796 - return strpos( $content, $keyword ) !== false;
1797 - }
1798 -
1799 - /**
1800 - * Get LMS related keywords.
1801 - *
1802 - * @return array LMS related keywords.
1803 - */
1804 - private function get_lms_keywords() {
1805 - return array(
1806 - 'lms',
1807 - 'learning',
1808 - 'course',
1809 - 'courses',
1810 - 'academy',
1811 - 'training',
1812 - 'lesson',
1813 - 'lessons',
1814 - 'class',
1815 - 'classes',
1816 - 'student',
1817 - 'students',
1818 - 'teach',
1819 - 'teaching',
1820 - 'tutor',
1821 - 'quiz',
1822 - 'education',
1823 - 'online course',
1824 - 'online courses',
1825 - );
1826 - }
1827 -
1828 - /**
1829 - * Get LMS related keywords for page title matching.
1830 - *
1831 - * @return array LMS related keywords.
1832 - */
1833 - private function get_lms_page_title_keywords() {
1834 - return array_values( array_diff( $this->get_lms_keywords(), array( 'lms', 'course', 'class' ) ) );
1835 - }
1836 -
1837 - /**
1838 - * Check if a published page title contains any of the provided keywords.
1839 - *
1840 - * @param array $keywords Keywords to look for.
1841 - *
1842 - * @return bool True if a matching page title is found, false otherwise.
1843 - */
1844 - private function has_published_page_title_with_keywords( $keywords ) {
1845 - if ( empty( $keywords ) ) {
1846 - return false;
1847 - }
1848 -
1849 - global $wpdb;
1850 -
1851 - $query_values = array( 'page', 'publish' );
1852 - $clauses = array();
1853 -
1854 - foreach ( $keywords as $keyword ) {
1855 - $clauses[] = 'post_title LIKE %s';
1856 - $query_values[] = '%' . $wpdb->esc_like( $keyword ) . '%';
1857 - }
1858 -
1859 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1860 - $page_title_match = $wpdb->get_var( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1861 - $wpdb->prepare(
1862 - 'SELECT 1 FROM ' . $wpdb->posts . ' WHERE post_type = %s AND post_status = %s AND ( ' . implode( ' OR ', $clauses ) . ' ) LIMIT 1',
1863 - $query_values
1864 - )
1865 - );
1866 - // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1867 -
1868 - return ! empty( $page_title_match );
1869 - }
1870 -
1871 - /**
1872 - * Get page title keyword checks.
1873 - *
1874 - * @return array Page title keyword checks.
1875 - */
1876 - private function get_page_title_checks() {
1877 - return array(
1878 - 'support' => array( 'support' ),
1879 - 'donate' => array( 'donate' ),
1880 - );
1407 + return 'yes' === $has_donate;
1881 1408 }
1882 1409 }