Issues (24)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/class-remote-notification-client.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Remote Dashboard Notifications.
4
 *
5
 * This class is part of the Remote Dashboard Notifications plugin.
6
 * This plugin allows you to send notifications to your client's
7
 * WordPress dashboard easily.
8
 *
9
 * Notification you send will be displayed as admin notifications
10
 * using the standard WordPress hooks. A "dismiss" option is added
11
 * in order to let the user hide the notification.
12
 *
13
 * @package   Remote Dashboard Notifications
14
 * @author    ThemeAvenue <[email protected]>
15
 * @license   GPL-2.0+
16
 * @link      http://themeavenue.net
17
 * @link      http://wordpress.org/plugins/remote-dashboard-notifications/
18
 * @link 	  https://github.com/ThemeAvenue/Remote-Dashboard-Notifications
19
 * @copyright 2016 ThemeAvenue
20
 */
21
22
// If this file is called directly, abort.
23
if ( ! defined( 'WPINC' ) ) {
24
	die;
25
}
26
27
if ( ! class_exists( 'Remote_Dashboard_Notifications_Client' ) ) {
28
29
	final class Remote_Dashboard_Notifications_Client {
30
31
		/**
32
		 * @var Remote_Dashboard_Notifications_Client Holds the unique instance
33
		 * @since 1.3.0
34
		 */
35
		private static $instance;
36
37
		/**
38
		 * Minimum version of WordPress required ot run the plugin
39
		 *
40
		 * @since 1.3.0
41
		 * @var string
42
		 */
43
		public $wordpress_version_required = '3.8';
44
45
		/**
46
		 * Required version of PHP.
47
		 *
48
		 * Follow WordPress latest requirements and require
49
		 * PHP version 5.2 at least.
50
		 *
51
		 * @since 1.3.0
52
		 * @var string
53
		 */
54
		public $php_version_required = '5.2';
55
56
		/**
57
		 * Holds all the registered notifications
58
		 *
59
		 * @since 1.3.0
60
		 * @var array
61
		 */
62
		public $notifications = array();
63
64
		/**
65
		 * Instantiate and return the unique object
66
		 *
67
		 * @since     1.2.0
68
		 * @return object Remote_Dashboard_Notifications_Client Unique instance
69
		 */
70
		public static function instance() {
71
72
			if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Awesome_Support ) ) {
73
				self::$instance = new Remote_Dashboard_Notifications_Client;
74
				self::$instance->init();
75
			}
76
77
			return self::$instance;
78
79
		}
80
81
		/**
82
		 * Instantiate the plugin
83
		 *
84
		 * @since 1.3.0
85
		 * @return void
86
		 */
87
		private function init() {
88
89
			// Make sure the WordPress version is recent enough
90
			if ( ! self::$instance->is_version_compatible() ) {
91
				return;
92
			}
93
94
			// Make sure we have a version of PHP that's not too old
95
			if ( ! self::$instance->is_php_version_enough() ) {
96
				return;
97
			}
98
99
			// Call the dismiss method before testing for Ajax
100
			if ( isset( $_GET['rn'] ) && isset( $_GET['notification'] ) ) {
101
				add_action( 'plugins_loaded', array( self::$instance, 'dismiss' ) );
102
			}
103
104
			if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) {
105
				add_action( 'admin_print_styles', array( self::$instance, 'style' ), 100 );
106
				add_action( 'admin_notices', array( self::$instance, 'show_notices' ) );
107
				add_action( 'admin_footer', array( self::$instance, 'script' ) );
108
			}
109
110
			add_action( 'wp_ajax_rdn_fetch_notifications', array( $this, 'remote_get_notice_ajax' ) );
111
			add_filter( 'heartbeat_received', array( self::$instance, 'heartbeat' ), 10, 2 );
112
113
		}
114
115
		/**
116
		 * Throw error on object clone
117
		 *
118
		 * The whole idea of the singleton design pattern is that there is a single
119
		 * object therefore, we don't want the object to be cloned.
120
		 *
121
		 * @since 3.2.5
122
		 * @return void
123
		 */
124
		public function __clone() {
125
			// Cloning instances of the class is forbidden
126
			_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'awesome-support' ), '3.2.5' );
127
		}
128
129
		/**
130
		 * Disable unserializing of the class
131
		 *
132
		 * @since 3.2.5
133
		 * @return void
134
		 */
135
		public function __wakeup() {
136
			// Unserializing instances of the class is forbidden
137
			_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'awesome-support' ), '3.2.5' );
138
		}
139
140
		/**
141
		 * Check if the core version is compatible with this addon.
142
		 *
143
		 * @since  1.3.0
144
		 * @return boolean
145
		 */
146 View Code Duplication
		private function is_version_compatible() {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147
148
			if ( empty( self::$instance->wordpress_version_required ) ) {
149
				return true;
150
			}
151
152
			if ( version_compare( get_bloginfo( 'version' ), self::$instance->wordpress_version_required, '<' ) ) {
153
				return false;
154
			}
155
156
			return true;
157
158
		}
159
160
		/**
161
		 * Check if the version of PHP is compatible with this addon.
162
		 *
163
		 * @since  1.3.0
164
		 * @return boolean
165
		 */
166 View Code Duplication
		private function is_php_version_enough() {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
167
168
			/**
169
			 * No version set, we assume everything is fine.
170
			 */
171
			if ( empty( self::$instance->php_version_required ) ) {
172
				return true;
173
			}
174
175
			if ( version_compare( phpversion(), self::$instance->php_version_required, '<' ) ) {
176
				return false;
177
			}
178
179
			return true;
180
181
		}
182
183
		/**
184
		 * Register a new remote notification
185
		 *
186
		 * @since 1.3.0
187
		 *
188
		 * @param int    $channel_id  Channel ID on the remote server
189
		 * @param string $channel_key Channel key for authentication with the server
190
		 * @param string $server      Notification server URL
191
		 * @param int    $cache       Cache lifetime (in hours)
192
		 *
193
		 * @return bool|string
194
		 */
195
		public function add_notification( $channel_id, $channel_key, $server, $cache = 6 ) {
196
197
			$notification = array(
198
				'channel_id'     => (int) $channel_id,
199
				'channel_key'    => $channel_key,
200
				'server_url'     => esc_url( $server ),
201
				'cache_lifetime' => apply_filters( 'rn_notice_caching_time', $cache ),
202
			);
203
204
			// Generate the notice unique ID
205
			$notification['notice_id'] = $notification['channel_id'] . substr( $channel_key, 0, 5 );
206
207
			// Double check that the required info is here
208
			if ( '' === ( $notification['channel_id'] || $notification['channel_key'] || $notification['server_url'] ) ) {
209
				return false;
210
			}
211
212
			// Check that there is no notification with the same ID
213
			if ( array_key_exists( $notification['notice_id'], $this->notifications ) ) {
214
				return false;
215
			}
216
217
			$this->notifications[ $notification['notice_id'] ] = $notification;
218
219
			return $notification['notice_id'];
220
221
		}
222
223
		/**
224
		 * Remove a registered notification
225
		 *
226
		 * @since 1.3.0
227
		 *
228
		 * @param string $notice_id ID of the notice to remove
229
		 *
230
		 * @return void
231
		 */
232
		public function remove_notification( $notice_id ) {
233
			if ( array_key_exists( $notice_id, $this->notifications ) ) {
234
				unset( $this->notifications[ $notice_id ] );
235
			}
236
		}
237
238
		/**
239
		 * Get all registered notifications
240
		 *
241
		 * @since 1.3.0
242
		 * @return array
243
		 */
244
		public function get_notifications() {
245
			return $this->notifications;
246
		}
247
248
		/**
249
		 * Get a specific notification
250
		 *
251
		 * @since 1.3.0
252
		 *
253
		 * @param string $notice_id ID of the notice to retrieve
254
		 *
255
		 * @return bool|array
256
		 */
257
		public function get_notification( $notice_id ) {
258
259
			if ( ! array_key_exists( $notice_id, $this->notifications ) ) {
260
				return false;
261
			}
262
263
			return $this->notifications[ $notice_id ];
264
		}
265
266
		/**
267
		 * Adds inline style for non standard notices
268
		 *
269
		 * This function will only be called if the notice style is not standard.
270
		 *
271
		 * @since 0.1.0
272
		 */
273
		public function style() { ?>
274
			<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,h2,h3,h4,h5,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>
275
		<?php }
276
277
		/**
278
		 * Display all the registered and available notifications
279
		 *
280
		 * @since 1.3.0
281
		 * @return void
282
		 */
283
		public function show_notices() {
284
285
			foreach ( $this->notifications as $id => $notification ) {
286
287
				$rn = $this->get_remote_notification( $notification );
288
289
				if ( empty( $rn ) || is_wp_error( $rn ) ) {
290
					continue;
291
				}
292
293
				if ( $this->is_notification_error( $rn ) ) {
294
					continue;
295
				}
296
297
				if ( $this->is_notice_dismissed( $rn->slug ) ) {
298
					continue;
299
				}
300
301
				if ( $this->is_post_type_restricted( $rn ) ) {
302
					continue;
303
				}
304
305
				if ( ! $this->is_notification_started( $rn ) ) {
306
					continue;
307
				}
308
309
				if ( $this->has_notification_ended( $rn ) ) {
310
					continue;
311
				}
312
313
				// Output the admin notice
314
				$this->create_admin_notice( $rn->message, $this->get_notice_class( isset( $rn->style ) ? $rn->style : 'updated' ), $this->get_notice_dismissal_url( $rn->slug ) );
315
316
			}
317
318
		}
319
320
		/**
321
		 * Check if the notification has been dismissed
322
		 *
323
		 * @since 1.2.0
324
		 *
325
		 * @param string $slug Slug of the notice to check
326
		 *
327
		 * @return bool
328
		 */
329
		protected function is_notice_dismissed( $slug ) {
330
331
			global $current_user;
332
333
			$dismissed = array_filter( (array) get_user_meta( $current_user->ID, '_rn_dismissed', true ) );
334
335
			if ( is_array( $dismissed ) && in_array( $slug, $dismissed ) ) {
336
				return true;
337
			}
338
339
			return false;
340
341
		}
342
343
		/**
344
		 * Check if the notification can be displayed for the current post type
345
		 *
346
		 * @since 1.2.0
347
		 *
348
		 * @param stdClass $notification The notification object
349
		 *
350
		 * @return bool
351
		 */
352
		protected function is_post_type_restricted( $notification ) {
353
354
			/* If the type array isn't empty we have a limitation */
355
			if ( isset( $notification->type ) && is_array( $notification->type ) && ! empty( $notification->type ) ) {
356
357
				/* Get current post type */
358
				$pt = get_post_type();
359
360
				/**
361
				 * If the current post type can't be retrieved
362
				 * or if it's not in the allowed post types,
363
				 * then we don't display the admin notice.
364
				 */
365
				if ( false === $pt || ! in_array( $pt, $notification->type ) ) {
366
					return true;
367
				}
368
369
			}
370
371
			return false;
372
373
		}
374
375
		/**
376
		 * Check if the notification has started yet
377
		 *
378
		 * @since 1.2.0
379
		 *
380
		 * @param stdClass $notification The notification object
381
		 *
382
		 * @return bool
383
		 */
384 View Code Duplication
		protected function is_notification_started( $notification ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
385
386
			if ( ! isset( $notification->date_start ) ) {
387
				return true;
388
			}
389
390
			if ( empty( $notification->date_start ) || strtotime( $notification->date_start ) < time() ) {
391
					return true;
392
			}
393
394
			return false;
395
396
		}
397
398
		/**
399
		 * Check if the notification has expired
400
		 *
401
		 * @since 1.2.0
402
		 *
403
		 * @param stdClass $notification The notification object
404
		 *
405
		 * @return bool
406
		 */
407 View Code Duplication
		protected function has_notification_ended( $notification ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
408
409
			if ( ! isset( $notification->date_end ) ) {
410
				return false;
411
			}
412
			
413
			if ( empty( $notification->date_end ) || strtotime( $notification->date_end ) > time() ) {
414
				return false;
415
			}
416
417
			return true;
418
419
		}
420
421
		/**
422
		 * Get the remote notification object
423
		 *
424
		 * @since 1.3.0
425
		 *
426
		 * @param array $notification The notification data array
427
		 *
428
		 * @return object|false
429
		 */
430
		protected function get_remote_notification( $notification ) {
431
432
			$content = get_transient( 'rn_last_notification_' . $notification['notice_id'] );
433
434
			if ( false === $content ) {
435
				add_option( 'rdn_fetch_' . $notification['notice_id'], 'fetch' );
436
			}
437
438
			return $content;
439
440
		}
441
442
		/**
443
		 * Get the admin notice class attribute
444
		 *
445
		 * @since 1.3.0
446
		 *
447
		 * @param string $style Notification style
448
		 *
449
		 * @return string
450
		 */
451
		protected function get_notice_class( $style ) {
452
453
			switch ( $style ) {
454
				case 'updated':
455
					$class = $style;
456
					break;
457
458
				case 'error':
459
					$class = 'updated error';
460
					break;
461
462
				default:
463
					$class = "updated rn-alert rn-alert-$style";
464
			}
465
466
			return $class;
467
468
		}
469
470
		/**
471
		 * Prepare the dismissal URL for the notice
472
		 *
473
		 * @since 1.3.0
474
		 *
475
		 * @param string $slug Notice slug
476
		 *
477
		 * @return string
478
		 */
479
		protected function get_notice_dismissal_url( $slug ) {
480
481
			$args                 = $_GET;
482
			$args['rn']           = wp_create_nonce( 'rn-dismiss' );
483
			$args['notification'] = trim( $slug );
484
485
			return esc_url( add_query_arg( $args, '' ) );
486
487
		}
488
489
		/**
490
		 * Create the actual admin notice
491
		 *
492
		 * @since 1.3.0
493
		 *
494
		 * @param string $contents Notice contents
495
		 * @param string $class    Wrapper class
496
		 * @param string $dismiss  Dismissal link
497
		 *
498
		 * @return void
499
		 */
500
		protected function create_admin_notice( $contents, $class, $dismiss ) { ?>
501
			<div class="<?php echo $class; ?>">
502
				<a href="<?php echo $dismiss; ?>" id="rn-dismiss" class="rn-dismiss-btn" title="<?php _e( 'Dismiss notification', 'remote-notifications' ); ?>">&times;</a>
503
				<p><?php echo html_entity_decode( $contents ); ?></p>
504
			</div>
505
		<?php }
506
507
		/**
508
		 * Dismiss notice
509
		 *
510
		 * When the user dismisses a notice, its slug
511
		 * is added to the _rn_dismissed entry in the DB options table.
512
		 * This entry is then used to check if a notie has been dismissed
513
		 * before displaying it on the dashboard.
514
		 *
515
		 * @since 0.1.0
516
		 */
517
		public function dismiss() {
518
519
			global $current_user;
520
521
			/* Check if we have all the vars */
522
			if ( ! isset( $_GET['rn'] ) || ! isset( $_GET['notification'] ) ) {
523
				return;
524
			}
525
526
			/* Validate nonce */
527
			if ( ! wp_verify_nonce( sanitize_key( $_GET['rn'] ), 'rn-dismiss' ) ) {
528
				return;
529
			}
530
531
			/* Get dismissed list */
532
			$dismissed = array_filter( (array) get_user_meta( $current_user->ID, '_rn_dismissed', true ) );
533
534
			/* Add the current notice to the list if needed */
535
			if ( is_array( $dismissed ) && ! in_array( $_GET['notification'], $dismissed ) ) {
536
				array_push( $dismissed, $_GET['notification'] );
537
			}
538
539
			/* Update option */
540
			update_user_meta( $current_user->ID, '_rn_dismissed', $dismissed );
541
542
		}
543
544
		/**
545
		 * Adds the script that hooks into the Heartbeat API
546
		 *
547
		 * @since 1.3.0
548
		 * @return void
549
		 */
550
		public function script() {
551
552
			$maybe_fetch = array();
553
554
			foreach ( $this->get_notifications() as $id => $n ) {
555
				$maybe_fetch[] = (string) $id;
556
			} ?>
557
558
			<script type="text/javascript">
559
				jQuery(document).ready(function ($) {
560
561
					// Hook into the heartbeat-send
562
					$(document).on('heartbeat-send', function (e, data) {
563
						data['rdn_maybe_fetch'] = <?php echo json_encode( $maybe_fetch ); ?>;
564
					});
565
566
					// Listen for the custom event "heartbeat-tick" on $(document).
567
					$(document).on('heartbeat-tick', function (e, data) {
568
569
						if (data.rdn_fetch !== '') {
570
571
							ajax_data = {
572
								'action': 'rdn_fetch_notifications',
573
								'notices': data.rdn_fetch
574
							};
575
576
							$.post(ajaxurl, ajax_data);
577
578
						}
579
580
					});
581
				});
582
			</script>
583
584
		<?php }
585
586
		/**
587
		 * Hook into the Heartbeat API.
588
		 *
589
		 * @since 1.3.0
590
		 *
591
		 * @param  array $response Heartbeat tick response
592
		 * @param  array $data     Heartbeat tick data
593
		 *
594
		 * @return array           Updated Heartbeat tick response
595
		 */
596
		function heartbeat( $response, $data ) {
597
598
			if ( isset( $data['rdn_maybe_fetch'] ) ) {
599
600
				$notices = $data['rdn_maybe_fetch'];
601
602
				if ( ! is_array( $notices ) ) {
603
					$notices = array( $notices );
604
				}
605
606
				foreach ( $notices as $notice_id ) {
607
608
					$fetch = get_option( "rdn_fetch_$notice_id", false );
609
610
					if ( 'fetch' === $fetch ) {
611
612
						if ( ! isset( $response['rdn_fetch'] ) ) {
613
							$response['rdn_fetch'] = array();
614
						}
615
616
						$response['rdn_fetch'][] = $notice_id;
617
618
					}
619
620
				}
621
622
			}
623
624
			return $response;
625
626
		}
627
628
		/**
629
		 * Triggers the remote requests that fetches notices for this particular instance
630
		 *
631
		 * @since 1.3.0
632
		 * @return void
633
		 */
634
		public function remote_get_notice_ajax() {
635
636
			if ( isset( $_POST['notices'] ) ) {
637
				$notices = $_POST['notices'];
638
			} else {
639
				echo 'No notice ID';
640
				die();
641
			}
642
643
			if ( ! is_array( $notices ) ) {
644
				$notices = array( $notices );
645
			}
646
647
			foreach ( $notices as $notice_id ) {
648
649
				$notification = $this->get_notification( $notice_id );
650
				$rn           = $this->remote_get_notification( $notification );
651
652
				if ( is_wp_error( $rn ) ) {
653
					echo $rn->get_error_message();
654
				} else {
655
					echo json_encode( $rn );
656
				}
657
658
			}
659
660
			die();
661
662
		}
663
664
		/**
665
		 * Get the remote server URL
666
		 *
667
		 * @since 1.2.0
668
		 *
669
		 * @param string $url THe server URL to sanitize
670
		 *
671
		 * @return string
672
		 */
673
		protected function get_remote_url( $url ) {
674
675
			$url = explode( '?', $url );
676
677
			return esc_url( $url[0] );
678
679
		}
680
681
		/**
682
		 * Maybe get a notification from the remote server
683
		 *
684
		 * @since 1.2.0
685
		 *
686
		 * @param array $notification The notification data array
687
		 *
688
		 * @return string|WP_Error
689
		 */
690
		protected function remote_get_notification( $notification ) {
691
692
			/* Query the server */
693
			$response = wp_remote_get( $this->build_query_url( $notification['server_url'], $this->get_payload( $notification ) ), array( 'timeout' => apply_filters( 'rn_http_request_timeout', 5 ) ) );
694
695
			/* If we have a WP_Error object we abort */
696
			if ( is_wp_error( $response ) ) {
697
				return $response;
698
			}
699
700
			if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
701
				return new WP_Error( 'invalid_response', sprintf( __( 'The server response was invalid (code %s)', 'remote-notifications' ), wp_remote_retrieve_response_code( $response ) ) );
702
			}
703
704
			$body = wp_remote_retrieve_body( $response );
705
706
			if ( empty( $body ) ) {
707
				return new WP_Error( 'empty_response', __( 'The server response is empty', 'remote-notifications' ) );
708
			}
709
710
			$body = json_decode( $body );
711
712
			if ( is_null( $body ) ) {
713
				return new WP_Error( 'json_decode_error', __( 'Cannot decode the response content', 'remote-notifications' ) );
714
			}
715
716
			set_transient( 'rn_last_notification_' . $notification['notice_id'], $body, $notification['cache_lifetime'] * 60 * 60 );
717
			delete_option( 'rdn_fetch_' . $notification['notice_id'] );
718
719
			if ( $this->is_notification_error( $body ) ) {
720
				return new WP_Error( 'notification_error', $this->get_notification_error_message( $body ) );
721
			}
722
723
			return $body;
724
725
		}
726
727
		/**
728
		 * Check if the notification returned by the server is an error
729
		 *
730
		 * @since 1.2.0
731
		 *
732
		 * @param object $notification Notification returned
733
		 *
734
		 * @return bool
735
		 */
736
		protected function is_notification_error( $notification ) {
737
738
			if ( false === $this->get_notification_error_message( $notification ) ) {
739
				return false;
740
			}
741
742
			return true;
743
744
		}
745
746
		/**
747
		 * Get the error message returned by the remote server
748
		 *
749
		 * @since 1.2.0
750
		 *
751
		 * @param object $notification Notification returned
752
		 *
753
		 * @return bool|string
754
		 */
755
		protected function get_notification_error_message( $notification ) {
756
757
			if ( ! is_object( $notification ) ) {
758
				return false;
759
			}
760
761
			if ( ! isset( $notification->error ) ) {
762
				return false;
763
			}
764
765
			return sanitize_text_field( $notification->error );
766
767
		}
768
769
		/**
770
		 * Get the payload required for querying the remote server
771
		 *
772
		 * @since 1.2.0
773
		 *
774
		 * @param array $notification The notification data array
775
		 *
776
		 * @return string
777
		 */
778
		protected function get_payload( $notification ) {
779
			return base64_encode( json_encode( array(
780
				'channel' => $notification['channel_id'],
781
				'key'     => $notification['channel_key']
782
			) ) );
783
		}
784
785
		/**
786
		 * Get the full URL used for the remote get
787
		 *
788
		 * @since 1.2.0
789
		 *
790
		 * @param string $url     The remote server URL
791
		 * @param string $payload The encoded payload
792
		 *
793
		 * @return string
794
		 */
795
		protected function build_query_url( $url, $payload ) {
796
			return add_query_arg( array(
797
				'post_type' => 'notification',
798
				'payload'   => $payload
799
			), $this->get_remote_url( $url ) );
800
		}
801
802
	}
803
804
}
805
806
/**
807
 * The main function responsible for returning the unique RDN client
808
 *
809
 * Use this function like you would a global variable, except without needing
810
 * to declare the global.
811
 *
812
 * @since 1.3.0
813
 * @return object Remote_Dashboard_Notifications_Client
814
 */
815
function RDNC() {
816
	return Remote_Dashboard_Notifications_Client::instance();
817
}
818
819
// Get Awesome Support Running
820
RDNC();
821
822
/**
823
 * Register a new remote notification
824
 *
825
 * Helper function for registering new notifications through the Remote_Dashboard_Notifications_Client class
826
 *
827
 * @since 1.3.0
828
 *
829
 * @param int    $channel_id  Channel ID on the remote server
830
 * @param string $channel_key Channel key for authentication with the server
831
 * @param string $server      Notification server URL
832
 * @param int    $cache       Cache lifetime (in hours)
833
 *
834
 * @return bool|string
835
 */
836
function rdnc_add_notification( $channel_id, $channel_key, $server, $cache = 6 ) {
837
	return RDNC()->add_notification( $channel_id, $channel_key, $server, $cache );
838
}
839
840
if ( ! class_exists( 'TAV_Remote_Notification_Client' ) ) {
841
842
	/**
843
	 * Class TAV_Remote_Notification_Client
844
	 *
845
	 * This class, even though deprecated, is kept here for backwards compatibility. It is now just a wrapper for the new notification registration method.
846
	 *
847
	 * @deprecated @1.3.0
848
	 */
849
	class TAV_Remote_Notification_Client {
850
851
		public function __construct( $channel_id = false, $channel_key = false, $server = false ) {
852
			rdnc_add_notification( $channel_id, $channel_key, $server );
853
		}
854
	}
855
856
}