is_from_partner( $product ) ) { return false; } $this->debug = apply_filters( 'themeisle_sdk_promo_debug', $this->debug ); $promotions_to_load = apply_filters( $product->get_key() . '_load_promotions', array() ); $promotions_to_load[] = 'optimole'; $promotions_to_load[] = 'rop'; $promotions_to_load[] = 'woo_plugins'; $promotions_to_load[] = 'neve'; $promotions_to_load[] = 'redirection-cf7'; $promotions_to_load[] = 'hyve'; $promotions_to_load[] = 'wp_full_pay'; $promotions_to_load[] = 'feedzy_import'; $promotions_to_load[] = 'learning-management-system'; if ( defined( 'NEVE_VERSION' ) || defined( 'WPMM_PATH' ) || defined( 'OTTER_BLOCKS_VERSION' ) || defined( 'OBFX_URL' ) ) { $promotions_to_load[] = 'feedzy_embed'; } $promotions_to_load = array_unique( $promotions_to_load ); $this->promotions = $this->get_promotions(); $this->dissallowed_promotions = apply_filters( $product->get_key() . '_dissallowed_promotions', array() ); foreach ( $this->promotions as $slug => $data ) { if ( ! in_array( $slug, $promotions_to_load, true ) ) { unset( $this->promotions[ $slug ] ); } } add_action( 'init', array( $this, 'register_settings' ), 99 ); add_action( 'admin_init', array( $this, 'register_reference' ), 99 ); return ! empty( $this->promotions ); } /** * Registers the hooks. * * @param Product $product Product to load. */ public function load( $product ) { if ( ! $this->is_writeable() || ! current_user_can( 'install_plugins' ) ) { return; } $last_dismiss_time = $this->get_last_dismiss_time(); if ( ! $this->debug && is_int( $last_dismiss_time ) && ( time() - $last_dismiss_time ) < ( 3 * WEEK_IN_SECONDS ) ) { return; } $this->product = $product; add_filter( 'attachment_fields_to_edit', array( $this, 'add_attachment_field' ), 10, 2 ); add_action( 'current_screen', [ $this, 'load_available' ] ); add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'enqueue' ) ); add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_visualizer_block_editor_shim' ), 100 ); add_action( 'wp_ajax_tisdk_update_option', array( $this, 'dismiss_promotion' ) ); add_filter( 'plugins_api_result', array( $this, 'inject_visualizer_block_directory_suggestion' ), 10, 3 ); add_filter( 'option_visualizer-activated', array( $this, 'suppress_visualizer_onboarding_in_editor' ) ); add_filter( 'themeisle_sdk_ran_promos', '__return_true' ); if ( get_option( $this->option_neve, false ) !== true ) { add_action( 'wp_ajax_themeisle_sdk_dismiss_notice', 'ThemeisleSDK\Modules\Notification::dismiss' ); } } /** * Inject Visualizer as the first block-directory suggestion for chart queries. * * @param object|WP_Error $res Response object or WP_Error. * @param string $action The API action. * @param object $args The API arguments. * @return object|WP_Error */ public function inject_visualizer_block_directory_suggestion( $res, $action, $args ) { if ( 'query_plugins' !== $action || ! is_object( $args ) || empty( $args->block ) ) { return $res; } if ( ! $this->should_suggest_visualizer( $args->block ) ) { return $res; } if ( $this->is_plugin_installed( 'visualizer' ) ) { return $res; } $plugin = $this->get_visualizer_block_directory_data(); if ( empty( $plugin ) ) { return $res; } if ( is_wp_error( $res ) ) { $res = (object) array( 'plugins' => array() ); } if ( ! isset( $res->plugins ) || ! is_array( $res->plugins ) ) { $res->plugins = array(); } $res->plugins = array_values( array_filter( $res->plugins, function ( $existing ) use ( $plugin ) { return ! isset( $existing['slug'] ) || $existing['slug'] !== $plugin['slug']; } ) ); array_unshift( $res->plugins, $plugin ); return $res; } /** * Check if the query should trigger the Visualizer suggestion. * * @param string $term Search term. * @return bool */ private function should_suggest_visualizer( $term ) { $term = strtolower( (string) $term ); return false !== strpos( $term, 'chart' ) || false !== strpos( $term, 'visualizer' ) || false !== strpos( $term, 'visualization' ) || false !== strpos( $term, 'graph' ); } /** * Build the plugin data for Visualizer block directory results. * * @return array */ private function get_visualizer_block_directory_data() { $slug = 'visualizer'; $plugin_info = $this->call_plugin_api( $slug ); if ( is_wp_error( $plugin_info ) || empty( $plugin_info ) ) { return array(); } $icons = array(); if ( ! empty( $plugin_info->icons ) ) { if ( ! empty( $plugin_info->icons['1x'] ) ) { $icons['1x'] = $plugin_info->icons['1x']; } if ( ! empty( $plugin_info->icons['2x'] ) ) { $icons['2x'] = $plugin_info->icons['2x']; } } $name = isset( $plugin_info->name ) ? $plugin_info->name : 'Visualizer'; return array( 'slug' => $slug, 'name' => $name, 'short_description' => isset( $plugin_info->short_description ) ? $plugin_info->short_description : '', 'author' => isset( $plugin_info->author ) ? wp_strip_all_tags( $plugin_info->author ) : '', 'rating' => isset( $plugin_info->rating ) ? (int) $plugin_info->rating : 0, 'num_ratings' => isset( $plugin_info->num_ratings ) ? (int) $plugin_info->num_ratings : 0, 'active_installs' => isset( $plugin_info->active_installs ) ? (int) $plugin_info->active_installs : 0, 'author_block_rating' => isset( $plugin_info->author_block_rating ) ? (int) $plugin_info->author_block_rating : 0, 'author_block_count' => isset( $plugin_info->author_block_count ) ? (int) $plugin_info->author_block_count : 0, 'icons' => $icons, 'last_updated' => isset( $plugin_info->last_updated ) ? $plugin_info->last_updated : gmdate( 'Y-m-d H:i:s' ), 'blocks' => array( array( 'name' => 'visualizer/chart', 'title' => $name, ), ), ); } /** * Prevent Visualizer onboarding redirects while in the block editor. * * @param mixed $value Option value. * @return mixed */ public function suppress_visualizer_onboarding_in_editor( $value ) { if ( ! $this->is_block_editor_screen() ) { return $value; } return false; } /** * Add a small compatibility shim for Visualizer's block editor bundle. * * Visualizer's "Display an existing chart" flow reads * `google.visualization.Version` before the Google Charts loader has fully * populated `google.visualization`, which can throw after dynamic install. * * @return void */ public function enqueue_visualizer_block_editor_shim() { global $themeisle_sdk_max_version; if ( ! $this->is_block_editor_screen() ) { return; } if ( ! wp_script_is( 'visualizer-gutenberg-block', 'enqueued' ) ) { return; } wp_register_script( 'ti-sdk-visualizer-editor-shim', '', array(), $themeisle_sdk_max_version, true ); wp_enqueue_script( 'ti-sdk-visualizer-editor-shim' ); wp_add_inline_script( 'ti-sdk-visualizer-editor-shim', 'window.google = window.google || {}; window.google.visualization = window.google.visualization || {}; window.google.visualization.Version = window.google.visualization.Version || "current";' ); } /** * Check if the current admin screen is the block editor. * * @return bool */ private function is_block_editor_screen() { if ( ! function_exists( 'get_current_screen' ) ) { return $this->is_block_editor_request(); } $screen = get_current_screen(); if ( is_object( $screen ) && method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() ) { return true; } return $this->is_block_editor_request(); } /** * Detect block editor requests before the current screen is available. * * @return bool */ private function is_block_editor_request() { global $pagenow; if ( 'post-new.php' === $pagenow ) { $post_type = isset( $_GET['post_type'] ) ? sanitize_key( $_GET['post_type'] ) : 'post'; return function_exists( 'use_block_editor_for_post_type' ) ? use_block_editor_for_post_type( $post_type ) : true; } if ( 'post.php' === $pagenow && isset( $_GET['post'] ) ) { $post_id = absint( $_GET['post'] ); return function_exists( 'use_block_editor_for_post' ) ? use_block_editor_for_post( $post_id ) : true; } return false; } /** * Load available promotions. */ public function load_available() { if ( $this->available_promotions_loaded ) { return; } $this->available_promotions_loaded = true; $this->promotions = $this->filter_by_screen_and_merge(); if ( empty( $this->promotions ) ) { return; } $this->load_promotion( $this->promotions[ array_rand( $this->promotions ) ] ); } /** * Register plugin reference. * * @return void */ public function register_reference() { if ( ! current_user_can( 'activate_plugins' ) ) { return; } if ( ! isset( $_GET['plugin'] ) || ! isset( $_GET['_wpnonce'] ) ) { return; } $plugin = rawurldecode( $_GET['plugin'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( wp_verify_nonce( $_GET['_wpnonce'], 'activate-plugin_' . $plugin ) === false ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized return; } if ( isset( $_GET['reference_key'] ) ) { update_option( 'otter_reference_key', sanitize_key( $_GET['reference_key'] ) ); } if ( isset( $_GET['optimole_reference_key'] ) ) { update_option( 'optimole_reference_key', sanitize_key( $_GET['optimole_reference_key'] ) ); } if ( isset( $_GET['rop_reference_key'] ) ) { update_option( 'rop_reference_key', sanitize_key( $_GET['rop_reference_key'] ) ); } if ( isset( $_GET['neve_reference_key'] ) ) { update_option( 'neve_reference_key', sanitize_key( $_GET['neve_reference_key'] ) ); } if ( isset( $_GET['hyve_reference_key'] ) ) { update_option( 'hyve_reference_key', sanitize_key( $_GET['hyve_reference_key'] ) ); } if ( isset( $_GET['wp_full_pay_reference_key'] ) ) { update_option( 'wp_full_pay_reference_key', sanitize_key( $_GET['wp_full_pay_reference_key'] ) ); } if ( isset( $_GET['feedzy_reference_key'] ) || ( isset( $_GET['from'], $_GET['plugin'] ) && $_GET['from'] === 'import' && str_starts_with( sanitize_key( $_GET['plugin'] ), 'feedzy' ) ) ) { update_option( 'feedzy_reference_key', sanitize_key( $_GET['feedzy_reference_key'] ?? 'i-' . $this->product->get_key() ) ); update_option( $this->option_feedzy, 1 ); } } /** * Register Settings */ public function register_settings() { $default = get_option( 'themeisle_sdk_promotions_otter', '{}' ); register_setting( 'themeisle_sdk_settings', $this->option_main, array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'show_in_rest' => true, 'default' => $default, ) ); register_setting( 'themeisle_sdk_settings', $this->option_otter, array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'show_in_rest' => true, 'default' => false, ) ); register_setting( 'themeisle_sdk_settings', $this->option_optimole, array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'show_in_rest' => true, 'default' => false, ) ); register_setting( 'themeisle_sdk_settings', $this->option_rop, array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'show_in_rest' => true, 'default' => false, ) ); register_setting( 'themeisle_sdk_settings', $this->option_neve, array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'show_in_rest' => true, 'default' => false, ) ); register_setting( 'themeisle_sdk_settings', $this->option_redirection_cf7, array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'show_in_rest' => true, 'default' => false, ) ); register_setting( 'themeisle_sdk_settings', $this->option_hyve, array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'show_in_rest' => true, 'default' => false, ) ); register_setting( 'themeisle_sdk_settings', $this->option_wp_full_pay, array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'show_in_rest' => true, 'default' => false, ) ); register_setting( 'themeisle_sdk_settings', $this->option_masteriyo, array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'show_in_rest' => true, 'default' => false, ) ); } /** * Check if the path is writable. * * @return boolean * @access public */ public function is_writeable() { include_once ABSPATH . 'wp-admin/includes/file.php'; $filesystem_method = get_filesystem_method(); if ( 'direct' === $filesystem_method ) { return true; } return false; } /** * Third-party compatibility. * * @return boolean */ private function has_conflicts() { global $pagenow; // Editor notices aren't compatible with Enfold theme. if ( defined( 'AV_FRAMEWORK_VERSION' ) && in_array( $pagenow, array( 'post.php', 'post-new.php' ) ) ) { return true; } return false; } /** * Get promotions. * * @return array */ private function get_promotions() { $has_otter = defined( 'OTTER_BLOCKS_VERSION' ) || $this->is_plugin_installed( 'otter-blocks' ); $had_otter_from_promo = get_option( $this->option_otter, false ); $has_optimole = defined( 'OPTIMOLE_VERSION' ) || $this->is_plugin_installed( 'optimole-wp' ); $had_optimole_from_promo = get_option( $this->option_optimole, false ); $has_rop = defined( 'ROP_LITE_VERSION' ) || $this->is_plugin_installed( 'tweet-old-post' ); $had_rop_from_promo = get_option( $this->option_rop, false ); $has_woocommerce = class_exists( 'WooCommerce' ); $has_sparks = defined( 'SPARKS_WC_VERSION' ) || $this->is_plugin_installed( 'sparks-for-woocommerce' ); $has_ppom = defined( 'PPOM_VERSION' ) || $this->is_plugin_installed( 'woocommerce-product-addon' ); $has_redirection_cf7 = defined( 'WPCF7_PRO_REDIRECT_PLUGIN_VERSION' ) || $this->is_plugin_installed( 'wpcf7-redirect' ); $had_redirection_cf7_promo = get_option( $this->option_redirection_cf7, false ); $is_min_php_7_4 = version_compare( PHP_VERSION, '7.4', '>=' ); $is_min_php_7_2 = version_compare( PHP_VERSION, '7.2', '>=' ); $can_check_plugin_install = $this->can_check_plugin_install_promo(); $has_hyve = defined( 'HYVE_LITE_VERSION' ) || $this->is_plugin_installed( 'hyve' ) || $this->is_plugin_installed( 'hyve-lite' ); $had_hyve_from_promo = get_option( $this->option_hyve, false ); $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(); $has_wfp_full_pay = defined( 'WP_FULL_STRIPE_BASENAME' ) || $this->is_plugin_installed( 'wp-full-stripe-free' ); $had_wfp_from_promo = get_option( $this->option_wp_full_pay, false ); $has_wfp_conditions = ! $has_wfp_full_pay && ! $had_wfp_from_promo && $can_check_plugin_install && $this->has_donate_page(); $is_min_req_v = version_compare( get_bloginfo( 'version' ), '5.8', '>=' ); $current_theme = wp_get_theme(); $has_neve = $current_theme->template === 'neve' || $current_theme->parent() === 'neve'; $has_neve_from_promo = get_option( $this->option_neve, false ); $has_enough_attachments = $this->has_min_media_attachments(); $has_enough_old_posts = $this->has_old_posts(); $has_feedzy = defined( 'FEEDZY_BASEFILE' ) || $this->is_plugin_installed( 'feedzy-rss-feedss' ); $had_feedzy_from_promo = get_option( $this->option_feedzy, false ); $had_masteriyo_from_promo = get_option( $this->option_masteriyo, false ); $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(); $all = [ 'optimole' => [ 'om-editor' => [ 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo, 'screen' => 'editor', 'delayed' => true, ], 'om-image-block' => [ 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo, 'screen' => 'editor', 'delayed' => true, ], 'om-attachment' => [ 'env' => ! $has_optimole && ! $had_optimole_from_promo, 'screen' => 'media-editor', ], 'om-media' => [ 'env' => ! $has_optimole && ! $had_optimole_from_promo && $has_enough_attachments, 'screen' => 'media', ], 'om-elementor' => [ 'env' => ! $has_optimole && ! $had_optimole_from_promo && defined( 'ELEMENTOR_VERSION' ), 'screen' => 'elementor', 'delayed' => true, ], ], 'feedzy_import' => [ 'feedzy-import' => [ 'env' => true, 'screen' => 'import', 'always' => true, ], ], 'feedzy_embed' => [ 'feedzy-editor' => [ 'env' => ! $has_feedzy && is_main_site() && ! $had_feedzy_from_promo, 'screen' => 'editor', ], ], 'otter' => [ 'blocks-css' => [ 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo, 'screen' => 'editor', 'delayed' => true, ], 'blocks-animation' => [ 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo, 'screen' => 'editor', 'delayed' => true, ], 'blocks-conditions' => [ 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo, 'screen' => 'editor', 'delayed' => true, ], ], 'rop' => [ 'rop-posts' => [ 'env' => ! $has_rop && ! $had_rop_from_promo && $has_enough_old_posts, 'screen' => 'edit-post', 'delayed' => true, ], ], 'woo_plugins' => [ 'ppom' => [ 'env' => ! $has_ppom && $has_woocommerce, 'screen' => 'edit-product', ], 'sparks-wishlist' => [ 'env' => ! $has_sparks && $has_woocommerce, 'screen' => 'edit-product', ], 'sparks-announcement' => [ 'env' => ! $has_sparks && $has_woocommerce, 'screen' => 'edit-product', ], 'sparks-product-review' => [ 'env' => ! $has_sparks && $has_woocommerce, 'screen' => 'edit-product', ], ], 'neve' => [ 'neve-themes-popular' => [ 'env' => ! $has_neve && ! $has_neve_from_promo, 'screen' => 'themes-install-popular', ], ], 'redirection-cf7' => [ 'wpcf7' => [ 'env' => ! $has_redirection_cf7 && ! $had_redirection_cf7_promo, 'screen' => 'wpcf7', 'delayed' => true, ], ], 'hyve' => [ 'hyve-plugins-install' => [ 'env' => $has_hyve_conditions, 'screen' => 'plugin-install', ], ], 'wp_full_pay' => [ 'wp-full-pay-plugins-install' => [ 'env' => $has_wfp_conditions, 'screen' => 'plugin-install', ], ], 'learning-management-system' => [ 'masteriyo-plugins-install' => [ 'env' => $has_masteriyo_conditions, 'screen' => 'plugin-install', ], ], ]; foreach ( $all as $slug => $data ) { foreach ( $data as $key => $conditions ) { if ( ! $conditions['env'] || $this->has_conflicts() ) { unset( $all[ $slug ][ $key ] ); continue; } if ( $this->get_upsells_dismiss_time( $key ) ) { unset( $all[ $slug ][ $key ] ); } } if ( empty( $all[ $slug ] ) ) { unset( $all[ $slug ] ); } } return $all; } /** * Get the upsell dismiss time. * * @param string $key The upsell key. If empty will return all dismiss times. * * @return false | string | array */ private function get_upsells_dismiss_time( $key = '' ) { $old = get_option( 'themeisle_sdk_promotions_otter', '{}' ); $data = get_option( $this->option_main, $old ); $data = json_decode( $data, true ); if ( empty( $key ) ) { return $data; } return isset( $data[ $key ] ) ? $data[ $key ] : false; } /** * Get the last dismiss time of a promotion. * * @return int | false The timestamp of last dismiss or false. */ private function get_last_dismiss_time() { $dismissed = $this->get_upsells_dismiss_time(); return empty( $dismissed ) ? false : max( array_values( $dismissed ) ); } /** * Filter by screen & merge into single array of keys. * * @return array */ private function filter_by_screen_and_merge() { $current_screen = get_current_screen(); $is_elementor = isset( $_GET['action'] ) && $_GET['action'] === 'elementor'; $is_media = isset( $current_screen->id ) && $current_screen->id === 'upload'; $is_posts = isset( $current_screen->id ) && $current_screen->id === 'edit-post'; $is_editor = method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor(); $is_theme_install = isset( $current_screen->id ) && ( $current_screen->id === 'theme-install' ); $is_plugin_install = isset( $current_screen->id ) && ( $current_screen->id === 'plugin-install' ); $is_product = isset( $current_screen->id ) && $current_screen->id === 'product'; $is_import = isset( $current_screen->id ) && $current_screen->id === 'import'; $is_cf7_install = isset( $current_screen->id ) && function_exists( 'str_contains' ) ? str_contains( $current_screen->id, 'page_wpcf7' ) : false; $return = []; $product_install_time = (int) $this->product->get_install_time(); $is_older = time() > ( $product_install_time + ( 3 * DAY_IN_SECONDS ) ); $is_newer = time() < ( $product_install_time + ( 6 * HOUR_IN_SECONDS ) ); foreach ( $this->promotions as $slug => $promos ) { if ( ! is_array( $promos ) ) { continue; } foreach ( $promos as $key => $data ) { if ( ! is_array( $data ) || ! array_key_exists( 'screen', $data ) ) { unset( $this->promotions[ $slug ][ $key ] ); continue; } $data = wp_parse_args( $data, [ 'delayed' => false, 'always' => false, ] ); if ( ! $this->debug && ( ( $data['delayed'] === true && ! $is_older ) || // Skip promotions that are delayed for 3 days. $is_newer // Skip promotions for the first 6 hours after install. ) && ! $data['always'] ) { unset( $this->promotions[ $slug ][ $key ] ); continue; } switch ( $data['screen'] ) { case 'media-editor': if ( ! $is_media && ! $is_editor ) { unset( $this->promotions[ $slug ][ $key ] ); } break; case 'media': if ( ! $is_media ) { unset( $this->promotions[ $slug ][ $key ] ); } break; case 'editor': if ( ! $is_editor || $is_elementor ) { unset( $this->promotions[ $slug ][ $key ] ); } break; case 'import': if ( ! $is_import ) { unset( $this->promotions[ $slug ][ $key ] ); } break; case 'elementor': if ( ! $is_elementor ) { unset( $this->promotions[ $slug ][ $key ] ); } break; case 'edit-post': if ( ! $is_posts ) { unset( $this->promotions[ $slug ][ $key ] ); } break; case 'edit-product': if ( ! $is_product ) { unset( $this->promotions[ $slug ][ $key ] ); } break; case 'themes-install-popular': if ( ! $is_theme_install ) { unset( $this->promotions[ $slug ][ $key ] ); } break; case 'wpcf7': if ( ! $is_cf7_install ) { unset( $this->promotions[ $slug ][ $key ] ); } break; case 'plugin-install': if ( ! $is_plugin_install ) { unset( $this->promotions[ $slug ][ $key ] ); } break; } } $return = array_merge( $return, $this->promotions[ $slug ] ); } $return = array_filter( $return, function ( $value, $key ) { return ! in_array( $key, $this->dissallowed_promotions, true ); }, ARRAY_FILTER_USE_BOTH ); return array_keys( $return ); } /** * Load single promotion. * * @param string $slug slug of the promotion. */ private function load_promotion( $slug ) { $this->loaded_promo = $slug; if ( $this->debug ) { add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] ); if ( $this->get_upsells_dismiss_time( 'om-media' ) === false ) { add_action( 'admin_notices', [ $this, 'render_optimole_dash_notice' ] ); } if ( $this->get_upsells_dismiss_time( 'rop-posts' ) === false ) { add_action( 'admin_notices', [ $this, 'render_rop_dash_notice' ] ); } if ( $this->get_upsells_dismiss_time( 'neve-themes-popular' ) === false ) { add_action( 'admin_notices', [ $this, 'render_neve_themes_notice' ] ); } if ( $this->get_upsells_dismiss_time( 'redirection-cf7' ) === false ) { add_action( 'admin_notices', [ $this, 'render_redirection_cf7_notice' ] ); } if ( $this->get_upsells_dismiss_time( 'hyve-plugins-install' ) === false ) { add_action( 'admin_notices', [ $this, 'render_hyve_notice' ] ); } if ( $this->get_upsells_dismiss_time( 'wp-full-pay-plugins-install' ) === false ) { add_action( 'admin_notices', [ $this, 'render_wp_full_pay_notice' ] ); } if ( $this->get_upsells_dismiss_time( 'masteriyo-plugins-install' ) === false ) { add_action( 'admin_notices', [ $this, 'render_masteriyo_notice' ] ); } add_action( 'load-import.php', [ $this, 'add_import' ] ); $this->load_woo_promos(); return; } switch ( $slug ) { case 'om-editor': case 'om-image-block': case 'blocks-css': case 'blocks-animation': case 'blocks-conditions': add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue' ] ); break; case 'om-attachment': add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] ); break; case 'om-media': add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] ); add_action( 'admin_notices', [ $this, 'render_optimole_dash_notice' ] ); break; case 'rop-posts': add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] ); add_action( 'admin_notices', [ $this, 'render_rop_dash_notice' ] ); break; case 'feedzy-import': add_action( 'load-import.php', [ $this, 'add_import' ] ); break; case 'feedzy-editor': add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue' ] ); break; case 'ppom': case 'sparks-wishlist': case 'sparks-announcement': case 'sparks-product-reviews': $this->load_woo_promos(); break; case 'neve-themes-popular': // Remove any other notifications if Neve promotion is showing remove_action( 'admin_notices', array( 'ThemeisleSDK\Modules\Notification', 'show_notification' ) ); remove_action( 'wp_ajax_themeisle_sdk_dismiss_notice', array( 'ThemeisleSDK\Modules\Notification', 'dismiss', ) ); remove_action( 'admin_head', array( 'ThemeisleSDK\Modules\Notification', 'dismiss_get' ) ); remove_action( 'admin_head', array( 'ThemeisleSDK\Modules\Notification', 'setup_notifications' ) ); // Add required actions to display this notification add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] ); add_action( 'admin_notices', [ $this, 'render_neve_themes_notice' ] ); break; case 'wpcf7': add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] ); add_action( 'admin_notices', [ $this, 'render_redirection_cf7_notice' ] ); break; case 'hyve-plugins-install': add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] ); add_action( 'admin_notices', [ $this, 'render_hyve_notice' ] ); break; case 'wp-full-pay-plugins-install': add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] ); add_action( 'admin_notices', [ $this, 'render_wp_full_pay_notice' ] ); break; case 'masteriyo-plugins-install': add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] ); add_action( 'admin_notices', [ $this, 'render_masteriyo_notice' ] ); break; } } /** * Add import row. * * @return void */ public function add_import() { global $wp_importers; if ( isset( $wp_importers['feedzy-rss-feeds'] ) ) { return; } $wp_importers['feedzy-rss-feeds'] = array( // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited 'Feedzy', sprintf( Loader::$labels['promotions']['feedzy']['import_desc'], '', $this->product->get_friendly_name(), '' ), 'install' => 'feedzy-rss-feeds', ); if ( defined( 'FEEDZY_BASEFILE' ) ) { unset( $wp_importers['feedzy-rss-feeds']['install'] ); } } /** * Render dashboard notice. */ public function render_optimole_dash_notice() { $screen = get_current_screen(); if ( ! isset( $screen->id ) || $screen->id !== 'upload' ) { return; } echo '
'; } /** * Enqueue the assets. */ public function enqueue() { global $themeisle_sdk_max_path; $handle = 'ti-sdk-promo'; $saved = $this->get_upsells_dismiss_time(); $themeisle_sdk_src = $this->get_sdk_uri(); $user = wp_get_current_user(); $asset_file = require $themeisle_sdk_max_path . '/assets/js/build/promos/index.asset.php'; $deps = array_merge( $asset_file['dependencies'], [ 'updates' ] ); $themes = wp_get_themes(); $neve_action = isset( $themes['neve'] ) ? 'activate' : 'install'; $labels = Loader::$labels['promotions']; $labels['feedzy']['editor_recommends'] = sprintf( $labels['feedzy']['editor_recommends'], $this->product->get_friendly_name(), '', '' ); wp_register_script( $handle, $themeisle_sdk_src . 'assets/js/build/promos/index.js', $deps, $asset_file['version'], true ); wp_localize_script( $handle, 'themeisleSDKPromotions', [ 'debug' => $this->debug, 'labels' => $labels, 'email' => $user->user_email, 'showPromotion' => $this->loaded_promo, 'optionKey' => $this->option_main, 'product' => $this->product->get_name(), 'option' => empty( $saved ) ? new \stdClass() : $saved, 'nonce' => wp_create_nonce( 'wp_rest' ), 'assets' => $themeisle_sdk_src . 'assets/images/', 'optimoleApi' => esc_url( rest_url( 'optml/v1/register_service' ) ), 'optimoleActivationUrl' => $this->get_plugin_activation_link( 'optimole-wp' ), 'otterActivationUrl' => $this->get_plugin_activation_link( 'otter-blocks' ), 'ropActivationUrl' => $this->get_plugin_activation_link( 'tweet-old-post' ), 'optimoleDash' => esc_url( add_query_arg( [ 'page' => 'optimole' ], admin_url( 'upload.php' ) ) ), 'ropDash' => esc_url( add_query_arg( [ 'page' => 'TweetOldPost' ], admin_url( 'admin.php' ) ) ), // translators: %s is the product name. 'title' => esc_html( sprintf( Loader::$labels['promotions']['recommended'], $this->product->get_name() ) ), 'redirectionCF7MoreUrl' => tsdk_utmify( 'https://docs.themeisle.com/collection/2014-redirection-for-contact-form-7', 'redirection-for-contact-form-7', 'plugin-install' ), 'rfCF7ActivationUrl' => $this->get_plugin_activation_link( 'wpcf7-redirect' ), 'cf7Dash' => esc_url( add_query_arg( [ 'page' => 'wpcf7-new' ], admin_url( 'admin.php' ) ) ), 'hyveActivationUrl' => $this->get_plugin_activation_link( 'hyve-lite' ), 'hyveDash' => esc_url( add_query_arg( [ 'page' => 'wpfs-settings-stripe' ], admin_url( 'admin.php' ) ) ), 'wpFullPayActivationUrl' => $this->get_plugin_activation_link( 'wp-full-stripe-free' ), 'wpFullPayDash' => esc_url( add_query_arg( [ 'page' => 'wpfs-settings-stripe' ], admin_url( 'admin.php' ) ) ), 'masteriyoActivationUrl' => $this->get_plugin_activation_link( 'masteriyo' ), 'masteriyoDash' => esc_url( add_query_arg( [ 'page' => 'masteriyo-onboard' ], admin_url( 'index.php' ) ) ), 'nevePreviewURL' => esc_url( add_query_arg( [ 'theme' => 'neve' ], admin_url( 'theme-install.php' ) ) ), 'neveAction' => $neve_action, 'activateNeveURL' => esc_url( add_query_arg( [ 'action' => 'activate', 'stylesheet' => 'neve', '_wpnonce' => wp_create_nonce( 'switch-theme_neve' ), ], admin_url( 'themes.php' ) ) ), ] ); wp_enqueue_script( $handle ); wp_enqueue_style( $handle, $themeisle_sdk_src . 'assets/js/build/promos/style-index.css', [ 'wp-components' ], $asset_file['version'] ); } /** * Render rop notice. */ public function render_rop_dash_notice() { $screen = get_current_screen(); if ( ! isset( $screen->id ) || $screen->id !== 'edit-post' ) { return; } echo ''; } /** * Render Neve Themes notice. */ public function render_neve_themes_notice() { echo ''; } /** * Render Hyve notice. */ public function render_hyve_notice() { echo ''; } /** * Render WP Full Pay notice. */ public function render_wp_full_pay_notice() { echo ''; } /** * Render Redirection for CF7 notice. */ public function render_redirection_cf7_notice() { echo ''; } /** * Render Masteriyo notice. */ public function render_masteriyo_notice() { echo ''; } /** * Add promo to attachment modal. * * @param array $fields Fields array. * @param \WP_Post $post Post object. * * @return array */ public function add_attachment_field( $fields, $post ) { if ( $post->post_type !== 'attachment' ) { return $fields; } if ( ! isset( $post->post_mime_type ) || strpos( $post->post_mime_type, 'image' ) === false ) { return $fields; } $meta = wp_get_attachment_metadata( $post->ID ); if ( isset( $meta['filesize'] ) && $meta['filesize'] < 100000 ) { return $fields; } $fields['optimole'] = array( 'input' => 'html', 'html' => '', 'label' => '', ); if ( count( $fields ) < 2 ) { add_filter( 'wp_required_field_message', '__return_empty_string' ); } return $fields; } /** * Check if has 50 image media items. * * @return bool */ private function has_min_media_attachments() { if ( $this->debug ) { return true; } $attachment_count = get_transient( 'tsk_attachment_count' ); if ( false === $attachment_count ) { $args = array( 'post_type' => 'attachment', 'posts_per_page' => 51, 'fields' => 'ids', 'post_status' => 'inherit', 'no_found_rows' => true, ); $query = new \WP_Query( $args ); $attachment_count = $query->post_count; set_transient( 'tsk_attachment_count', $attachment_count, DAY_IN_SECONDS ); } return $attachment_count > 50; } /** * Check if the website has more than 100 posts and over 10 are over a year old. * * @return bool */ private function has_old_posts() { if ( $this->debug ) { return true; } $posts_count = get_transient( 'tsk_posts_count' ); // Create a new WP_Query object to get all posts $args = array( 'post_type' => 'post', 'posts_per_page' => 101, //phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page 'fields' => 'ids', 'no_found_rows' => true, ); if ( false === $posts_count ) { $query = new \WP_Query( $args ); $total_posts = $query->post_count; wp_reset_postdata(); // Count the number of posts older than 1 year $one_year_ago = gmdate( 'Y-m-d H:i:s', strtotime( '-1 year' ) ); $args['date_query'] = array( array( 'before' => $one_year_ago, 'inclusive' => true, ), ); $query = new \WP_Query( $args ); $old_posts = $query->post_count; wp_reset_postdata(); $posts_count = array( 'total_posts' => $total_posts, 'old_posts' => $old_posts, ); set_transient( 'tsk_posts_count', $posts_count, DAY_IN_SECONDS ); } // Check if there are more than 100 posts and more than 10 old posts return $posts_count['total_posts'] > 100 && $posts_count['old_posts'] > 10; } /** * Check if should load Woo promos. * * @return bool */ private function load_woo_promos() { $this->woo_promos = array( 'ppom' => array( 'title' => Loader::$labels['promotions']['woo']['ppom_title'], 'description' => Loader::$labels['promotions']['woo']['ppom_desc'], 'icon' => '', 'has_install' => true, 'link' => wp_nonce_url( add_query_arg( array( 'action' => 'install-plugin', 'plugin' => 'woocommerce-product-addon', ), admin_url( 'update.php' ) ), 'install-plugin_woocommerce-product-addon' ), ), 'sparks-wishlist' => array( 'title' => Loader::$labels['promotions']['woo']['spark_title1'], 'description' => Loader::$labels['promotions']['woo']['spark_desc1'], 'icon' => '', 'link' => tsdk_utmify( 'https://themeisle.com/plugins/sparks-for-woocommerce/', 'promo', 'products-tabs' ), ), 'sparks-announcement' => array( 'title' => Loader::$labels['promotions']['woo']['spark_title2'], 'description' => Loader::$labels['promotions']['woo']['spark_desc2'], 'icon' => '', 'link' => tsdk_utmify( 'https://themeisle.com/plugins/sparks-for-woocommerce/', 'promo', 'products-tabs' ), ), 'sparks-product-review' => array( 'title' => Loader::$labels['promotions']['woo']['spark_title3'], 'description' => Loader::$labels['promotions']['woo']['spark_desc3'], 'icon' => '', 'link' => tsdk_utmify( 'https://themeisle.com/plugins/sparks-for-woocommerce/', 'promo', 'products-tabs' ), ), ); // Check if $this-promotions isn't empty and has one of the items to load. $can_load = ! empty( $this->promotions ) && count( array_intersect( $this->promotions, array_keys( $this->woo_promos ) ) ) > 0; if ( ! $can_load && ! $this->debug ) { return; } add_action( 'woocommerce_product_data_tabs', function ( $tabs ) { $tabs['tisdk-suggestions'] = array( 'label' => Loader::$labels['promotions']['woo']['title'], 'target' => 'tisdk_suggestions', 'class' => array(), 'priority' => 1000, ); return $tabs; } ); add_action( 'woocommerce_product_data_panels', array( $this, 'woocommerce_tab_content' ) ); } /** * WooCommerce Tab Content. */ public function woocommerce_tab_content() { // Filter content based on if the key exists in $this->promotions array. $content = array_filter( $this->woo_promos, function ( $key ) { return in_array( $key, $this->promotions, true ); }, ARRAY_FILTER_USE_KEY ); // Display CSS self::render_woo_tabs_css(); self::render_notice_dismiss_ajax(); ?> false, 'message' => 'Missing nonce or value.', ); wp_send_json( $response ); wp_die(); } $nonce = sanitize_text_field( $_POST['nonce'] ); $value = sanitize_text_field( $_POST['value'] ); if ( ! wp_verify_nonce( $nonce, 'tisdk_update_option' ) ) { $response = array( 'success' => false, 'message' => 'Invalid nonce.', ); wp_send_json( $response ); wp_die(); } $options = get_option( $this->option_main ); $options = json_decode( $options, true ); $options[ $value ] = time(); update_option( $this->option_main, wp_json_encode( $options ) ); $response = array( 'success' => true, ); wp_send_json( $response ); wp_die(); } /** * Check if the user has a support page. */ public function has_support_page() { $page_title_matches = $this->get_page_title_keyword_matches(); return 'yes' === $page_title_matches['support']; } /** * Check if the user has a donate page. */ public function has_donate_page() { $page_title_matches = $this->get_page_title_keyword_matches(); return 'yes' === $page_title_matches['donate']; } /** * Check if the tagline or a published page title contains LMS related keywords. * * @return bool True if LMS-related keywords are found, false otherwise. */ public function has_lms_tagline() { $tagline = strtolower( get_bloginfo( 'description' ) ); foreach ( $this->get_lms_keywords() as $keyword ) { if ( $this->has_lms_keyword_match( $tagline, $keyword ) ) { return true; } } return $this->has_lms_page_title(); } /** * Check if a supported LMS plugin is active. * * @return bool True if an LMS plugin is active, false otherwise. */ private function has_active_lms_plugin() { if ( defined( 'MASTERIYO_VERSION' ) ) { return true; } include_once ABSPATH . 'wp-admin/includes/plugin.php'; $lms_plugins = array( 'tutor/tutor.php', 'sfwd-lms/sfwd_lms.php', 'lifterlms/lifterlms.php', 'sensei-lms/sensei-lms.php', 'learnpress/learnpress.php', 'masterstudy-lms-learning-management-system/masterstudy-lms-learning-management-system.php', 'learning-management-system/lms.php', ); foreach ( $lms_plugins as $plugin ) { if ( is_plugin_active( $plugin ) || ( is_multisite() && is_plugin_active_for_network( $plugin ) ) ) { return true; } } return false; } /** * Check if the current request can display a plugin install promo. * * @return bool True if a plugin install promo can be displayed, false otherwise. */ private function can_check_plugin_install_promo() { if ( ! is_admin() || ! current_user_can( 'install_plugins' ) ) { return false; } $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( isset( $current_screen->id ) ) { return $current_screen->id === 'plugin-install'; } global $pagenow; return isset( $pagenow ) && $pagenow === 'plugin-install.php'; } /** * Check if a published page title contains LMS related keywords. * * @return bool True if an LMS-related page title is found, false otherwise. */ private function has_lms_page_title() { $lms_page_title_signal = get_transient( 'tisdk_lms_page_title_signal_v1' ); if ( in_array( $lms_page_title_signal, array( 'yes', 'no' ), true ) ) { return 'yes' === $lms_page_title_signal; } $has_lms_page_title = $this->has_published_page_title_with_keywords( $this->get_lms_page_title_keywords() ); set_transient( 'tisdk_lms_page_title_signal_v1', $has_lms_page_title ? 'yes' : 'no', 7 * DAY_IN_SECONDS ); return $has_lms_page_title; } /** * Get cached keyword matches for published page titles. * * @return array Page title keyword matches. */ private function get_page_title_keyword_matches() { $default_matches = array( 'support' => 'no', 'donate' => 'no', ); $page_title_matches = get_transient( 'tisdk_page_title_signals_v1' ); if ( is_array( $page_title_matches ) && empty( array_diff_key( $default_matches, $page_title_matches ) ) ) { return array_merge( $default_matches, $page_title_matches ); } global $wpdb; $select_clauses = array(); $query_values = array(); $page_checks = $this->get_page_title_checks(); foreach ( $page_checks as $match_key => $keywords ) { $match_clauses = array(); $query_values[] = 'page'; $query_values[] = 'publish'; foreach ( $keywords as $keyword ) { $match_clauses[] = 'post_title LIKE %s'; $query_values[] = '%' . $wpdb->esc_like( $keyword ) . '%'; } $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; } // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $query = $wpdb->get_row( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( 'SELECT ' . implode( ', ', $select_clauses ), $query_values ), ARRAY_A ); // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $page_title_matches = $default_matches; if ( is_array( $query ) ) { foreach ( array_keys( $page_checks ) as $match_key ) { $page_title_matches[ $match_key ] = ! empty( $query[ 'has_' . $match_key ] ) ? 'yes' : 'no'; } } set_transient( 'tisdk_page_title_signals_v1', $page_title_matches, 7 * DAY_IN_SECONDS ); return $page_title_matches; } /** * Check if a keyword matches content. * * @param string $content Content to check. * @param string $keyword Keyword to look for. * * @return bool True if the keyword matches, false otherwise. */ private function has_lms_keyword_match( $content, $keyword ) { if ( in_array( $keyword, array( 'lms', 'course', 'class' ), true ) ) { return preg_match( '/(^|[^a-z0-9])' . preg_quote( $keyword, '/' ) . '([^a-z0-9]|$)/', $content ) === 1; } return strpos( $content, $keyword ) !== false; } /** * Get LMS related keywords. * * @return array LMS related keywords. */ private function get_lms_keywords() { return array( 'lms', 'learning', 'course', 'courses', 'academy', 'training', 'lesson', 'lessons', 'class', 'classes', 'student', 'students', 'teach', 'teaching', 'tutor', 'quiz', 'education', 'online course', 'online courses', ); } /** * Get LMS related keywords for page title matching. * * @return array LMS related keywords. */ private function get_lms_page_title_keywords() { return array_values( array_diff( $this->get_lms_keywords(), array( 'lms', 'course', 'class' ) ) ); } /** * Check if a published page title contains any of the provided keywords. * * @param array $keywords Keywords to look for. * * @return bool True if a matching page title is found, false otherwise. */ private function has_published_page_title_with_keywords( $keywords ) { if ( empty( $keywords ) ) { return false; } global $wpdb; $query_values = array( 'page', 'publish' ); $clauses = array(); foreach ( $keywords as $keyword ) { $clauses[] = 'post_title LIKE %s'; $query_values[] = '%' . $wpdb->esc_like( $keyword ) . '%'; } // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $page_title_match = $wpdb->get_var( //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( 'SELECT 1 FROM ' . $wpdb->posts . ' WHERE post_type = %s AND post_status = %s AND ( ' . implode( ' OR ', $clauses ) . ' ) LIMIT 1', $query_values ) ); // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare return ! empty( $page_title_match ); } /** * Get page title keyword checks. * * @return array Page title keyword checks. */ private function get_page_title_checks() { return array( 'support' => array( 'support' ), 'donate' => array( 'donate' ), ); } }