PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.7.0.3
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.7.0.3
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / includes / admin / notifications / class-charitable-notifications.php

class-charitable-notifications.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.7.0.3, at includes/admin/notifications/class-charitable-notifications.php

851 lines 18.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin form model class.
4 *
5 * @package Charitable/Classes/Charitable_Admin_Form
6 * @author David Bisset
7 * @copyright Copyright (c) 2022, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.5.0
10 * @version 1.5.0
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 /**
19 * Notifications.
20 *
21 * @since 1.7.5
22 */
23 class Charitable_Notifications {
24
25 /**
26 * The single instance of this class.
27 *
28 * @var Charitable_Notifications|null
29 */
30 private static $instance = null;
31
32 /**
33 * Source of notifications content.
34 *
35 * @since 1.7.5
36 *
37 * @var string
38 */
39 const SOURCE_URL = 'https://plugin.wpcharitable.com/wp-content/notifications.json';
40
41 /**
42 * Array of license types, that are considered being Elite level.
43 *
44 * @since 1.7.5
45 *
46 * @var array
47 */
48 const LICENSES_ELITE = [ 'agency', 'pro' ];
49
50 /**
51 * Option value.
52 *
53 * @since 1.7.5
54 *
55 * @var bool|array
56 */
57 public $option = false;
58
59 /**
60 * Current license type.
61 *
62 * @since 1.7.5
63 *
64 * @var string
65 */
66 private $license_type;
67
68 /**
69 * Returns and/or create the single instance of this class.
70 *
71 * @since 1.2.0
72 *
73 * @return Charitable_User_Dashboard
74 */
75 public static function get_instance() {
76 if ( is_null( self::$instance ) ) {
77 self::$instance = new self();
78 }
79
80 return self::$instance;
81 }
82
83 public function __construct() {
84
85 $this->init();
86
87 }
88
89 /**
90 * Initialize class.
91 *
92 * @since 1.7.5
93 */
94 public function init() {
95
96 $this->hooks();
97 }
98
99 /**
100 * Register hooks.
101 *
102 * @since 1.7.5
103 */
104 public function hooks() {
105
106 add_action( 'after_charitable_admin_enqueue_scripts', [ $this, 'enqueues' ], 10, 3 );
107
108 // add_action( 'charitable_admin_overview_before_table', [ $this, 'output' ] ); // where the notifications comes out in the admin
109 add_action( 'admin_notices', [ $this, 'output_notices' ], 99 ); // where the notifications comes out in the admin
110 add_action( 'charitable_maybe_show_notification', [ $this, 'output' ], 99 ); // where the notifications comes out in the admin
111 /*
112 function my_cpt_info() {
113 if ('edit-campaign' === get_current_screen()->id) {
114 ?>
115 <div class="updated">
116 <p>
117 <?php _e('Some information about my CPT...', 'my-text-domain'); ?>
118 </p>
119 </div>
120 <?php
121 }
122 } // function my_cpt_info
123 add_action('admin_notices', 'my_cpt_info');
124 */
125
126 add_action( 'charitable_admin_notifications_update', [ $this, 'update' ] );
127
128 add_action( 'deactivate_plugin', [ $this, 'delete' ], 10, 2 );
129
130 add_action( 'wp_ajax_charitable_notification_dismiss', [ $this, 'dismiss' ] );
131 }
132
133 /**
134 * Check if user has access and is enabled.
135 *
136 * @since 1.7.5
137 *
138 * @return bool
139 */
140 public function has_access() {
141
142 $access = charitable_current_user_can( 'administrator' ) && ! charitable_get_option( 'hide-announcements' );
143
144 /**
145 * Allow modifying state if a user has access.
146 *
147 * @since 1.7.0.3
148 *
149 * @param bool $access True if user has access.
150 */
151 return (bool) apply_filters( 'charitable_admin_notifications_has_access', $access );
152 }
153
154 /**
155 * Get option value.
156 *
157 * @since 1.7.5
158 *
159 * @param bool $cache Reference property cache if available.
160 *
161 * @return array
162 */
163 public function get_option( $cache = true ) {
164
165 if ( $this->option && $cache ) {
166 return $this->option;
167 }
168
169 $option = (array) get_option( 'charitable_notifications', [] );
170
171 $this->option = [
172 'update' => ! empty( $option['update'] ) ? (int) $option['update'] : 0,
173 'feed' => ! empty( $option['feed'] ) ? (array) $option['feed'] : [],
174 'events' => ! empty( $option['events'] ) ? (array) $option['events'] : [],
175 'dismissed' => ! empty( $option['dismissed'] ) ? (array) $option['dismissed'] : [],
176 ];
177
178 return $this->option;
179 }
180
181 /**
182 * Fetch notifications from feed.
183 *
184 * @since 1.7.5
185 *
186 * @return array
187 */
188 public function fetch_feed() {
189
190 $response = wp_remote_get(
191 self::SOURCE_URL,
192 [
193 'timeout' => 10,
194 'user-agent' => charitable_get_default_user_agent(),
195 ]
196 );
197
198 if ( is_wp_error( $response ) ) {
199 return [];
200 }
201
202 $body = wp_remote_retrieve_body( $response );
203
204 if ( empty( $body ) ) {
205 return [];
206 }
207
208 return $this->verify( json_decode( $body, true ) );
209 }
210
211 /**
212 * Verify notification data before it is saved.
213 *
214 * @since 1.7.5
215 *
216 * @param array $notifications Array of notifications items to verify.
217 *
218 * @return array
219 */
220 public function verify( $notifications ) {
221
222 $data = [];
223
224 if ( ! is_array( $notifications ) || empty( $notifications ) ) {
225 return $data;
226 }
227
228 foreach ( $notifications as $notification ) {
229
230 // Ignore if one of the conditional checks is true:
231 //
232 // 1. notification message is empty.
233 // 2. license type does not match.
234 // 3. notification is expired.
235 // 4. notification has already been dismissed.
236 // 5. notification existed before installing WPCharitable.
237 // (Prevents bombarding the user with notifications after activation).
238 if (
239 empty( $notification['content'] ) ||
240 ! $this->is_license_type_match( $notification ) ||
241 $this->is_expired( $notification ) ||
242 $this->is_dismissed( $notification ) ||
243 $this->is_existed( $notification )
244 ) {
245 // echo 'false';
246 continue;
247 }
248
249 $data[] = $notification;
250 }
251
252 // print_r( $data ); exit;
253
254 return $data;
255 }
256
257 /**
258 * Verify saved notification data for active notifications.
259 *
260 * @since 1.7.5
261 *
262 * @param array $notifications Array of notifications items to verify.
263 *
264 * @return array
265 */
266 public function verify_active( $notifications ) {
267
268 if ( ! is_array( $notifications ) || empty( $notifications ) ) {
269 return [];
270 }
271
272 $current_timestamp = time();
273
274 // Remove notifications that are not active.
275 foreach ( $notifications as $key => $notification ) {
276 if (
277 ( ! empty( $notification['start'] ) && $current_timestamp < strtotime( $notification['start'] ) ) ||
278 ( ! empty( $notification['end'] ) && $current_timestamp > strtotime( $notification['end'] ) )
279 ) {
280 unset( $notifications[ $key ] );
281 }
282 }
283
284 return $notifications;
285 }
286
287 /**
288 * Get notification data.
289 *
290 * @since 1.7.5
291 *
292 * @return array
293 */
294 public function get() {
295
296 if ( ! $this->has_access() ) {
297 return [];
298 }
299
300 $option = $this->get_option();
301
302 // Update notifications using async task.
303 if ( empty( $option['update'] ) || time() > $option['update'] + DAY_IN_SECONDS ) {
304
305 $this->update();
306
307 // $tasks = charitable()->get( 'tasks' );
308
309 // if ( ! $tasks->is_scheduled( 'charitable_admin_notifications_update' ) !== false ) {
310 // $tasks
311 // ->create( 'charitable_admin_notifications_update' )
312 // ->async()
313 // ->params()
314 // ->register();
315 // }
316 }
317
318 $feed = ! empty( $option['feed'] ) ? $this->verify_active( $option['feed'] ) : [];
319 $events = ! empty( $option['events'] ) ? $this->verify_active( $option['events'] ) : [];
320
321 return array_merge( $feed, $events );
322 }
323
324 /**
325 * Get notification count.
326 *
327 * @since 1.7.5
328 *
329 * @return int
330 */
331 public function get_count() {
332
333 return count( $this->get() );
334 }
335
336 /**
337 * Add a new Event Driven notification.
338 *
339 * @since 1.7.5
340 *
341 * @param array $notification Notification data.
342 */
343 public function add( $notification ) {
344
345 if ( ! $this->is_valid( $notification ) ) {
346 return;
347 }
348
349 $option = $this->get_option();
350
351 // Notification ID already exists.
352 if ( ! empty( $option['events'][ $notification['id'] ] ) ) {
353 return;
354 }
355
356 update_option(
357 'charitable_notifications',
358 [
359 'update' => $option['update'],
360 'feed' => $option['feed'],
361 'events' => array_merge( $notification, $option['events'] ),
362 'dismissed' => $option['dismissed'],
363 ]
364 );
365 }
366
367 /**
368 * Determine if notification data is valid.
369 *
370 * @since 1.7.5
371 *
372 * @param array $notification Notification data.
373 *
374 * @return bool
375 */
376 public function is_valid( $notification ) {
377
378 if ( empty( $notification['id'] ) ) {
379 return false;
380 }
381
382 return ! empty( $this->verify( [ $notification ] ) );
383 }
384
385 /**
386 * Determine if notification has already been dismissed.
387 *
388 * @since 1.7.5
389 *
390 * @param array $notification Notification data.
391 *
392 * @return bool
393 */
394 private function is_dismissed( $notification ) {
395
396 $option = $this->get_option();
397
398 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
399 return ! empty( $option['dismissed'] ) && in_array( $notification['id'], $option['dismissed'] );
400 }
401
402 /**
403 * Determine if license type is match.
404 *
405 * @since 1.7.5
406 *
407 * @param array $notification Notification data.
408 *
409 * @return bool
410 */
411 private function is_license_type_match( $notification ) {
412
413 // A specific license type is not required.
414 if ( empty( $notification['type'] ) ) {
415 return true;
416 }
417
418 return in_array( $this->get_license_type(), (array) $notification['type'], true );
419 }
420
421 /**
422 * Determine if notification is expired.
423 *
424 * @since 1.7.5
425 *
426 * @param array $notification Notification data.
427 *
428 * @return bool
429 */
430 private function is_expired( $notification ) {
431
432 return ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] );
433 }
434
435 /**
436 * Determine if notification existed before installing WPForms.
437 *
438 * @since 1.7.5
439 *
440 * @param array $notification Notification data.
441 *
442 * @return bool
443 */
444 private function is_existed( $notification ) {
445
446 $activated = charitable_get_activated_timestamp();
447
448 return ! empty( $activated ) &&
449 ! empty( $notification['start'] ) &&
450 $activated > strtotime( $notification['start'] );
451 }
452
453 /**
454 * Update notification data from feed.
455 *
456 * @since 1.7.5
457 */
458 public function update() {
459
460 $option = $this->get_option();
461 $data = [
462 'update' => time(),
463 'feed' => $this->fetch_feed(),
464 'events' => $option['events'],
465 'dismissed' => $option['dismissed'],
466 ];
467
468 // echo 'data:'; print_r( $data ); exit;
469
470 // phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
471 /**
472 * Allow changing notification data before it will be updated in database.
473 *
474 * @since 1.7.5
475 *
476 * @param array $data New notification data.
477 */
478 $data = (array) apply_filters( 'charitable_admin_notifications_update_data', $data );
479 // phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
480
481 update_option( 'charitable_notifications', $data );
482 }
483
484 /**
485 * Remove notification data from database before a plugin is deactivated.
486 *
487 * @since 1.7.5
488 *
489 * @param string $plugin Path to the plugin file relative to the plugins directory.
490 * @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network
491 * or just the current site. Multisite only. Default false.
492 */
493 public function delete( $plugin, $network_deactivating ) {
494
495 $charitable_plugins = [
496 'charitable-lite/charitable.php',
497 'charitable/charitable.php',
498 ];
499
500 if ( ! in_array( $plugin, $charitable_plugins, true ) ) {
501 return;
502 }
503
504 delete_option( 'charitable_notifications' );
505 }
506
507 /**
508 * Enqueue assets on Form Overview admin page.
509 *
510 * @since 1.7.5
511 */
512 public function enqueues( $suffix, $version, $assets_dir ) {
513
514 if ( ! $this->get_count() ) {
515 return;
516 }
517
518 $suffix = false;
519
520 wp_register_style(
521 'charitable-admin-notifications',
522 $assets_dir . 'css/charitable-admin-notifications' . $suffix . '.css',
523 array( 'charitable-lity' ),
524 $version
525 );
526
527 wp_enqueue_style( 'charitable-admin-notifications' );
528
529 wp_register_script(
530 'charitable-admin-notifications',
531 $assets_dir . 'js/charitable-admin-notifications' . $suffix . '.js',
532 array( 'jquery', 'charitable-lity' ),
533 $version,
534 true
535 );
536
537 wp_enqueue_script( 'charitable-admin-notifications' );
538
539 // Lity.
540 wp_enqueue_style(
541 'charitable-lity',
542 $assets_dir . 'lib/lity/lity.min.css',
543 [],
544 $version
545 );
546
547 wp_enqueue_script(
548 'charitable-lity',
549 $assets_dir . 'lib/lity/lity.min.js',
550 [ 'jquery' ],
551 $version,
552 true
553 );
554
555 }
556
557 public function output_notices() {
558 return $this->output( 'notices' );
559 }
560
561 /**
562 * Output notifications on Form Overview admin area.
563 *
564 * @since 1.7.5
565 */
566 public function output( $location = false ) {
567
568 global $post;
569
570 if ('notices' === $location && ! $this->show_notifications() ) {
571 return;
572 }
573
574 $notifications = $this->get();
575
576 // print_r( $notifications ); exit;
577
578 if ( empty( $notifications ) ) {
579 return;
580 }
581
582 $notifications_html = '';
583 $current_class = ' current';
584 $content_allowed_tags = [
585 'em' => [],
586 'strong' => [],
587 'span' => [
588 'style' => [],
589 ],
590 'a' => [
591 'href' => [],
592 'target' => [],
593 'rel' => [],
594 ],
595 ];
596
597 foreach ( $notifications as $notification ) {
598
599 // Prepare required arguments.
600 $notification = wp_parse_args(
601 $notification,
602 [
603 'id' => 0,
604 'title' => '',
605 'content' => '',
606 'video' => '',
607 ]
608 );
609
610 $title = $this->get_component_data( $notification['title'] );
611 $content = $this->get_component_data( $notification['content'] );
612
613 if ( ! $title && ! $content ) {
614 continue;
615 }
616
617 // Notification HTML.
618 $notifications_html .= sprintf(
619 '<div class="charitable-notifications-message%5$s" data-message-id="%4$s">
620 <h3 class="charitable-notifications-title">%1$s%6$s</h3>
621 <p class="charitable-notifications-content">%2$s</p>
622 %3$s
623 </div>',
624 esc_html( $title ),
625 wp_kses( $content, $content_allowed_tags ),
626 $this->get_notification_buttons_html( $notification ),
627 esc_attr( $notification['id'] ),
628 esc_attr( $current_class ),
629 $this->get_video_badge_html( $this->get_component_data( $notification['video'] ) )
630 );
631
632 // Only first notification is current.
633 $current_class = '';
634 }
635
636 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
637 echo charitable_render(
638 '/admin/templates/notifications',
639 [
640 'notifications' => [
641 'count' => count( $notifications ),
642 'html' => $notifications_html,
643 ],
644 ],
645 true
646 );
647
648 }
649
650 /**
651 * Retrieve notification's buttons HTML.
652 *
653 * @since 1.7.5
654 *
655 * @param array $notification Notification data.
656 *
657 * @return string
658 */
659 private function get_notification_buttons_html( $notification ) {
660
661 $html = '';
662
663 if ( empty( $notification['btns'] ) || ! is_array( $notification['btns'] ) ) {
664 return $html;
665 }
666
667 foreach ( $notification['btns'] as $btn_type => $btn ) {
668
669 $btn = $this->get_component_data( $btn );
670
671 if ( ! $btn ) {
672 continue;
673 }
674
675 $url = $this->prepare_btn_url( $btn );
676 $target = ! empty( $btn['target'] ) ? $btn['target'] : '_blank';
677 $target = ! empty( $url ) && strpos( $url, home_url() ) === 0 ? '_self' : $target;
678
679 $html .= sprintf(
680 '<a href="%1$s" class="button button-%2$s"%3$s>%4$s</a>',
681 esc_url( $url ),
682 $btn_type === 'main' ? 'primary' : 'secondary',
683 $target === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '',
684 ! empty( $btn['text'] ) ? esc_html( $btn['text'] ) : ''
685 );
686 }
687
688 return ! empty( $html ) ? sprintf( '<div class="charitable-notifications-buttons">%s</div>', $html ) : '';
689 }
690
691 /**
692 * Retrieve notification's component data by a license type.
693 *
694 * @since 1.7.5
695 *
696 * @param mixed $data Component data.
697 *
698 * @return false|mixed
699 */
700 private function get_component_data( $data ) {
701
702 if ( empty( $data['license'] ) ) {
703 return $data;
704 }
705
706 $license_type = $this->get_license_type();
707
708 if ( in_array( $license_type, self::LICENSES_ELITE, true ) ) {
709 $license_type = 'elite';
710 }
711
712 return ! empty( $data['license'][ $license_type ] ) ? $data['license'][ $license_type ] : false;
713 }
714
715 /**
716 * Retrieve the current installation license type (always lowercase).
717 *
718 * @since 1.7.5
719 *
720 * @return string
721 */
722 private function get_license_type() {
723
724 if ( $this->license_type ) {
725 return $this->license_type;
726 }
727
728 if ( charitable_is_pro() ) {
729 return 'pro';
730 } else {
731 return 'lite';
732 }
733
734 // $this->license_type = charitable_get_license_type();
735
736 // if ( ! $this->license_type ) {
737 // $this->license_type = 'lite';
738 // }
739
740 // return $this->license_type;
741 }
742
743 /**
744 * Dismiss notification via AJAX.
745 *
746 * @since 1.7.5
747 */
748 public function dismiss() {
749
750 // Check for required param, security and access.
751 if (
752 empty( $_POST['id'] ) ||
753 ! check_ajax_referer( 'charitable-admin', 'nonce', false ) ||
754 ! $this->has_access()
755 ) {
756 wp_send_json_error();
757 }
758
759 $id = sanitize_key( $_POST['id'] );
760 $type = is_numeric( $id ) ? 'feed' : 'events';
761 $option = $this->get_option();
762
763 $option['dismissed'][] = $id;
764 $option['dismissed'] = array_unique( $option['dismissed'] );
765
766 // Remove notification.
767 if ( is_array( $option[ $type ] ) && ! empty( $option[ $type ] ) ) {
768 foreach ( $option[ $type ] as $key => $notification ) {
769 if ( (string) $notification['id'] === (string) $id ) {
770 unset( $option[ $type ][ $key ] );
771
772 break;
773 }
774 }
775 }
776
777 update_option( 'charitable_notifications', $option );
778
779 wp_send_json_success();
780 }
781
782 /**
783 * Prepare button URL.
784 *
785 * @since 1.7.5
786 *
787 * @param array $btn Button data.
788 *
789 * @return string
790 */
791 private function prepare_btn_url( $btn ) {
792
793 if ( empty( $btn['url'] ) ) {
794 return '';
795 }
796
797 $replace_tags = [
798 '{admin_url}' => admin_url(),
799 ];
800
801 return str_replace( array_keys( $replace_tags ), array_values( $replace_tags ), $btn['url'] );
802 }
803
804 /**
805 * Get the notification's video badge HTML.
806 *
807 * @since 1.7.5
808 *
809 * @param string $video_url Valid video URL.
810 *
811 * @return string
812 */
813 private function get_video_badge_html( $video_url ) {
814
815 $video_url = wp_http_validate_url( $video_url );
816
817 if ( empty( $video_url ) ) {
818 return '';
819 }
820
821 $data_attr_lity = wp_is_mobile() ? '' : 'data-lity';
822
823 return sprintf(
824 '<a class="charitable-notifications-badge" href="%1$s" %2$s><i class="fa fa-youtube-play" aria-hidden="true"></i>%3$s</a>',
825 esc_url( $video_url ),
826 esc_attr( $data_attr_lity ),
827 esc_html__( 'Watch Video', 'charitable-lite' )
828 );
829 }
830
831 /**
832 * Determine to show notifications on this page or not.
833 *
834 * @since 1.7.0.3
835 *
836 * @return boolean
837 */
838 public function show_notifications() {
839 if ( isset( $_GET['taxonomy'] ) ) {
840 return false;
841 }
842 if ( isset( $_GET['post_type'] ) && $_GET['post_type'] == 'campaign' ) {
843 return true;
844 }
845 if ( isset( $_GET['post_type'] ) && $_GET['post_type'] == 'donation' ) {
846 return true;
847 }
848 return false;
849 }
850 }
851