PluginProbe
LoginPress | wp-login Custom Login Page Customizer / trunk
LoginPress | wp-login Custom Login Page Customizer vtrunk
6.2.5 6.2.4 6.2.3 6.2.2 6.2.1 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.3 1.0.4 All 118 releases
loginpress / include / class-remote-notification-client.php

class-remote-notification-client.php in LoginPress | wp-login Custom Login Page Customizer trunk, at include/class-remote-notification-client.php

659 lines 17.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * LoginPress Remote Dashboard Notifications Client Class.
4 *
5 * Remote Dashboard Notifications.
6 *
7 * This class is part of the Remote Dashboard Notifications plugin.
8 * This plugin allows you to send notifications to your client's
9 * WordPress dashboard easily.
10 *
11 * Notification you send will be displayed as admin notifications
12 * using the standard WordPress hooks. A "dismiss" option is added
13 * in order to let the user hide the notification.
14 *
15 * @package LoginPress
16 * @author ThemeAvenue <web@themeavenue.net>
17 * @license GPL-2.0+
18 * @link http://themeavenue.net
19 * @link http://wordpress.org/plugins/remote-dashboard-notifications/
20 * @link https://github.com/ThemeAvenue/Remote-Dashboard-Notifications
21 * @copyright 2016 ThemeAvenue
22 */
23
24 // If this file is called directly, abort.
25 if ( ! defined( 'ABSPATH' ) ) {
26 exit;
27 }
28
29 if ( ! class_exists( 'Remote_Dashboard_Notifications_Client' ) ) {
30 include LOGINPRESS_DIR_PATH . 'include/loginpress-remote-notification-trait.php';
31 /**
32 * LoginPress Remote Dashboard Notifications Client Class.
33 *
34 * Handles remote dashboard notifications for LoginPress.
35 *
36 * @package LoginPress
37 * @since 1.3.0
38 */
39 final class Remote_Dashboard_Notifications_Client {
40 use LoginPress_Remote_Notification_Trait;
41
42 /**
43 * Holds the unique instance of the class.
44 *
45 * @var Remote_Dashboard_Notifications_Client
46 * @since 1.3.0
47 */
48 private static $instance;
49
50 /**
51 * Minimum version of WordPress required to run the plugin.
52 *
53 * @since 1.3.0
54 * @var string
55 */
56 public $wordpress_version_required = '3.8';
57
58 /**
59 * Required version of PHP.
60 *
61 * Follow WordPress latest requirements and require
62 * PHP version 5.2 at least.
63 *
64 * @since 1.3.0
65 * @var string
66 */
67 public $php_version_required = '5.2';
68
69 /**
70 * Holds all the registered notifications.
71 *
72 * @since 1.3.0
73 * @var array<string, array<string, mixed>>
74 */
75 public $notifications = array();
76
77 /**
78 * Instantiate and return the unique object.
79 *
80 * @since 1.2.0
81 * @return object Remote_Dashboard_Notifications_Client Unique instance.
82 */
83 public static function instance() {
84
85 if ( null === self::$instance ) {
86 self::$instance = new Remote_Dashboard_Notifications_Client();
87 self::$instance->init();
88 }
89
90 return self::$instance;
91 }
92
93 /**
94 * Instantiate the plugin.
95 *
96 * @since 1.3.0
97 * @return void
98 */
99 private function init() {
100
101 // Make sure the WordPress version is recent enough.
102 if ( ! self::$instance->is_version_compatible() ) {
103 return;
104 }
105
106 // Make sure we have a version of PHP that's not too old.
107 if ( ! self::$instance->is_php_version_enough() ) {
108 return;
109 }
110
111 // Call the dismiss method before testing for Ajax.
112 if ( isset( $_GET['rn'] ) && isset( $_GET['notification'] ) ) {
113 add_action( 'plugins_loaded', array( self::$instance, 'dismiss' ) );
114 }
115
116 if ( ! wp_doing_ajax() ) {
117 add_action( 'admin_print_styles', array( self::$instance, 'style' ), 100 );
118 add_action( 'admin_notices', array( self::$instance, 'show_notices' ) );
119 add_action( 'admin_footer', array( self::$instance, 'script' ) );
120 }
121
122 add_action( 'wp_ajax_rdn_fetch_notifications', array( $this, 'remote_get_notice_ajax' ) );
123 add_filter( 'heartbeat_received', array( self::$instance, 'heartbeat' ), 10, 2 );
124 }
125
126 /**
127 * Throw error on object clone.
128 *
129 * The whole idea of the singleton design pattern is that there is a single
130 * object therefore, we don't want the object to be cloned.
131 *
132 * @since 3.2.5
133 * @version 6.2.0
134 * @return void
135 */
136 public function __clone() {
137 // Cloning instances of the class is forbidden.
138 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'loginpress' ), '3.2.5' );
139 }
140
141 /**
142 * Disable unserializing of the class.
143 *
144 * @since 3.2.5
145 * @version 6.2.0
146 * @return void
147 */
148 public function __wakeup() {
149 // Unserializing instances of the class is forbidden.
150 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'loginpress' ), '3.2.5' );
151 }
152
153 /**
154 * Check if the core version is compatible with this addon.
155 *
156 * @since 1.3.0
157 * @return boolean
158 */
159 private function is_version_compatible() {
160
161 if ( empty( self::$instance->wordpress_version_required ) ) {
162 return true;
163 }
164
165 if ( version_compare( get_bloginfo( 'version' ), self::$instance->wordpress_version_required, '<' ) ) {
166 return false;
167 }
168
169 return true;
170 }
171
172 /**
173 * Check if the version of PHP is compatible with this addon.
174 *
175 * @since 1.3.0
176 * @return boolean
177 */
178 private function is_php_version_enough() {
179
180 /**
181 * No version set, we assume everything is fine.
182 */
183 if ( empty( self::$instance->php_version_required ) ) {
184 return true;
185 }
186
187 if ( version_compare( phpversion(), self::$instance->php_version_required, '<' ) ) {
188 return false;
189 }
190
191 return true;
192 }
193
194 /**
195 * Adds inline style for non standard notices.
196 *
197 * This function will only be called if the notice style is not standard.
198 *
199 * @since 0.1.0
200 * @return void
201 */
202 public function style() {
203 ?>
204 <style type="text/css">div.rn-alert{padding:15px 35px 15px 15px;margin-bottom:20px;border:1px solid transparent;-webkit-box-shadow:none;box-shadow:none}div.rn-alert p:empty{display:none}div.rn-alert ol,div.rn-alert ol li,div.rn-alert ul,div.rn-alert ul li{list-style:inherit!important}div.rn-alert ol,div.rn-alert ul{padding-left:30px}div.rn-alert hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}div.rn-alert h1,div.rn-alert h2,div.rn-alert h3,div.rn-alert h4,div.rn-alert h5,div.rn-alert h6{margin-top:0;color:inherit}div.rn-alert a{font-weight:700}div.rn-alert a:hover{text-decoration:underline}div.rn-alert>p{margin:0;padding:0;line-height:1}div.rn-alert>p,div.rn-alert>ul{margin-bottom:0}div.rn-alert>p+p{margin-top:5px}div.rn-alert .rn-dismiss-btn{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;position:relative;top:-2px;right:-21px;padding:0;cursor:pointer;background:0;border:0;-webkit-appearance:none;float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20);text-decoration:none}div.rn-alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}div.rn-alert-success hr{border-top-color:#c9e2b3}div.rn-alert-success a{color:#2b542c}div.rn-alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}div.rn-alert-info hr{border-top-color:#a6e1ec}div.rn-alert-info a{color:#245269}div.rn-alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}div.rn-alert-warning hr{border-top-color:#f7e1b5}div.rn-alert-warning a{color:#66512c}div.rn-alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}div.rn-alert-danger hr{border-top-color:#e4b9c0}div.rn-alert-danger a{color:#843534}</style>
205 <?php
206 }
207
208 /**
209 * Display all the registered and available notifications.
210 *
211 * @since 1.3.0
212 * @return void
213 */
214 public function show_notices() {
215
216 foreach ( $this->notifications as $id => $notification ) {
217
218 $rn = $this->get_remote_notification( $notification );
219
220 if ( empty( $rn ) || is_wp_error( $rn ) ) {
221 continue;
222 }
223
224 if ( $this->is_notification_error( $rn ) ) {
225 continue;
226 }
227
228 if ( isset( $rn->slug ) && $this->is_notice_dismissed( $rn->slug ) ) {
229 continue;
230 }
231
232 if ( $this->is_post_type_restricted( $rn ) ) {
233 continue;
234 }
235
236 if ( ! $this->is_notification_started( $rn ) ) {
237 continue;
238 }
239
240 if ( $this->has_notification_ended( $rn ) ) {
241 continue;
242 }
243
244 // Output the admin notice.
245 $message = isset( $rn->message ) ? $rn->message : '';
246 $slug = isset( $rn->slug ) ? $rn->slug : '';
247 $this->create_admin_notice( $message, $this->get_notice_class( isset( $rn->style ) ? $rn->style : 'updated' ), $this->get_notice_dismissal_url( $slug ) );
248
249 }
250 }
251
252 /**
253 * Check if the notification has been dismissed.
254 *
255 * @since 1.2.0
256 *
257 * @param string $slug Slug of the notice to check.
258 *
259 * @return bool
260 */
261 protected function is_notice_dismissed( $slug ) {
262
263 global $current_user;
264
265 $dismissed = array_filter( (array) get_user_meta( $current_user->ID, '_rn_dismissed', true ) );
266
267 if ( in_array( $slug, $dismissed ) ) {
268 return true;
269 }
270
271 return false;
272 }
273
274 /**
275 * Check if the notification can be displayed for the current post type.
276 *
277 * @since 1.2.0
278 *
279 * @param object $notification The notification object.
280 *
281 * @return bool
282 */
283 protected function is_post_type_restricted( $notification ) {
284
285 /* If the type array isn't empty we have a limitation. */
286 if ( isset( $notification->type ) && is_array( $notification->type ) && ! empty( $notification->type ) ) {
287
288 /* Get current post type. */
289 $pt = get_post_type();
290
291 /**
292 * If the current post type can't be retrieved
293 * or if it's not in the allowed post types,
294 * then we don't display the admin notice.
295 */
296 if ( false === $pt || ! in_array( $pt, $notification->type ) ) {
297 return true;
298 }
299 }
300
301 return false;
302 }
303
304 /**
305 * Get the admin notice class attribute.
306 *
307 * @since 1.3.0
308 *
309 * @param string $style Notification style
310 *
311 * @return string
312 */
313 protected function get_notice_class( $style ) {
314
315 switch ( $style ) {
316 case 'updated':
317 $class = $style;
318 break;
319
320 case 'error':
321 $class = 'updated error';
322 break;
323
324 default:
325 $class = "updated rn-alert rn-alert-$style";
326 }
327
328 return $class;
329 }
330
331 /**
332 * Prepare the dismissal URL for the notice.
333 *
334 * @since 1.3.0
335 * @version 6.2.0
336 *
337 * @param string $slug Notice slug
338 *
339 * @return string
340 */
341 protected function get_notice_dismissal_url( $slug ) {
342
343 // Building URL from current query args; dismiss action verifies nonce when link is followed.
344 $args = array_map( 'sanitize_text_field', wp_unslash( $_GET ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
345 $args['rn'] = wp_create_nonce( 'rn-dismiss' );
346 $args['notification'] = trim( $slug );
347
348 return esc_url( add_query_arg( $args, '' ) );
349 }
350
351 /**
352 * Create the actual admin notice.
353 *
354 * @since 1.3.0
355 *
356 * @param string $contents Notice contents
357 * @param string $class Wrapper class
358 * @param string $dismiss Dismissal link
359 *
360 * @return void
361 */
362 protected function create_admin_notice( $contents, $class, $dismiss ) {
363 ?>
364 <div class="<?php echo esc_attr( $class ); ?>">
365 <a href="<?php echo esc_url( $dismiss ); ?>" id="rn-dismiss" class="rn-dismiss-btn" title="<?php esc_attr_e( 'Dismiss notification', 'loginpress' ); ?>">&times;</a>
366 <p><?php echo wp_kses_post( html_entity_decode( $contents ) ); ?></p>
367 </div>
368 <?php
369 }
370
371 /**
372 * Dismiss notice
373 *
374 * When the user dismisses a notice, its slug
375 * is added to the _rn_dismissed entry in the DB options table.
376 * This entry is then used to check if a notice has been dismissed
377 * before displaying it on the dashboard.
378 *
379 * @since 0.1.0
380 * @version 6.2.0
381 * @return void
382 */
383 public function dismiss() {
384
385 global $current_user;
386
387 /* Check if we have all the vars. */
388 if ( ! isset( $_GET['rn'] ) || ! isset( $_GET['notification'] ) ) {
389 return;
390 }
391
392 /* Validate nonce. */
393 if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['rn'] ) ), 'rn-dismiss' ) ) {
394 return;
395 }
396
397 $notification_param = sanitize_text_field( wp_unslash( $_GET['notification'] ) );
398
399 /* Get dismissed list. */
400 $dismissed = array_filter( (array) get_user_meta( $current_user->ID, '_rn_dismissed', true ) );
401
402 /* Add the current notice to the list if needed. */
403 if ( ! in_array( $notification_param, $dismissed, true ) ) {
404 $dismissed[] = $notification_param;
405 }
406
407 /* Update option. */
408 update_user_meta( $current_user->ID, '_rn_dismissed', $dismissed );
409 }
410
411 /**
412 * Adds the script that hooks into the Heartbeat API.
413 *
414 * @since 1.3.0
415 * @version 6.2.0
416 * @return void
417 */
418 public function script() {
419
420 $maybe_fetch = array();
421
422 foreach ( $this->get_notifications() as $id => $n ) {
423 $maybe_fetch[] = (string) $id;
424 }
425
426 if ( false === get_transient( 'loginpress_rdn_fetch_notifications' ) ) {
427 $rdn_nonce = wp_create_nonce( 'rdn_fetch_notifications' );
428 ?>
429
430 <script type="text/javascript">
431 jQuery(document).ready(function ($) {
432
433 // Hook into the heartbeat-send.
434 $(document).on('heartbeat-send', function (e, data) {
435 data['rdn_maybe_fetch'] = <?php echo wp_json_encode( $maybe_fetch ); ?>;
436 });
437
438 // Listen for the custom event "heartbeat-tick" on $(document).
439 $(document).on('heartbeat-tick', function (e, data) {
440
441 if (data.rdn_fetch !== '') {
442
443 ajax_data = {
444 'action': 'rdn_fetch_notifications',
445 'nonce': <?php echo wp_json_encode( $rdn_nonce ); ?>,
446 'notices': data.rdn_fetch
447 };
448
449 $.post(ajaxurl, ajax_data);
450
451 }
452
453 });
454 });
455 </script>
456 <?php
457 }
458 }
459
460 /**
461 * Hook into the Heartbeat API.
462 *
463 * @since 1.3.0
464 *
465 * @param array<string, mixed> $response Heartbeat tick response.
466 * @param array<string, mixed> $data Heartbeat tick data.
467 *
468 * @return array<string, mixed> Updated Heartbeat tick response.
469 */
470 function heartbeat( $response, $data ) {
471
472 if ( isset( $data['rdn_maybe_fetch'] ) ) {
473
474 $notices = $data['rdn_maybe_fetch'];
475
476 if ( ! is_array( $notices ) ) {
477 $notices = array( $notices );
478 }
479
480 foreach ( $notices as $notice_id ) {
481
482 $fetch = get_option( "rdn_fetch_$notice_id", false );
483
484 if ( 'fetch' === $fetch ) {
485
486 if ( ! isset( $response['rdn_fetch'] ) ) {
487 $response['rdn_fetch'] = array();
488 }
489
490 $response['rdn_fetch'][] = $notice_id;
491
492 }
493 }
494 }
495
496 return $response;
497 }
498
499 /**
500 * Triggers the remote requests that fetches notices for this particular instance.
501 *
502 * @since 1.3.0
503 * @version 6.2.0
504 * @return void
505 */
506 public function remote_get_notice_ajax() {
507 check_ajax_referer( 'rdn_fetch_notifications', 'nonce' );
508
509 // Transient set for 1 week.
510 set_transient( 'loginpress_rdn_fetch_notifications', 'rdn_fetch_notifications', 604800 );
511
512 if ( isset( $_POST['notices'] ) ) {
513 $notices = sanitize_text_field( wp_unslash( $_POST['notices'] ) );
514 } else {
515 echo 'No notice ID';
516 die();
517 }
518
519 if ( ! is_array( $notices ) ) {
520 $notices = array( $notices );
521 }
522
523 foreach ( $notices as $notice_id ) {
524
525 $notification = $this->get_notification( $notice_id );
526 if ( false === $notification ) {
527 continue;
528 }
529 $rn = $this->remote_get_notification( $notification );
530
531 if ( is_wp_error( $rn ) ) {
532 echo esc_html( $rn->get_error_message() );
533 } else {
534 // Intentional JSON output for AJAX response.
535 echo wp_json_encode( $rn ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
536 }
537 }
538
539 die();
540 }
541
542 /**
543 * Get the remote server URL.
544 *
545 * @since 1.2.0
546 *
547 * @param string $url THe server URL to sanitize.
548 *
549 * @return string
550 */
551 protected function get_remote_url( $url ) {
552
553 $url = explode( '?', $url );
554
555 return esc_url( $url[0] );
556 }
557
558 /**
559 * Get the payload required for querying the remote server.
560 *
561 * @since 1.2.0
562 *
563 * @param array<string, mixed> $notification The notification data array.
564 *
565 * @return string
566 */
567 protected function get_payload( $notification ) {
568 $payload = json_encode(
569 array(
570 'channel' => isset( $notification['channel_id'] ) && ! empty( $notification['channel_id'] ) ? $notification['channel_id'] : '',
571 'key' => isset( $notification['channel_key'] ) && ! empty( $notification['channel_key'] ) ? $notification['channel_key'] : '',
572 )
573 );
574 return base64_encode( $payload ?: '' );
575 }
576
577 /**
578 * Get the full URL used for the remote get.
579 *
580 * @since 1.2.0
581 *
582 * @param string $url The remote server URL
583 * @param string $payload The encoded payload
584 *
585 * @return string
586 */
587 protected function build_query_url( $url, $payload ) {
588 return add_query_arg(
589 array(
590 'post_type' => 'notification',
591 'payload' => $payload,
592 ),
593 $this->get_remote_url( $url )
594 );
595 }
596 }
597
598 }
599
600 /**
601 * The main function responsible for returning the unique RDN client.
602 *
603 * Use this function like you would a global variable, except without needing
604 * to declare the global.
605 *
606 * @since 1.3.0
607 * @return object Remote_Dashboard_Notifications_Client
608 */
609 function RDNC() {
610 return Remote_Dashboard_Notifications_Client::instance();
611 }
612
613 // Get Awesome Support Running
614 RDNC();
615
616 /**
617 * Register a new remote notification.
618 *
619 * Helper function for registering new notifications through the Remote_Dashboard_Notifications_Client class.
620 *
621 * @since 1.3.0
622 *
623 * @param int|false $channel_id Channel ID.
624 * @param string|false $channel_key Channel key.
625 * @param string|false $server Server URL.
626 * @param int $cache Cache lifetime (in hours)
627 *
628 * @return bool|string
629 */
630 function rdnc_add_notification( $channel_id, $channel_key, $server, $cache = 6 ) {
631 /** @phpstan-ignore-next-line */
632 return RDNC()->add_notification( $channel_id, $channel_key, $server, $cache );
633 }
634
635 if ( ! class_exists( 'TAV_Remote_Notification_Client' ) ) {
636
637 /**
638 * Class TAV_Remote_Notification_Client
639 *
640 * This class, even though deprecated, is kept here for backwards compatibility. It is now just a wrapper for the new notification registration method.
641 *
642 * @deprecated @1.3.0
643 */
644 class TAV_Remote_Notification_Client {
645
646 /**
647 * Constructor for backwards compatibility.
648 *
649 * @param int|false $channel_id Channel ID.
650 * @param string|false $channel_key Channel key.
651 * @param string|false $server Server URL.
652 */
653 public function __construct( $channel_id = false, $channel_key = false, $server = false ) {
654 rdnc_add_notification( $channel_id, $channel_key, $server );
655 }
656 }
657
658 }
659