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-fse'; $this->promotions = $this->get_promotions(); 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; } $this->product = $product; $last_dismiss = $this->get_last_dismiss_time(); if ( ! $this->debug && $last_dismiss && ( time() - $last_dismiss ) < 7 * DAY_IN_SECONDS ) { return; } 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( 'wp_ajax_tisdk_update_option', array( $this, 'dismiss_promotion' ) ); add_filter( 'themeisle_sdk_ran_promos', '__return_true' ); if ( get_option( $this->option_neve_fse, false ) !== true ) { add_action( 'wp_ajax_themeisle_sdk_dismiss_notice', 'ThemeisleSDK\Modules\Notification::regular_dismiss' ); } } /** * Load available promotions. */ public function load_available() { $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 ( 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_fse_reference_key'] ) ) { update_option( 'neve_fse_reference_key', sanitize_key( $_GET['neve_fse_reference_key'] ) ); } } /** * 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_fse, 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' ); $is_min_req_v = version_compare( get_bloginfo( 'version' ), '5.8', '>=' ); $is_min_fse_v = version_compare( get_bloginfo( 'version' ), '6.2', '>=' ); $current_theme = wp_get_theme(); $has_neve_fse = $current_theme->template === 'neve-fse' || $current_theme->parent() === 'neve-fse'; $has_enough_attachments = $this->has_min_media_attachments(); $has_enough_old_posts = $this->has_old_posts(); $all = [ 'optimole' => [ 'om-editor' => [ 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo, 'screen' => 'editor', ], 'om-image-block' => [ 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo, 'screen' => 'editor', ], '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', ], ], 'otter' => [ 'blocks-css' => [ 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo, 'screen' => 'editor', ], 'blocks-animation' => [ 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo, 'screen' => 'editor', ], 'blocks-conditions' => [ 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo, 'screen' => 'editor', ], ], 'rop' => [ 'rop-posts' => [ 'env' => ! $has_rop && ! $had_rop_from_promo && $has_enough_old_posts, 'screen' => 'edit-post', ], ], '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-fse' => [ 'neve-fse-themes-popular' => [ 'env' => ! $has_neve_fse && $is_min_fse_v, 'screen' => 'themes-install-popular', ], ], ]; 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 The timestamp of last dismiss, or install time - 4 days. */ private function get_last_dismiss_time() { $dismissed = $this->get_upsells_dismiss_time(); if ( empty( $dismissed ) ) { // we return the product install time - 4 days because we want to show the upsell after 3 days, // and we move the product install time 4 days in the past. return $this->product->get_install_time() - 4 * DAY_IN_SECONDS; } return 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' || $current_screen->id === 'themes' ); $is_product = isset( $current_screen->id ) && $current_screen->id === 'product'; $return = []; foreach ( $this->promotions as $slug => $promos ) { foreach ( $promos as $key => $data ) { 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 '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; } } $return = array_merge( $return, $this->promotions[ $slug ] ); } 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-fse-themes-popular' ) === false ) { add_action( 'admin_notices', [ $this, 'render_neve_fse_themes_notice' ] ); } $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 'ppom': case 'sparks-wishlist': case 'sparks-announcement': case 'sparks-product-reviews': $this->load_woo_promos(); break; case 'neve-fse-themes-popular': // Remove any other notifications if Neve FSE 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_fse_themes_notice' ] ); break; } } /** * 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' ] ); 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, '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' ) ) ), 'neveFSEMoreUrl' => tsdk_utmify( 'https://themeisle.com/themes/neve-fse/', 'neve-fse-themes-popular', 'theme-install' ), // translators: %s is the product name. 'title' => esc_html( sprintf( __( 'Recommended by %s', 'textdomain' ), $this->product->get_name() ) ), ] ); 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 FSE Themes notice. */ public function render_neve_fse_themes_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'] < 200000 ) { 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' => 'Product Add-Ons', 'description' => 'Add extra custom fields & add-ons on your product pages, like sizes, colors & more.', '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' => 'Wishlist', 'description' => 'Loyalize your customers by allowing them to save their favorite products.', 'icon' => '', 'link' => tsdk_utmify( 'https://themeisle.com/plugins/sparks-for-woocommerce/', 'promo', 'products-tabs' ), ), 'sparks-announcement' => array( 'title' => 'Multi-Announcement Bars', 'description' => 'Add a top notification bar on your website to highlight the latest products, offers, or upcoming events.', 'icon' => '', 'link' => tsdk_utmify( 'https://themeisle.com/plugins/sparks-for-woocommerce/', 'promo', 'products-tabs' ), ), 'sparks-product-review' => array( 'title' => 'Advanced Product Review', 'description' => 'Enable an advanced review section, enlarging the basic review options with lots of capabilities.', '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' => 'More extensions from Themeisle', '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(); } }