Completed
Pull Request — master (#7)
by Julien
03:35
created

handler.php (2 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
 * Dismissible Notices Handler.
4
 *
5
 * This library is designed to handle dismissible admin notices.
6
 *
7
 * LICENSE: This program is free software; you can redistribute it and/or modify it under the terms of the GNU
8
 * General Public License as published by the Free Software Foundation; either version 3 of the License, or (at
9
 * your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY
10
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
 * General Public License for more details. You should have received a copy of the GNU General Public License along
12
 * with this program. If not, see <http://opensource.org/licenses/gpl-license.php>
13
 *
14
 * @package   Dismissible Notices Handler
15
 * @author    Julien Liabeuf <[email protected]>
16
 * @version   1.1
17
 * @license   GPL-2.0+
18
 * @link      https://julienliabeuf.com
19
 * @copyright 2018 Julien Liabeuf
20
 */
21
22
// If this file is called directly, abort.
23
if ( ! defined( 'WPINC' ) ) {
24
	die;
25
}
26
27
if ( ! class_exists( 'Dismissible_Notices_Handler' ) ) {
28
29
	final class Dismissible_Notices_Handler {
30
31
		/**
32
		 * @var Dismissible_Notices_Handler Holds the unique instance of the handler
33
		 * @since 1.0
34
		 */
35
		private static $instance;
36
37
		/**
38
		 * Library version
39
		 *
40
		 * @since 1.0
41
		 * @var string
42
		 */
43
		public $version = '1.1';
44
45
		/**
46
		 * Required version of PHP.
47
		 *
48
		 * @since 1.0
49
		 * @var string
50
		 */
51
		public $php_version_required = '5.5';
52
53
		/**
54
		 * Minimum version of WordPress required to use the library
55
		 *
56
		 * @since 1.0
57
		 * @var string
58
		 */
59
		public $wordpress_version_required = '4.2';
60
61
		/**
62
		 * @var array Holds all our registered notices
63
		 * @since 1.0
64
		 */
65
		private $notices;
66
67
		/**
68
		 * Instantiate and return the unique Dismissible_Notices_Handler object
69
		 *
70
		 * @since     1.0
71
		 * @return object Dismissible_Notices_Handler Unique instance of the handler
72
		 */
73
		public static function instance() {
74
75
			if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Dismissible_Notices_Handler ) ) {
76
				self::$instance = new Dismissible_Notices_Handler;
77
				self::$instance->init();
78
			}
79
80
			return self::$instance;
81
82
		}
83
84
		/**
85
		 * Initialize the library
86
		 *
87
		 * @since 1.0
88
		 * @return void
89
		 */
90
		private function init() {
91
92
			// Make sure WordPress is compatible
93
			if ( ! self::$instance->is_wp_compatible() ) {
94
				self::$instance->spit_error(
95
					sprintf(
96
						/* translators: %s: required wordpress version */
97
						esc_html__( 'The library can not be used because your version of WordPress is too old. You need version %s at least.', 'wp-dismissible-notices-handler' ),
98
						self::$instance->wordpress_version_required
99
					)
100
				);
101
102
				return;
103
			}
104
105
			// Make sure PHP is compatible
106
			if ( ! self::$instance->is_php_compatible() ) {
107
				self::$instance->spit_error(
108
					sprintf(
109
						/* translators: %s: required php version */
110
						esc_html__( 'The library can not be used because your version of PHP is too old. You need version %s at least.', 'wp-dismissible-notices-handler' ),
111
						self::$instance->php_version_required
112
					)
113
				);
114
115
				return;
116
			}
117
118
			self::$instance->includes();
119
120
			add_action( 'admin_notices', array( self::$instance, 'display' ) );
121
			add_action( 'admin_print_scripts', array( self::$instance, 'load_script' ) );
122
			add_action( 'wp_ajax_dnh_dismiss_notice', array( self::$instance, 'dismiss_notice_ajax' ) );
123
124
		}
125
126
		/**
127
		 * Check if the current WordPress version fits the requirements
128
		 *
129
		 * @since  1.0
130
		 * @return boolean
131
		 */
132
		private function is_wp_compatible() {
133
134
			if ( version_compare( get_bloginfo( 'version' ), self::$instance->wordpress_version_required, '<' ) ) {
135
				return false;
136
			}
137
138
			return true;
139
140
		}
141
142
		/**
143
		 * Check if the version of PHP is compatible with this library
144
		 *
145
		 * @since  1.0
146
		 * @return boolean
147
		 */
148
		private function is_php_compatible() {
149
150
			if ( version_compare( phpversion(), self::$instance->php_version_required, '<' ) ) {
151
				return false;
152
			}
153
154
			return true;
155
156
		}
157
158
		/**
159
		 * Include all our files
160
		 *
161
		 * @since 1.0
162
		 * @return void
163
		 */
164
		private function includes() {
165
			require( trailingslashit( plugin_dir_path( __FILE__ ) ) . 'includes/helper-functions.php' );
166
		}
167
168
		/**
169
		 * Load the script
170
		 *
171
		 * @since 1.0
172
		 * @return void
173
		 */
174
		public function load_script() {
175
			wp_register_script( 'dnh', trailingslashit( plugin_dir_url( __FILE__ ) ) . 'assets/js/main.js', array( 'jquery' ), self::$instance->version, true );
176
			wp_enqueue_script( 'dnh' );
177
		}
178
179
		/**
180
		 * Display all the registered notices
181
		 *
182
		 * @since 1.0
183
		 * @return void
184
		 */
185
		public function display() {
186
187
			if ( is_null( self::$instance->notices ) || empty( self::$instance->notices ) ) {
188
				return;
189
			}
190
191
			foreach ( self::$instance->notices as $id => $notice ) {
192
193
				$id = self::$instance->get_id( $id );
194
195
				// Check if the notice was dismissed
196
				if ( self::$instance->is_dismissed( $id ) ) {
197
					continue;
198
				}
199
200
				// Check if the current user has required capability
201
				if ( ! empty( $notice['cap'] ) && ! current_user_can( $notice['cap'] ) ) {
202
					continue;
203
				}
204
205
				$class = array(
206
					'notice',
207
					$notice['type'],
208
					'is-dismissible',
209
					$notice['class'],
210
				);
211
212
				printf( '<div id="%3$s" class="%1$s"><p>%2$s</p></div>', trim( implode( ' ', $class ) ), $notice['content'], "dnh-$id" );
213
214
			}
215
216
		}
217
218
		/**
219
		 * Spits an error message at the top of the admin screen
220
		 *
221
		 * @since 1.0
222
		 *
223
		 * @param string $error Error message to spit
224
		 *
225
		 * @return void
226
		 */
227
		protected function spit_error( $error ) {
228
			printf(
229
				'<div style="margin: 20px; text-align: center;"><strong>%1$s</strong> %2$s</pre></div>',
230
				esc_html__( 'Dismissible Notices Handler Error:', 'wp-dismissible-notices-handler' ),
231
				wp_kses_post( $error )
232
			);
233
		}
234
235
		/**
236
		 * Sanitize a notice ID and return it
237
		 *
238
		 * @since 1.0
239
		 *
240
		 * @param string $id
241
		 *
242
		 * @return string
243
		 */
244
		public function get_id( $id ) {
245
			return sanitize_key( $id );
246
		}
247
248
		/**
249
		 * Get available notice types
250
		 *
251
		 * @since 1.0
252
		 * @return array
253
		 */
254
		public function get_types() {
255
256
			$types = array(
257
				'error',
258
				'updated',
259
				// New types of notification style.
260
				'notice-error',
261
				'notice-warning',
262
				'notice-success',
263
				'notice-info',
264
			);
265
266
			return apply_filters( 'dnh_notice_types', $types );
267
268
		}
269
270
		/**
271
		 * Get the default arguments for a notice
272
		 *
273
		 * @since 1.0
274
		 * @return array
275
		 */
276
		private function default_args() {
277
278
			$args = array(
279
				'screen' => '', // Coming soon
280
				'scope'  => 'user', // Scope of the dismissal. Either user or global
281
				'cap'    => '', // Required user capability
282
				'class'  => '', // Additional class to add to the notice
283
			);
284
285
			return apply_filters( 'dnh_default_args', $args );
286
287
		}
288
289
		/**
290
		 * Register a new notice
291
		 *
292
		 * @since 1.0
293
		 *
294
		 * @param string $id      Notice ID, used to identify it
295
		 * @param string $type    Type of notice to display
296
		 * @param string $content Notice content
297
		 * @param array  $args    Additional parameters
298
		 *
299
		 * @return bool
300
		 */
301
		public function register_notice( $id, $type, $content, $args = array() ) {
302
303
			if ( is_null( self::$instance->notices ) ) {
304
				self::$instance->notices = array();
305
			}
306
307
			$id      = self::$instance->get_id( $id );
308
			$type    = in_array( $t = sanitize_text_field( $type ), self::$instance->get_types() ) ? $t : 'updated';
309
			$content = wp_kses_post( $content );
310
			$args    = wp_parse_args( $args, self::$instance->default_args() );
311
312
			if ( array_key_exists( $id, self::$instance->notices ) ) {
313
314
				self::$instance->spit_error(
315
					sprintf(
316
						/* translators: %s: required php version */
317
						esc_html__( 'A notice with the ID %s has already been registered.', 'wp-dismissible-notices-handler' ),
318
						"<code>$id</code>"
319
					)
320
				);
321
322
				return false;
323
			}
324
325
			$notice = array(
326
				'type'    => $type,
327
				'content' => $content,
328
			);
329
330
			$notice = array_merge( $notice, $args );
331
332
			self::$instance->notices[ $id ] = $notice;
333
334
			return true;
335
336
		}
337
338
		/**
339
		 * Notice dismissal triggered by Ajax
340
		 *
341
		 * @since 1.0
342
		 * @return void
343
		 */
344
		public function dismiss_notice_ajax() {
345
346
			if ( ! isset( $_POST['id'] ) ) {
347
				echo 0;
348
				exit;
349
			}
350
351
			if ( empty( $_POST['id'] ) || false === strpos( $_POST['id'], 'dnh-' ) ) {
352
				echo 0;
353
				exit;
354
			}
355
356
			$id = self::$instance->get_id( str_replace( 'dnh-', '', $_POST['id'] ) );
357
358
			echo self::$instance->dismiss_notice( $id );
359
			exit;
360
361
		}
362
363
		/**
364
		 * Dismiss a notice
365
		 *
366
		 * @since 1.0
367
		 *
368
		 * @param string $id ID of the notice to dismiss
369
		 *
370
		 * @return bool
371
		 */
372
		public function dismiss_notice( $id ) {
373
374
			$notice = self::$instance->get_notice( self::$instance->get_id( $id ) );
375
376
			if ( false === $notice ) {
377
				return false;
378
			}
379
380
			if ( self::$instance->is_dismissed( $id ) ) {
381
				return false;
382
			}
383
384
			return 'user' === $notice['scope'] ? self::$instance->dismiss_user( $id ) : self::$instance->dismiss_global( $id );
385
386
		}
387
388
		/**
389
		 * Dismiss notice for the current user
390
		 *
391
		 * @since 1.0
392
		 *
393
		 * @param string $id Notice ID
394
		 *
395
		 * @return int|bool
396
		 */
397
		private function dismiss_user( $id ) {
398
399
			$dismissed = self::$instance->dismissed_user();
400
401
			if ( in_array( $id, $dismissed ) ) {
402
				return false;
403
			}
404
405
			array_push( $dismissed, $id );
406
407
			return update_user_meta( get_current_user_id(), 'dnh_dismissed_notices', $dismissed );
408
409
		}
410
411
		/**
412
		 * Dismiss notice globally on the site
413
		 *
414
		 * @since 1.0
415
		 *
416
		 * @param string $id Notice ID
417
		 *
418
		 * @return bool
419
		 */
420
		private function dismiss_global( $id ) {
421
422
			$dismissed = self::$instance->dismissed_global();
423
424
			if ( in_array( $id, $dismissed ) ) {
425
				return false;
426
			}
427
428
			array_push( $dismissed, $id );
429
430
			return update_option( 'dnh_dismissed_notices', $dismissed );
431
432
		}
433
434
		/**
435
		 * Restore a dismissed notice
436
		 *
437
		 * @since 1.0
438
		 *
439
		 * @param string $id ID of the notice to restore
440
		 *
441
		 * @return bool
442
		 */
443
		public function restore_notice( $id ) {
444
445
			$id     = self::$instance->get_id( $id );
446
			$notice = self::$instance->get_notice( $id );
447
448
			if ( false === $notice ) {
449
				return false;
450
			}
451
452
			return 'user' === $notice['scope'] ? self::$instance->restore_user( $id ) : self::$instance->restore_global( $id );
453
454
		}
455
456
		/**
457
		 * Restore a notice dismissed by the current user
458
		 *
459
		 * @since 1.0
460
		 *
461
		 * @param string $id ID of the notice to restore
462
		 *
463
		 * @return bool
464
		 */
465 View Code Duplication
		private function restore_user( $id ) {
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...
466
467
			$id     = self::$instance->get_id( $id );
468
			$notice = self::$instance->get_notice( $id );
469
470
			if ( false === $notice ) {
471
				return false;
472
			}
473
474
			$dismissed = self::$instance->dismissed_user();
475
476
			if ( ! in_array( $id, $dismissed ) ) {
477
				return false;
478
			}
479
480
			$flip = array_flip( $dismissed );
481
			$key  = $flip[ $id ];
482
483
			unset( $dismissed[ $key ] );
484
485
			return update_user_meta( get_current_user_id(), 'dnh_dismissed_notices', $dismissed );
486
487
		}
488
489
		/**
490
		 * Restore a notice dismissed globally
491
		 *
492
		 * @since 1.0
493
		 *
494
		 * @param string $id ID of the notice to restore
495
		 *
496
		 * @return bool
497
		 */
498 View Code Duplication
		private function restore_global( $id ) {
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...
499
500
			$id     = self::$instance->get_id( $id );
501
			$notice = self::$instance->get_notice( $id );
502
503
			if ( false === $notice ) {
504
				return false;
505
			}
506
507
			$dismissed = self::$instance->dismissed_global();
508
509
			if ( ! in_array( $id, $dismissed ) ) {
510
				return false;
511
			}
512
513
			$flip = array_flip( $dismissed );
514
			$key  = $flip[ $id ];
515
516
			unset( $dismissed[ $key ] );
517
518
			return update_option( 'dnh_dismissed_notices', $dismissed );
519
520
		}
521
522
		/**
523
		 * Get all dismissed notices
524
		 *
525
		 * This includes notices dismissed globally or per user.
526
		 *
527
		 * @since 1.0
528
		 * @return array
529
		 */
530
		public function dismissed_notices() {
531
532
			$user   = self::$instance->dismissed_user();
533
			$global = self::$instance->dismissed_global();
534
535
			return array_merge( $user, $global );
536
537
		}
538
539
		/**
540
		 * Get user dismissed notices
541
		 *
542
		 * @since 1.0
543
		 * @return array
544
		 */
545
		private function dismissed_user() {
546
547
			$dismissed = get_user_meta( get_current_user_id(), 'dnh_dismissed_notices', true );
548
549
			if ( '' === $dismissed ) {
550
				$dismissed = array();
551
			}
552
553
			return $dismissed;
554
555
		}
556
557
		/**
558
		 * Get globally dismissed notices
559
		 *
560
		 * @since 1.0
561
		 * @return array
562
		 */
563
		private function dismissed_global() {
564
			return get_option( 'dnh_dismissed_notices', array() );
565
		}
566
567
		/**
568
		 * Check if a notice has been dismissed
569
		 *
570
		 * @since 1.0
571
		 *
572
		 * @param string $id Notice ID
573
		 *
574
		 * @return bool
575
		 */
576
		public function is_dismissed( $id ) {
577
578
			$dismissed = self::$instance->dismissed_notices();
579
580
			if ( ! in_array( self::$instance->get_id( $id ), $dismissed ) ) {
581
				return false;
582
			}
583
584
			return true;
585
586
		}
587
588
		/**
589
		 * Get all the registered notices
590
		 *
591
		 * @since 1.0
592
		 * @return array|null
593
		 */
594
		public function get_notices() {
595
			return self::$instance->notices;
596
		}
597
598
		/**
599
		 * Return a specific notice
600
		 *
601
		 * @since 1.0
602
		 *
603
		 * @param string $id Notice ID
604
		 *
605
		 * @return array|false
606
		 */
607
		public function get_notice( $id ) {
608
609
			$id = self::$instance->get_id( $id );
610
611
			if ( ! is_array( self::$instance->notices ) || ! array_key_exists( $id, self::$instance->notices ) ) {
612
				return false;
613
			}
614
615
			return self::$instance->notices[ $id ];
616
617
		}
618
619
	}
620
621
	/**
622
	 * The main function responsible for returning the unique Dismissible Notices Handler instance
623
	 *
624
	 * Use this function like you would a global variable, except without needing
625
	 * to declare the global.
626
	 *
627
	 * @since 1.0
628
	 * @return object Dismissible_Notices_Handler
629
	 */
630
	function DNH() {
631
		return Dismissible_Notices_Handler::instance();
632
	}
633
634
	/**
635
	 * Get the library running
636
	 */
637
	DNH();
638
639
}
640