TAV_Remote_Notification_Client::build_query_url()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * Remote Dashobard 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 2014 ThemeAvenue
20
 */
21
22
// If this file is called directly, abort.
23
if ( ! defined( 'WPINC' ) ) {
24
	die;
25
}
26
27
class TAV_Remote_Notification_Client {
28
29
	/**
30
	 * Channel ID
31
	 *
32
	 * @var int
33
	 */
34
	protected $id;
35
36
	/**
37
	 * Channel identification key
38
	 *
39
	 * @var string
40
	 */
41
	protected $key;
42
43
	/**
44
	 * Notice unique identifier
45
	 *
46
	 * @var string
47
	 */
48
	protected $notice_id;
49
50
	/**
51
	 * Notification server URL
52
	 *
53
	 * @var string
54
	 */
55
	protected $server;
56
57
	/**
58
	 * Notification caching delay
59
	 *
60
	 * @var int
61
	 */
62
	protected $cache;
63
64
	/**
65
	 * Error message
66
	 *
67
	 * @var string
68
	 */
69
	protected $error;
70
71
	/**
72
	 * Notification
73
	 *
74
	 * @var string|object
75
	 */
76
	protected $notice;
77
78
	/**
79
	 * Class version.
80
	 *
81
	 * @since    0.1.0
82
	 *
83
	 * @var      string
84
	 */
85
	protected static $version = '0.2.0';
86
87
	public function __construct( $channel_id = false, $channel_key = false, $server = false ) {
88
89
		/* Don't continue during Ajax process */
90
		if ( ! is_admin() || defined( 'DOING_AJAX' ) && DOING_AJAX ) {
91
			return;
92
		}
93
94
		$this->id        = (int) $channel_id;
95
		$this->key       = sanitize_key( $channel_key );
96
		$this->server    = esc_url( $server );
97
		$this->notice_id = $this->id . substr( $this->key, 0, 5 );
98
		$this->cache     = apply_filters( 'rn_notice_caching_time', 6 );
99
		$this->error     = null;
100
101
		/* The plugin can't work without those 2 parameters */
102
		if ( false === ( $this->id || $this->key || $this->server ) ) {
103
			return;
104
		}
105
106
		$this->init();
107
108
	}
109
110
	/**
111
	 * Instantiate the plugin
112
	 *
113
	 * @since 1.2.0
114
	 * @return void
115
	 */
116
	public function init() {
117
118
		/* Call the dismiss method before testing for Ajax */
119
		if ( isset( $_GET['rn'] ) && isset( $_GET['notification'] ) ) {
120
			add_action( 'init', array( $this, 'dismiss' ) );
121
		}
122
123
		add_action( 'admin_print_styles', array( $this, 'style' ), 100 );
124
		add_action( 'admin_notices', array( $this, 'show_notice' ) );
125
126
	}
127
128
	/**
129
	 * Get the notification message
130
	 *
131
	 * @since 1.2.0
132
	 * @return string
133
	 */
134
	public function get_notice() {
135
136
		if ( is_null( $this->notice ) ) {
137
			$this->notice = $this->fetch_notice();
138
		}
139
140
		return $this->notice;
141
142
	}
143
144
	/**
145
	 * Retrieve the notice from the transient or from the remote server
146
	 *
147
	 * @since 1.2.0
148
	 * @return mixed
149
	 */
150
	protected function fetch_notice() {
151
152
		$content = get_transient( "rn_last_notification_$this->notice_id" );
153
154
		if ( false === $content ) {
155
			$content = $this->remote_get_notice();
156
		}
157
158
		return $content;
159
160
	}
161
162
	/**
163
	 * Get the remote server URL
164
	 *
165
	 * @since 1.2.0
166
	 * @return string
167
	 */
168
	protected function get_remote_url() {
169
170
		$url = explode( '?', $this->server );
171
172
		return esc_url( $url[0] );
173
174
	}
175
176
	/**
177
	 * Maybe get a notification from the remote server
178
	 *
179
	 * @since 1.2.0
180
	 * @return string|WP_Error
181
	 */
182
	protected function remote_get_notice() {
183
184
		/* Query the server */
185
		$response = wp_remote_get( $this->build_query_url(), array( 'timeout' => apply_filters( 'rn_http_request_timeout', 5 ) ) );
186
187
		/* If we have a WP_Error object we abort */
188
		if ( is_wp_error( $response ) ) {
189
			return $response;
190
		}
191
192
		if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
193
			return new WP_Error( 'invalid_response', sprintf( __( 'The server response was invalid (code %s)', 'remote-notifications' ), wp_remote_retrieve_response_code( $response ) ) );
194
		}
195
196
		$body = wp_remote_retrieve_body( $response );
197
198
		if ( empty( $body ) ) {
199
			return new WP_Error( 'empty_response', __( 'The server response is empty', 'remote-notifications' ) );
200
		}
201
202
		$body = json_decode( $body );
203
204
		if ( is_null( $body ) ) {
205
			return new WP_Error( 'json_decode_error', __( 'Cannot decode the response content', 'remote-notifications' ) );
206
		}
207
208
		set_transient( "rn_last_notification_$this->notice_id", $body, $this->cache*60*60 );
209
210
		if ( $this->is_notification_error( $body ) ) {
211
			return new WP_Error( 'notification_error', $this->get_notification_error_message( $body ) );
212
		}
213
214
		return $body;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $body; (object|integer|double|string|array|boolean) is incompatible with the return type documented by TAV_Remote_Notification_Client::remote_get_notice of type string|WP_Error.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
215
216
	}
217
218
	/**
219
	 * Check if the notification returned by the server is an error
220
	 *
221
	 * @since 1.2.0
222
	 *
223
	 * @param object $notification Notification returned
224
	 *
225
	 * @return bool
226
	 */
227
	protected function is_notification_error( $notification ) {
228
229
		if ( false === $this->get_notification_error_message( $notification ) ) {
230
			return false;
231
		}
232
233
		return true;
234
235
	}
236
237
	/**
238
	 * Get the error message returned by the remote server
239
	 *
240
	 * @since 1.2.0
241
	 *
242
	 * @param object $notification Notification returned
243
	 *
244
	 * @return bool|string
245
	 */
246
	protected function get_notification_error_message( $notification ) {
247
248
		if ( ! is_object( $notification ) ) {
249
			return false;
250
		}
251
252
		if ( ! isset( $notification->error ) ) {
253
			return false;
254
		}
255
256
		return sanitize_text_field( $notification->error );
257
258
	}
259
260
	/**
261
	 * Get the payload required for querying the remote server
262
	 *
263
	 * @since 1.2.0
264
	 * @return string
265
	 */
266
	protected function get_payload() {
267
		return base64_encode( json_encode( array( 'channel' => $this->id, 'key' => $this->key ) ) );
268
	}
269
270
	/**
271
	 * Get the full URL used for the remote get
272
	 *
273
	 * @since 1.2.0
274
	 * @return string
275
	 */
276
	protected function build_query_url() {
277
		return add_query_arg( array( 'post_type' => 'notification', 'payload' => $this->get_payload() ), $this->get_remote_url() );
278
	}
279
280
	/**
281
	 * Check if the notification has been dismissed
282
	 *
283
	 * @since 1.2.0
284
	 * @return bool
285
	 */
286
	protected function is_notification_dismissed() {
287
288
		if ( is_wp_error( $this->get_notice() ) || $this->is_notification_error( $this->get_notice() ) ) {
0 ignored issues
show
Documentation introduced by
$this->get_notice() is of type string, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
289
			return false;
290
		}
291
292
		global $current_user;
293
294
		$dismissed = array_filter( (array) get_user_meta( $current_user->ID, '_rn_dismissed', true ) );
295
296
		if ( is_array( $dismissed ) && in_array( $this->get_notice()->slug, $dismissed ) ) {
297
			return true;
298
		}
299
300
		return false;
301
302
	}
303
304
	/**
305
	 * Check if the notification can be displayed for the current post type
306
	 *
307
	 * @since 1.2.0
308
	 * @return bool
309
	 */
310
	protected function is_post_type_restricted() {
311
312
		/* If the type array isn't empty we have a limitation */
313
		if ( isset( $this->get_notice()->type ) && is_array( $this->get_notice()->type ) && ! empty( $this->get_notice()->type ) ) {
314
315
			/* Get current post type */
316
			$pt = get_post_type();
317
318
			/**
319
			 * If the current post type can't be retrieved
320
			 * or if it's not in the allowed post types,
321
			 * then we don't display the admin notice.
322
			 */
323
			if ( false === $pt || ! in_array( $pt, $this->get_notice()->type ) ) {
324
				return true;
325
			}
326
327
		}
328
329
		return false;
330
331
	}
332
333
	/**
334
	 * Check if the notification has started yet
335
	 *
336
	 * @since 1.2.0
337
	 * @return bool
338
	 */
339 View Code Duplication
	protected function is_notification_started() {
0 ignored issues
show
Duplication introduced by
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...
340
341
		if ( isset( $this->get_notice()->date_start ) && ! empty( $this->get_notice()->date_start ) && strtotime( $this->get_notice()->date_start ) < time() ) {
342
			return true;
343
		}
344
345
		return false;
346
347
	}
348
349
	/**
350
	 * Check if the notification has expired
351
	 *
352
	 * @since 1.2.0
353
	 * @return bool
354
	 */
355 View Code Duplication
	protected function has_notification_ended() {
0 ignored issues
show
Duplication introduced by
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...
356
357
		if ( isset( $this->get_notice()->date_end ) && ! empty( $this->get_notice()->date_end ) && strtotime( $this->get_notice()->date_end ) < time() ) {
358
			return true;
359
		}
360
361
		return false;
362
363
	}
364
365
	/**
366
	 * Display the admin notice
367
	 *
368
	 * The function will do some checks to verify if
369
	 * the notice can be displayed on the current page.
370
	 * If all the checks are passed, the notice
371
	 * is added to the page.
372
	 *
373
	 * @since 0.1.0
374
	 */
375
	public function show_notice() {
376
377
		/**
378
		 * @var object $content
379
		 */
380
		$content = $this->get_notice();
381
382
		if ( empty( $content ) || is_wp_error( $content ) ) {
383
			return;
384
		}
385
386
		if ( $this->is_notification_dismissed() ) {
387
			return;
388
		}
389
390
		if ( $this->is_post_type_restricted() ) {
391
			return;
392
		}
393
394
		if ( ! $this->is_notification_started() ) {
395
			return;
396
		}
397
398
		if ( $this->has_notification_ended() ) {
399
			return;
400
		}
401
402
		/* Prepare alert class */
403
		$style = isset( $content->style ) ? $content->style : 'updated';
404
405
		if ( 'updated' == $style ) {
406
			$class = $style;
407
		}
408
409
		elseif ( 'error' == $style ) {
410
			$class = 'updated error';
411
		}
412
413
		else {
414
			$class = "updated rn-alert rn-alert-$style";
415
		}
416
417
		/**
418
		 * Prepare the dismiss URL
419
		 *
420
		 * @var (string) URL
421
		 * @todo get a more accurate URL of the current page
422
		 */
423
		$args  = array();
424
		$nonce = wp_create_nonce( 'rn-dismiss' );
425
		$slug  = $content->slug;
426
427
		array_push( $args, "rn=$nonce" );
428
		array_push( $args, "notification=$slug" );
429
430
		foreach( $_GET as $key => $value ) {
431
432
			array_push( $args, "$key=$value" );
433
434
		}
435
436
		$args = implode( '&', $args );
437
		$url  = "?$args";
438
		?>
439
440
		<div class="<?php echo $class; ?>">
441 View Code Duplication
			<?php if ( !in_array( $style, array( 'updated', 'error' ) ) ): ?><a href="<?php echo $url; ?>" id="rn-dismiss" class="rn-dismiss-btn" title="<?php _e( 'Dismiss notification', 'remote-notifications' ); ?>">&times;</a><?php endif; ?>
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
442
			<p><?php echo html_entity_decode( $content->message ); ?></p>
443 View Code Duplication
			<?php if ( in_array( $style, array( 'updated', 'error' ) ) ): ?><p><a href="<?php echo $url; ?>" id="rn-dismiss" class="rn-dismiss-button button-secondary"><?php _e( 'Dismiss', 'remote-notifications' ); ?></a></p><?php endif; ?>
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
444
		</div>
445
		<?php
446
447
	}
448
449
	/**
450
	 * Dismiss notice
451
	 *
452
	 * When the user dismisses a notice, its slug
453
	 * is added to the _rn_dismissed entry in the DB options table.
454
	 * This entry is then used to check if a notie has been dismissed
455
	 * before displaying it on the dashboard.
456
	 *
457
	 * @since 0.1.0
458
	 */
459
	public function dismiss() {
460
461
		global $current_user;
462
463
		/* Check if we have all the vars */
464
		if ( !isset( $_GET['rn'] ) || !isset( $_GET['notification'] ) ) {
465
			return;
466
		}
467
468
		/* Validate nonce */
469
		if ( !wp_verify_nonce( sanitize_key( $_GET['rn'] ), 'rn-dismiss' ) ) {
470
			return;
471
		}
472
473
		/* Get dismissed list */
474
		$dismissed = array_filter( (array) get_user_meta( $current_user->ID, '_rn_dismissed', true ) );
475
476
		/* Add the current notice to the list if needed */
477
		if ( is_array( $dismissed ) && !in_array( $_GET['notification'], $dismissed ) ) {
478
			array_push( $dismissed, $_GET['notification'] );
479
		}
480
481
		/* Update option */
482
		update_user_meta( $current_user->ID, '_rn_dismissed', $dismissed );
483
484
		/* Get redirect URL */
485
		$args = array();
486
487
		/* Get URL args */
488
		foreach( $_GET as $key => $value ) {
489
490
			if ( in_array( $key, array( 'rn', 'notification' ) ) )
491
				continue;
492
493
			array_push( $args, "$key=$value" );
494
495
		}
496
497
		$args = implode( '&', $args );
498
		$url  = "?$args";
499
500
		/* Redirect */
501
		wp_redirect( $url );
502
503
	}
504
505
	/**
506
	 * Adds inline style for non standard notices
507
	 *
508
	 * This function will only be called if the notice style is not standard.
509
	 *
510
	 * @since 0.1.0
511
	 */
512
	public function style() { ?>
513
514
		<style type="text/css">div.rn-alert{padding:15px;padding-right:35px;margin-bottom:20px;border:1px solid transparent;-webkit-box-shadow:none;box-shadow:none}div.rn-alert p:empty{display:none}div.rn-alert ul,div.rn-alert ul li,div.rn-alert ol,div.rn-alert ol li{list-style:inherit !important}div.rn-alert ul,div.rn-alert ol{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>
515
516
	<?php }
517
518
	/**
519
	 * Dismiss notice using Ajax
520
	 *
521
	 * This function is NOT used. Testing only.
522
	 */
523
	public function script() {
524
525
		$url = admin_url();
526
		?>
527
528
		<script type="text/javascript">
529
			jQuery(document).ready(function($) {
530
531
				var prout = 'prout';
532
533
				$('#rn-dismiss').on('click', function(event) {
534
					event.preventDefault();
535
					$.ajax({
536
						type: "GET",
537
						url: <?php echo $url; ?>,
538
						data: prout
539
					});
540
					console.log('clicked');
541
				});
542
543
				return false;
544
545
			});
546
		</script>
547
548
		<?php
549
550
	}
551
552
	/**
553
	 * Debug info.
554
	 *
555
	 * Display an error message commented in the admin footer.
556
	 *
557
	 * @since  0.1.2
558
	 */
559
	public function debug_info() {
560
561
		$error = $this->error;
562
563
		echo "<!-- RDN Debug Info: $error -->";
564
565
	}
566
567
}