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 +221 -2 4.2.6 → trunk 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 }
@@ -208,9 +223,9 @@
208 223 add_filter( 'option_visualizer-activated', array( $this, 'suppress_visualizer_onboarding_in_editor' ) );
209 224 add_filter( 'themeisle_sdk_ran_promos', '__return_true' );
210 225
211 226 if ( get_option( $this->option_neve, false ) !== true ) {
212 - add_action( 'wp_ajax_themeisle_sdk_dismiss_notice', 'ThemeisleSDK\Modules\Notification::regular_dismiss' );
227 + add_action( 'wp_ajax_themeisle_sdk_dismiss_notice', 'ThemeisleSDK\Modules\Notification::dismiss' );
213 228 }
214 229 }
215 230
216 231
@@ -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,20 @@
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 + // 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 + /**
1188 1276 * Add promo to attachment modal.
1189 1277 *
1190 1278 * @param array $fields Fields array.
1191 1279 * @param \WP_Post $post Post object.
@@ -1657,8 +1745,139 @@
1657 1745 }
1658 1746 }
1659 1747
1660 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 + );
1661 1880 }
1662 1881
1663 1882 /**
1664 1883 * Check if the current request can display a plugin install promo.