PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.9
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.9
4.0.9 4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 All 150 releases
← All changes | vendor/codeinwp/themeisle-sdk/src/Modules/Promotions.php +224 -1 4.0.5 → 4.0.9 View file →
@@ -112,8 +112,15 @@
112 112 */
113 113 private $option_masteriyo = 'themeisle_sdk_promotions_masteriyo_installed';
114 114
115 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 + /**
116 123 * Loaded promotion.
117 124 *
118 125 * @var string
119 126 */
@@ -119,8 +126,15 @@
119 126 */
120 127 private $loaded_promo;
121 128
122 129 /**
130 + * Whether promotions were processed for the current screen.
131 + *
132 + * @var bool
133 + */
134 + private $available_promotions_loaded = false;
135 +
136 + /**
123 137 * Woo promotions.
124 138 *
125 139 * @var array
126 140 */
@@ -159,8 +173,9 @@
159 173 $promotions_to_load[] = 'hyve';
160 174 $promotions_to_load[] = 'wp_full_pay';
161 175 $promotions_to_load[] = 'feedzy_import';
162 176 $promotions_to_load[] = 'learning-management-system';
177 + $promotions_to_load[] = 'easy-mcp';
163 178
164 179 if ( defined( 'NEVE_VERSION' ) || defined( 'WPMM_PATH' ) || defined( 'OTTER_BLOCKS_VERSION' ) || defined( 'OBFX_URL' ) ) {
165 180 $promotions_to_load[] = 'feedzy_embed';
166 181 }
@@ -412,9 +427,14 @@
412 427 /**
413 428 * Load available promotions.
414 429 */
415 430 public function load_available() {
416 - $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();
417 437 if ( empty( $this->promotions ) ) {
418 438 return;
419 439 }
420 440
@@ -464,8 +484,12 @@
464 484 if ( isset( $_GET['wp_full_pay_reference_key'] ) ) {
465 485 update_option( 'wp_full_pay_reference_key', sanitize_key( $_GET['wp_full_pay_reference_key'] ) );
466 486 }
467 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 +
468 492 if ( isset( $_GET['feedzy_reference_key'] ) || ( isset( $_GET['from'], $_GET['plugin'] ) && $_GET['from'] === 'import' && str_starts_with( sanitize_key( $_GET['plugin'] ), 'feedzy' ) ) ) {
469 493 update_option( 'feedzy_reference_key', sanitize_key( $_GET['feedzy_reference_key'] ?? 'i-' . $this->product->get_key() ) );
470 494 update_option( $this->option_feedzy, 1 );
471 495 }
@@ -567,8 +591,18 @@
567 591 'show_in_rest' => true,
568 592 'default' => false,
569 593 )
570 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 + );
571 605 }
572 606
573 607 /**
574 608 * Check if the path is writable.
@@ -639,8 +673,14 @@
639 673 $has_feedzy = defined( 'FEEDZY_BASEFILE' ) || $this->is_plugin_installed( 'feedzy-rss-feedss' );
640 674 $had_feedzy_from_promo = get_option( $this->option_feedzy, false );
641 675 $had_masteriyo_from_promo = get_option( $this->option_masteriyo, false );
642 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();
643 683
644 684 $all = [
645 685 'optimole' => [
646 686 'om-editor' => [
@@ -752,8 +792,18 @@
752 792 'env' => $has_masteriyo_conditions,
753 793 'screen' => 'plugin-install',
754 794 ],
755 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 + ],
756 806 ];
757 807
758 808 foreach ( $all as $slug => $data ) {
759 809 foreach ( $data as $key => $conditions ) {
@@ -819,8 +869,9 @@
819 869 $is_posts = isset( $current_screen->id ) && $current_screen->id === 'edit-post';
820 870 $is_editor = method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor();
821 871 $is_theme_install = isset( $current_screen->id ) && ( $current_screen->id === 'theme-install' );
822 872 $is_plugin_install = isset( $current_screen->id ) && ( $current_screen->id === 'plugin-install' );
873 + $is_profile = isset( $current_screen->id ) && $current_screen->id === 'profile';
823 874 $is_product = isset( $current_screen->id ) && $current_screen->id === 'product';
824 875 $is_import = isset( $current_screen->id ) && $current_screen->id === 'import';
825 876 $is_cf7_install = isset( $current_screen->id ) && function_exists( 'str_contains' ) ? str_contains( $current_screen->id, 'page_wpcf7' ) : false;
826 877
@@ -828,10 +879,19 @@
828 879 $product_install_time = (int) $this->product->get_install_time();
829 880 $is_older = time() > ( $product_install_time + ( 3 * DAY_IN_SECONDS ) );
830 881 $is_newer = time() < ( $product_install_time + ( 6 * HOUR_IN_SECONDS ) );
831 882 foreach ( $this->promotions as $slug => $promos ) {
883 + if ( ! is_array( $promos ) ) {
884 + continue;
885 + }
886 +
832 887 foreach ( $promos as $key => $data ) {
888 + if ( ! is_array( $data ) || ! array_key_exists( 'screen', $data ) ) {
889 + unset( $this->promotions[ $slug ][ $key ] );
833 890
891 + continue;
892 + }
893 +
834 894 $data = wp_parse_args(
835 895 $data,
836 896 [
837 897 'delayed' => false,
@@ -901,8 +961,13 @@
901 961 if ( ! $is_plugin_install ) {
902 962 unset( $this->promotions[ $slug ][ $key ] );
903 963 }
904 964 break;
965 + case 'profile':
966 + if ( ! $is_profile ) {
967 + unset( $this->promotions[ $slug ][ $key ] );
968 + }
969 + break;
905 970 }
906 971 }
907 972
908 973 $return = array_merge( $return, $this->promotions[ $slug ] );
@@ -953,8 +1018,12 @@
953 1018 if ( $this->get_upsells_dismiss_time( 'masteriyo-plugins-install' ) === false ) {
954 1019 add_action( 'admin_notices', [ $this, 'render_masteriyo_notice' ] );
955 1020 }
956 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 +
957 1026 add_action( 'load-import.php', [ $this, 'add_import' ] );
958 1027 $this->load_woo_promos();
959 1028
960 1029 return;
@@ -1022,8 +1091,13 @@
1022 1091 case 'masteriyo-plugins-install':
1023 1092 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
1024 1093 add_action( 'admin_notices', [ $this, 'render_masteriyo_notice' ] );
1025 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;
1026 1100 }
1027 1101 }
1028 1102
1029 1103 /**
@@ -1117,8 +1191,10 @@
1117 1191 'wpFullPayActivationUrl' => $this->get_plugin_activation_link( 'wp-full-stripe-free' ),
1118 1192 'wpFullPayDash' => esc_url( add_query_arg( [ 'page' => 'wpfs-settings-stripe' ], admin_url( 'admin.php' ) ) ),
1119 1193 'masteriyoActivationUrl' => $this->get_plugin_activation_link( 'masteriyo' ),
1120 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' ) ) ),
1121 1197 'nevePreviewURL' => esc_url( add_query_arg( [ 'theme' => 'neve' ], admin_url( 'theme-install.php' ) ) ),
1122 1198 'neveAction' => $neve_action,
1123 1199 'activateNeveURL' => esc_url(
1124 1200 add_query_arg(
@@ -1184,8 +1260,24 @@
1184 1260 echo '<div id="ti-masteriyo-notice" class="notice notice-info ti-sdk-om-notice"></div>';
1185 1261 }
1186 1262
1187 1263 /**
1264 + * Render Easy MCP AI notice.
1265 + */
1266 + public function render_easy_mcp_notice() {
1267 + // A product on this site already offers the connector in its own words.
1268 + if ( class_exists( Ai_Connect::class ) && Ai_Connect::has_products() ) {
1269 + return;
1270 + }
1271 + // Cards are injected only when a paid product loaded Featured_plugins; only then can this request already show one.
1272 + if ( apply_filters( 'themeisle_sdk_plugin_api_filter_registered', false ) && $this->is_easy_mcp_card_request() ) {
1273 + return;
1274 + }
1275 +
1276 + echo '<div id="ti-easy-mcp-notice" class="notice notice-info ti-sdk-om-notice"></div>';
1277 + }
1278 +
1279 + /**
1188 1280 * Add promo to attachment modal.
1189 1281 *
1190 1282 * @param array $fields Fields array.
1191 1283 * @param \WP_Post $post Post object.
@@ -1657,8 +1749,139 @@
1657 1749 }
1658 1750 }
1659 1751
1660 1752 return false;
1753 + }
1754 +
1755 + /**
1756 + * Check if the site shows signs of AI usage.
1757 + *
1758 + * @return bool True if an AI plugin is installed or a REST API application password is in use.
1759 + */
1760 + public function has_ai_usage_signal() {
1761 + return $this->has_ai_plugin_installed() || $this->has_application_password_in_use();
1762 + }
1763 +
1764 + /**
1765 + * Check if the current plugin-install request already surfaces the Easy MCP AI card,
1766 + * so the notice is not stacked on top of it: the Featured tab injects the card on
1767 + * WordPress 6.9+ and AI-related searches prepend it.
1768 + *
1769 + * @return bool True if the Easy MCP AI card is expected on the current request.
1770 + */
1771 + private function is_easy_mcp_card_request() {
1772 + $current_screen = get_current_screen();
1773 + if ( ! isset( $current_screen->id ) || 'plugin-install' !== $current_screen->id ) {
1774 + return false;
1775 + }
1776 +
1777 + // phpcs:disable WordPress.Security.NonceVerification.Recommended
1778 + $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : '';
1779 + $search = isset( $_GET['s'] ) ? strtolower( sanitize_text_field( wp_unslash( $_GET['s'] ) ) ) : '';
1780 + $paged = isset( $_GET['paged'] ) ? (int) $_GET['paged'] : 1;
1781 + // phpcs:enable WordPress.Security.NonceVerification.Recommended
1782 +
1783 + // Cards are only injected on the first results page.
1784 + if ( $paged > 1 ) {
1785 + return false;
1786 + }
1787 +
1788 + if ( '' === $tab || 'featured' === $tab ) {
1789 + return version_compare( get_bloginfo( 'version' ), '6.9', '>=' );
1790 + }
1791 +
1792 + if ( '' !== $search ) {
1793 + return Featured_Plugins::matches_ai_search_keywords( $search ) && ! Featured_Plugins::matches_lms_search_keywords( $search );
1794 + }
1795 +
1796 + return false;
1797 + }
1798 +
1799 + /**
1800 + * Check if a known AI plugin is installed, active or not.
1801 + *
1802 + * @return bool True if an AI plugin is installed, false otherwise.
1803 + */
1804 + private function has_ai_plugin_installed() {
1805 + return $this->any_plugin_dir_exists(
1806 + array(
1807 + 'ai-engine',
1808 + 'gpt3-ai-content-generator',
1809 + 'getgenie',
1810 + 'bertha-ai',
1811 + 'hyve',
1812 + 'hyve-lite',
1813 + )
1814 + );
1815 + }
1816 +
1817 + /**
1818 + * Check if any of the given plugin slugs has a directory in the plugins folder.
1819 + *
1820 + * @param array $slugs Plugin slugs.
1821 + *
1822 + * @return bool True if at least one plugin directory exists, false otherwise.
1823 + */
1824 + private function any_plugin_dir_exists( $slugs ) {
1825 + foreach ( $slugs as $slug ) {
1826 + if ( is_dir( WP_PLUGIN_DIR . '/' . $slug ) ) {
1827 + return true;
1828 + }
1829 + }
1830 +
1831 + return false;
1832 + }
1833 +
1834 + /**
1835 + * Check if a REST API application password is in use on the site.
1836 + *
1837 + * @return bool True if an application password is in use, false otherwise.
1838 + */
1839 + private function has_application_password_in_use() {
1840 + return class_exists( 'WP_Application_Passwords' ) && method_exists( 'WP_Application_Passwords', 'is_in_use' ) && \WP_Application_Passwords::is_in_use();
1841 + }
1842 +
1843 + /**
1844 + * Check if the current request is a profile screen where the Application
1845 + * Passwords section lives — a user wiring up an external REST client.
1846 + *
1847 + * @return bool True if a profile promo can be displayed, false otherwise.
1848 + */
1849 + private function can_check_profile_promo() {
1850 + if ( ! is_admin() || ! current_user_can( 'install_plugins' ) ) {
1851 + return false;
1852 + }
1853 +
1854 + $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
1855 + if ( isset( $current_screen->id ) ) {
1856 + return 'profile' === $current_screen->id;
1857 + }
1858 +
1859 + global $pagenow;
1860 +
1861 + return isset( $pagenow ) && 'profile.php' === $pagenow;
1862 + }
1863 +
1864 + /**
1865 + * Check if a competing MCP server plugin is installed, active or not.
1866 + *
1867 + * AI Engine ships an optional MCP feature but is intentionally kept in the
1868 + * AI-usage signals instead — its primary identity is an AI framework.
1869 + *
1870 + * @return bool True if an MCP server plugin is installed, false otherwise.
1871 + */
1872 + private function has_mcp_plugin_installed() {
1873 + return $this->any_plugin_dir_exists(
1874 + array(
1875 + 'vibe-ai',
1876 + 'royal-mcp',
1877 + 'stifli-flex-mcp',
1878 + 'miniorange-secure-mcp-server',
1879 + 'wsp-mcp-ai-agents-connector',
1880 + 'cowboy-mcp',
1881 + 'nibwp',
1882 + )
1883 + );
1661 1884 }
1662 1885
1663 1886 /**
1664 1887 * Check if the current request can display a plugin install promo.