PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.10.0
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.10.0
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / codeinwp / themeisle-sdk / src / Modules / Promotions.php

Promotions.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.10.0, at vendor/codeinwp/themeisle-sdk/src/Modules/Promotions.php

699 lines 18.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The promotions model class for ThemeIsle SDK
4 *
5 * Here's how to hook it in your plugin: add_filter( 'menu_icons_load_promotions', function() { return array( 'otter' ); } );
6 *
7 * @package ThemeIsleSDK
8 * @subpackage Modules
9 * @copyright Copyright (c) 2017, Marius Cristea
10 * @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
11 * @since 1.0.0
12 */
13
14 namespace ThemeisleSDK\Modules;
15
16 use ThemeisleSDK\Common\Abstract_Module;
17 use ThemeisleSDK\Product;
18
19 // Exit if accessed directly.
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit;
22 }
23
24 /**
25 * Promotions module for ThemeIsle SDK.
26 */
27 class Promotions extends Abstract_Module {
28
29 /**
30 * Holds the promotions.
31 *
32 * @var array
33 */
34 private $promotions = array();
35
36 /**
37 * Option key for promos.
38 *
39 * @var string
40 */
41 private $option_main = 'themeisle_sdk_promotions';
42
43 /**
44 * Option key for otter promos.
45 *
46 * @var string
47 */
48 private $option_otter = 'themeisle_sdk_promotions_otter_installed';
49
50 /**
51 * Option key for optimole promos.
52 *
53 * @var string
54 */
55 private $option_optimole = 'themeisle_sdk_promotions_optimole_installed';
56
57 /**
58 * Option key for ROP promos.
59 *
60 * @var string
61 */
62 private $option_rop = 'themeisle_sdk_promotions_rop_installed';
63
64 /**
65 * Loaded promotion.
66 *
67 * @var string
68 */
69 private $loaded_promo;
70
71 /**
72 * Debug mode.
73 *
74 * @var bool
75 */
76 private $debug = false;
77
78 /**
79 * Should we load this module.
80 *
81 * @param Product $product Product object.
82 *
83 * @return bool
84 */
85 public function can_load( $product ) {
86 if ( apply_filters( 'themeisle_sdk_ran_promos', false ) === true ) {
87 return false;
88 }
89 if ( $this->is_from_partner( $product ) ) {
90 return false;
91 }
92
93 $this->debug = apply_filters( 'themeisle_sdk_promo_debug', $this->debug );
94 $promotions_to_load = apply_filters( $product->get_key() . '_load_promotions', array() );
95 $promotions_to_load[] = 'optimole';
96 $promotions_to_load[] = 'rop';
97
98 $this->promotions = $this->get_promotions();
99
100 foreach ( $this->promotions as $slug => $data ) {
101 if ( ! in_array( $slug, $promotions_to_load, true ) ) {
102 unset( $this->promotions[ $slug ] );
103 }
104 }
105
106 add_action( 'init', array( $this, 'register_settings' ), 99 );
107 add_action( 'admin_init', array( $this, 'register_reference' ), 99 );
108
109 return ! empty( $this->promotions );
110 }
111
112 /**
113 * Registers the hooks.
114 *
115 * @param Product $product Product to load.
116 */
117 public function load( $product ) {
118 if ( ! $this->is_writeable() || ! current_user_can( 'install_plugins' ) ) {
119 return;
120 }
121
122 $this->product = $product;
123
124 $last_dismiss = $this->get_last_dismiss_time();
125
126 if ( ! $this->debug && $last_dismiss && ( time() - $last_dismiss ) < 7 * DAY_IN_SECONDS ) {
127 return;
128 }
129
130 add_filter( 'attachment_fields_to_edit', array( $this, 'add_attachment_field' ), 10, 2 );
131 add_action( 'current_screen', [ $this, 'load_available' ] );
132 add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'enqueue' ) );
133 add_filter( 'themeisle_sdk_ran_promos', '__return_true' );
134 }
135
136 /**
137 * Load available promotions.
138 */
139 public function load_available() {
140 $this->promotions = $this->filter_by_screen_and_merge();
141
142 if ( empty( $this->promotions ) ) {
143 return;
144 }
145
146 $this->load_promotion( $this->promotions[ array_rand( $this->promotions ) ] );
147 }
148
149
150 /**
151 * Register plugin reference.
152 *
153 * @return void
154 */
155 public function register_reference() {
156 if ( isset( $_GET['reference_key'] ) ) {
157 update_option( 'otter_reference_key', sanitize_key( $_GET['reference_key'] ) );
158 }
159
160 if ( isset( $_GET['optimole_reference_key'] ) ) {
161 update_option( 'optimole_reference_key', sanitize_key( $_GET['optimole_reference_key'] ) );
162 }
163
164 if ( isset( $_GET['rop_reference_key'] ) ) {
165 update_option( 'rop_reference_key', sanitize_key( $_GET['rop_reference_key'] ) );
166 }
167 }
168
169 /**
170 * Register Settings
171 */
172 public function register_settings() {
173 $default = get_option( 'themeisle_sdk_promotions_otter', '{}' );
174
175 register_setting(
176 'themeisle_sdk_settings',
177 $this->option_main,
178 array(
179 'type' => 'string',
180 'sanitize_callback' => 'sanitize_text_field',
181 'show_in_rest' => true,
182 'default' => $default,
183 )
184 );
185
186 register_setting(
187 'themeisle_sdk_settings',
188 $this->option_otter,
189 array(
190 'type' => 'boolean',
191 'sanitize_callback' => 'rest_sanitize_boolean',
192 'show_in_rest' => true,
193 'default' => false,
194 )
195 );
196 register_setting(
197 'themeisle_sdk_settings',
198 $this->option_optimole,
199 array(
200 'type' => 'boolean',
201 'sanitize_callback' => 'rest_sanitize_boolean',
202 'show_in_rest' => true,
203 'default' => false,
204 )
205 );
206 register_setting(
207 'themeisle_sdk_settings',
208 $this->option_rop,
209 array(
210 'type' => 'boolean',
211 'sanitize_callback' => 'rest_sanitize_boolean',
212 'show_in_rest' => true,
213 'default' => false,
214 )
215 );
216 }
217
218 /**
219 * Get the SDK base url.
220 *
221 * @return string
222 */
223 private function get_sdk_uri() {
224 global $themeisle_sdk_max_path;
225
226 /**
227 * $themeisle_sdk_max_path can point to the theme when the theme version is higher.
228 * hence we also need to check that the path does not point to the theme else this will break the URL.
229 * References: https://github.com/Codeinwp/neve-pro-addon/issues/2403
230 */
231 if ( $this->product->is_plugin() && false === strpos( $themeisle_sdk_max_path, get_template_directory() ) ) {
232 return plugins_url( '/', $themeisle_sdk_max_path . '/themeisle-sdk/' );
233 };
234
235 return get_template_directory_uri() . '/vendor/codeinwp/themeisle-sdk/';
236 }
237
238 /**
239 * Check if the path is writable.
240 *
241 * @return boolean
242 * @access public
243 */
244 public function is_writeable() {
245
246 include_once ABSPATH . 'wp-admin/includes/file.php';
247 $filesystem_method = get_filesystem_method();
248
249 if ( 'direct' === $filesystem_method ) {
250 return true;
251 }
252
253 return false;
254 }
255
256 /**
257 * Get the Otter Blocks plugin status.
258 *
259 * @param string $plugin Plugin slug.
260 *
261 * @return bool
262 */
263 private function is_plugin_installed( $plugin ) {
264 static $allowed_keys = [
265 'otter-blocks' => 'otter-blocks/otter-blocks.php',
266 'optimole-wp' => 'optimole-wp/optimole-wp.php',
267 'tweet-old-post' => 'tweet-old-post/tweet-old-post.php',
268 ];
269
270 if ( ! isset( $allowed_keys[ $plugin ] ) ) {
271 return false;
272 }
273
274 if ( file_exists( WP_CONTENT_DIR . '/plugins/' . $allowed_keys[ $plugin ] ) ) {
275 return true;
276 }
277
278 return false;
279 }
280
281 /**
282 * Get promotions.
283 *
284 * @return array
285 */
286 private function get_promotions() {
287 $has_otter = defined( 'OTTER_BLOCKS_VERSION' ) || $this->is_plugin_installed( 'otter-blocks' );
288 $had_otter_from_promo = get_option( $this->option_otter, false );
289 $has_optimole = defined( 'OPTIMOLE_VERSION' ) || $this->is_plugin_installed( 'optimole-wp' );
290 $had_optimole_from_promo = get_option( $this->option_optimole, false );
291 $has_rop = defined( 'ROP_LITE_VERSION' ) || $this->is_plugin_installed( 'tweet-old-post' );
292 $had_rop_from_promo = get_option( $this->option_rop, false );
293 $is_min_req_v = version_compare( get_bloginfo( 'version' ), '5.8', '>=' );
294 $has_enough_attachments = $this->has_min_media_attachments();
295 $has_enough_old_posts = $this->has_old_posts();
296
297 $all = [
298 'optimole' => [
299 'om-editor' => [
300 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
301 'screen' => 'editor',
302 ],
303 'om-image-block' => [
304 'env' => ! $has_optimole && $is_min_req_v && ! $had_optimole_from_promo,
305 'screen' => 'editor',
306 ],
307 'om-attachment' => [
308 'env' => ! $has_optimole && ! $had_optimole_from_promo,
309 'screen' => 'media-editor',
310 ],
311 'om-media' => [
312 'env' => ! $has_optimole && ! $had_optimole_from_promo && $has_enough_attachments,
313 'screen' => 'media',
314 ],
315 'om-elementor' => [
316 'env' => ! $has_optimole && ! $had_optimole_from_promo && defined( 'ELEMENTOR_VERSION' ),
317 'screen' => 'elementor',
318 ],
319 ],
320 'otter' => [
321 'blocks-css' => [
322 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
323 'screen' => 'editor',
324 ],
325 'blocks-animation' => [
326 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
327 'screen' => 'editor',
328 ],
329 'blocks-conditions' => [
330 'env' => ! $has_otter && $is_min_req_v && ! $had_otter_from_promo,
331 'screen' => 'editor',
332 ],
333 ],
334 'rop' => [
335 'rop-posts' => [
336 'env' => ! $has_rop && ! $had_rop_from_promo && $has_enough_old_posts,
337 'screen' => 'edit-post',
338 ],
339 ],
340 ];
341
342 foreach ( $all as $slug => $data ) {
343 foreach ( $data as $key => $conditions ) {
344 if ( ! $conditions['env'] ) {
345 unset( $all[ $slug ][ $key ] );
346
347 continue;
348 }
349
350 if ( $this->get_upsells_dismiss_time( $key ) ) {
351 unset( $all[ $slug ][ $key ] );
352 }
353 }
354
355 if ( empty( $all[ $slug ] ) ) {
356 unset( $all[ $slug ] );
357 }
358 }
359
360 return $all;
361 }
362
363 /**
364 * Get the upsell dismiss time.
365 *
366 * @param string $key The upsell key. If empty will return all dismiss times.
367 *
368 * @return false | string
369 */
370 private function get_upsells_dismiss_time( $key = '' ) {
371 $old = get_option( 'themeisle_sdk_promotions_otter', '{}' );
372 $data = get_option( $this->option_main, $old );
373
374 $data = json_decode( $data, true );
375
376 if ( empty( $key ) ) {
377 return $data;
378 }
379
380 return isset( $data[ $key ] ) ? $data[ $key ] : false;
381 }
382
383 /**
384 * Get the last dismiss time of a promotion.
385 *
386 * @return false
387 */
388 private function get_last_dismiss_time() {
389 $dismissed = $this->get_upsells_dismiss_time();
390
391 if ( empty( $dismissed ) ) {
392 return false;
393 }
394
395 $last_dismiss = max( array_values( $dismissed ) );
396
397 return $last_dismiss;
398 }
399
400 /**
401 * Filter by screen & merge into single array of keys.
402 *
403 * @return array
404 */
405 private function filter_by_screen_and_merge() {
406 $current_screen = get_current_screen();
407
408 $is_elementor = isset( $_GET['action'] ) && $_GET['action'] === 'elementor';
409 $is_media = isset( $current_screen->id ) && $current_screen->id === 'upload';
410 $is_posts = isset( $current_screen->id ) && $current_screen->id === 'edit-post';
411 $is_editor = method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor();
412
413 $return = [];
414
415 foreach ( $this->promotions as $slug => $promos ) {
416 foreach ( $promos as $key => $data ) {
417 switch ( $data['screen'] ) {
418 case 'media-editor':
419 if ( ! $is_media && ! $is_editor ) {
420 unset( $this->promotions[ $slug ][ $key ] );
421 }
422 break;
423 case 'media':
424 if ( ! $is_media ) {
425 unset( $this->promotions[ $slug ][ $key ] );
426 }
427 break;
428 case 'editor':
429 if ( ! $is_editor || $is_elementor ) {
430 unset( $this->promotions[ $slug ][ $key ] );
431 }
432 break;
433 case 'elementor':
434 if ( ! $is_elementor ) {
435 unset( $this->promotions[ $slug ][ $key ] );
436 }
437 break;
438 case 'edit-post':
439 if ( ! $is_posts ) {
440 unset( $this->promotions[ $slug ][ $key ] );
441 }
442 break;
443 }
444 }
445
446 $return = array_merge( $return, $this->promotions[ $slug ] );
447 }
448
449 return array_keys( $return );
450 }
451
452 /**
453 * Load single promotion.
454 *
455 * @param string $slug slug of the promotion.
456 */
457 private function load_promotion( $slug ) {
458 $this->loaded_promo = $slug;
459
460 if ( $this->debug ) {
461 add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue' ] );
462 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
463 if ( $this->get_upsells_dismiss_time( 'om-media' ) === false ) {
464 add_action( 'admin_notices', [ $this, 'render_optimole_dash_notice' ] );
465 }
466 if ( $this->get_upsells_dismiss_time( 'rop-posts' ) === false ) {
467 add_action( 'admin_notices', [ $this, 'render_rop_dash_notice' ] );
468 }
469
470 return;
471 }
472
473 switch ( $slug ) {
474 case 'om-editor':
475 case 'om-image-block':
476 case 'blocks-css':
477 case 'blocks-animation':
478 case 'blocks-conditions':
479 add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue' ] );
480 break;
481 case 'om-attachment':
482 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
483 break;
484 case 'om-media':
485 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
486 add_action( 'admin_notices', [ $this, 'render_optimole_dash_notice' ] );
487 break;
488 case 'rop-posts':
489 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
490 add_action( 'admin_notices', [ $this, 'render_rop_dash_notice' ] );
491 break;
492 }
493 }
494
495 /**
496 * Render dashboard notice.
497 */
498 public function render_optimole_dash_notice() {
499 $screen = get_current_screen();
500
501 if ( ! isset( $screen->id ) || $screen->id !== 'upload' ) {
502 return;
503 }
504
505 echo '<div id="ti-optml-notice" class="notice notice-info ti-sdk-om-notice"></div>';
506 }
507
508 /**
509 * Enqueue the assets.
510 */
511 public function enqueue() {
512 global $themeisle_sdk_max_path;
513 $handle = 'ti-sdk-promo';
514 $saved = $this->get_upsells_dismiss_time();
515 $themeisle_sdk_src = $this->get_sdk_uri();
516 $user = wp_get_current_user();
517 $asset_file = require $themeisle_sdk_max_path . '/assets/js/build/index.asset.php';
518 $deps = array_merge( $asset_file['dependencies'], [ 'updates' ] );
519
520 wp_register_script( $handle, $themeisle_sdk_src . 'assets/js/build/index.js', $deps, $asset_file['version'], true );
521 wp_localize_script(
522 $handle,
523 'themeisleSDKPromotions',
524 [
525 'debug' => $this->debug,
526 'email' => $user->user_email,
527 'showPromotion' => $this->loaded_promo,
528 'optionKey' => $this->option_main,
529 'product' => $this->product->get_name(),
530 'option' => empty( $saved ) ? new \stdClass() : $saved,
531 'nonce' => wp_create_nonce( 'wp_rest' ),
532 'assets' => $themeisle_sdk_src . 'assets/images/',
533 'optimoleApi' => esc_url( rest_url( 'optml/v1/register_service' ) ),
534 'optimoleActivationUrl' => $this->get_plugin_activation_link( 'optimole-wp' ),
535 'otterActivationUrl' => $this->get_plugin_activation_link( 'otter-blocks' ),
536 'ropActivationUrl' => $this->get_plugin_activation_link( 'tweet-old-post' ),
537 'optimoleDash' => esc_url( add_query_arg( [ 'page' => 'optimole' ], admin_url( 'upload.php' ) ) ),
538 'ropDash' => esc_url( add_query_arg( [ 'page' => 'TweetOldPost' ], admin_url( 'admin.php' ) ) ),
539 // translators: %s is the product name.
540 'title' => esc_html( sprintf( __( 'Recommended by %s', 'textdomain' ), $this->product->get_name() ) ),
541 ]
542 );
543 wp_enqueue_script( $handle );
544 wp_enqueue_style( $handle, $themeisle_sdk_src . 'assets/js/build/style-index.css', [ 'wp-components' ], $asset_file['version'] );
545 }
546
547 /**
548 * Render rop notice.
549 */
550 public function render_rop_dash_notice() {
551 $screen = get_current_screen();
552
553 if ( ! isset( $screen->id ) || $screen->id !== 'edit-post' ) {
554 return;
555 }
556
557 echo '<div id="ti-rop-notice" class="notice notice-info ti-sdk-rop-notice"></div>';
558 }
559
560 /**
561 * Add promo to attachment modal.
562 *
563 * @param array $fields Fields array.
564 * @param \WP_Post $post Post object.
565 *
566 * @return array
567 */
568 public function add_attachment_field( $fields, $post ) {
569 if ( $post->post_type !== 'attachment' ) {
570 return $fields;
571 }
572
573 if ( ! isset( $post->post_mime_type ) || strpos( $post->post_mime_type, 'image' ) === false ) {
574 return $fields;
575 }
576
577 $meta = wp_get_attachment_metadata( $post->ID );
578
579 if ( isset( $meta['filesize'] ) && $meta['filesize'] < 200000 ) {
580 return $fields;
581 }
582
583 $fields['optimole'] = array(
584 'input' => 'html',
585 'html' => '<div id="ti-optml-notice-helper"></div>',
586 'label' => '',
587 );
588
589 if ( count( $fields ) < 2 ) {
590 add_filter( 'wp_required_field_message', '__return_empty_string' );
591 }
592
593 return $fields;
594 }
595
596 /**
597 * Get plugin activation link.
598 *
599 * @param string $slug The plugin slug.
600 *
601 * @return string
602 */
603 private function get_plugin_activation_link( $slug ) {
604 $reference_key = $slug === 'otter-blocks' ? 'reference_key' : 'optimole_reference_key';
605
606 return esc_url(
607 add_query_arg(
608 array(
609 'plugin_status' => 'all',
610 'paged' => '1',
611 'action' => 'activate',
612 $reference_key => $this->product->get_key(),
613 'plugin' => rawurlencode( $slug . '/' . $slug . '.php' ),
614 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $slug . '/' . $slug . '.php' ),
615 ),
616 admin_url( 'plugins.php' )
617 )
618 );
619 }
620
621 /**
622 * Check if has 50 image media items.
623 *
624 * @return bool
625 */
626 private function has_min_media_attachments() {
627 if ( $this->debug ) {
628 return true;
629 }
630 $attachment_count = get_transient( 'tsk_attachment_count' );
631 if ( false === $attachment_count ) {
632 $args = array(
633 'post_type' => 'attachment',
634 'posts_per_page' => 51,
635 'fields' => 'ids',
636 'post_status' => 'inherit',
637 'no_found_rows' => true,
638 );
639
640 $query = new \WP_Query( $args );
641 $attachment_count = $query->post_count;
642
643
644 set_transient( 'tsk_attachment_count', $attachment_count, DAY_IN_SECONDS );
645 }
646 return $attachment_count > 50;
647 }
648
649 /**
650 * Check if the website has more than 100 posts and over 10 are over a year old.
651 *
652 * @return bool
653 */
654 private function has_old_posts() {
655 if ( $this->debug ) {
656 return true;
657 }
658
659 $posts_count = get_transient( 'tsk_posts_count' );
660
661 // Create a new WP_Query object to get all posts
662 $args = array(
663 'post_type' => 'post',
664 'posts_per_page' => 101, //phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
665 'fields' => 'ids',
666 'no_found_rows' => true,
667 );
668
669 if ( false === $posts_count ) {
670 $query = new \WP_Query( $args );
671 $total_posts = $query->post_count;
672 wp_reset_postdata();
673
674 // Count the number of posts older than 1 year
675 $one_year_ago = gmdate( 'Y-m-d H:i:s', strtotime( '-1 year' ) );
676 $args['date_query'] = array(
677 array(
678 'before' => $one_year_ago,
679 'inclusive' => true,
680 ),
681 );
682
683 $query = new \WP_Query( $args );
684 $old_posts = $query->post_count;
685 wp_reset_postdata();
686
687 $posts_count = array(
688 'total_posts' => $total_posts,
689 'old_posts' => $old_posts,
690 );
691
692 set_transient( 'tsk_posts_count', $posts_count, DAY_IN_SECONDS );
693 }
694
695 // Check if there are more than 100 posts and more than 10 old posts
696 return $posts_count['total_posts'] > 100 && $posts_count['old_posts'] > 10;
697 }
698 }
699