PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.7.0.12
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.7.0.12
1.8.13.1 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 All 212 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.12, at includes/admin/notifications/class-charitable-notifications.php

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