Passed
Push — master ( 0ea9fe...8403c0 )
by
unknown
08:02
created

MonsterInsights_Notifications::hooks()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 6
rs 10
1
<?php
2
3
/**
4
 * Notifications.
5
 *
6
 * @since 7.10.5
7
 */
8
class MonsterInsights_Notifications {
9
10
	/**
11
	 * Source of notifications content.
12
	 *
13
	 * @since {VERSION}
14
	 *
15
	 * @var string
16
	 */
17
	const SOURCE_URL = 'https://plugin-cdn.monsterinsights.com/notifications.json';
18
19
	/**
20
	 * Option value.
21
	 *
22
	 * @since {VERSION}
23
	 *
24
	 * @var bool|array
25
	 */
26
	public $option = false;
27
28
	/**
29
	 * The name of the option used to store the data.
30
	 *
31
	 * @var string
32
	 */
33
	public $option_name = 'monsterinsights_notifications';
34
35
	/**
36
	 * MonsterInsights_Notifications constructor.
37
	 */
38
	public function __construct() {
39
		$this->init();
40
	}
41
42
	/**
43
	 * Initialize class.
44
	 *
45
	 * @since {VERSION}
46
	 */
47
	public function init() {
48
49
		$this->hooks();
50
	}
51
52
	/**
53
	 * Register hooks.
54
	 *
55
	 * @since {VERSION}
56
	 */
57
	public function hooks() {
58
		add_action( 'wp_ajax_monsterinsights_notification_dismiss', array( $this, 'dismiss' ) );
59
60
		add_action( 'wp_ajax_monsterinsights_vue_get_notifications', array( $this, 'ajax_get_notifications' ) );
61
62
		add_action( 'monsterinsights_admin_notifications_update', array( $this, 'update' ) );
63
64
	}
65
66
	/**
67
	 * Check if user has access and is enabled.
68
	 *
69
	 * @return bool
70
	 * @since {VERSION}
71
	 *
72
	 */
73
	public function has_access() {
74
75
		$access = false;
76
77
		if ( current_user_can( 'monsterinsights_view_dashboard' ) && ! monsterinsights_get_option( 'hide_am_notices', false ) ) {
78
			$access = true;
79
		}
80
81
		return apply_filters( 'monsterinsights_admin_notifications_has_access', $access );
82
	}
83
84
	/**
85
	 * Get option value.
86
	 *
87
	 * @param bool $cache Reference property cache if available.
88
	 *
89
	 * @return array
90
	 * @since {VERSION}
91
	 *
92
	 */
93
	public function get_option( $cache = true ) {
94
95
		if ( $this->option && $cache ) {
96
			return $this->option;
97
		}
98
99
		$option = get_option( $this->option_name, array() );
100
101
		$this->option = array(
102
			'update'    => ! empty( $option['update'] ) ? $option['update'] : 0,
103
			'events'    => ! empty( $option['events'] ) ? $option['events'] : array(),
104
			'feed'      => ! empty( $option['feed'] ) ? $option['feed'] : array(),
105
			'dismissed' => ! empty( $option['dismissed'] ) ? $option['dismissed'] : array(),
106
		);
107
108
		return $this->option;
109
	}
110
111
	/**
112
	 * Fetch notifications from feed.
113
	 *
114
	 * @return array
115
	 * @since {VERSION}
116
	 *
117
	 */
118
	public function fetch_feed() {
119
120
		$res = wp_remote_get( self::SOURCE_URL );
121
122
		if ( is_wp_error( $res ) ) {
123
			return array();
124
		}
125
126
		$body = wp_remote_retrieve_body( $res );
127
128
		if ( empty( $body ) ) {
129
			return array();
130
		}
131
132
		return $this->verify( json_decode( $body, true ) );
133
	}
134
135
	/**
136
	 * Verify notification data before it is saved.
137
	 *
138
	 * @param array $notifications Array of notifications items to verify.
139
	 *
140
	 * @return array
141
	 * @since {VERSION}
142
	 *
143
	 */
144
	public function verify( $notifications ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
145
146
		$data = array();
147
148
		if ( ! is_array( $notifications ) || empty( $notifications ) ) {
0 ignored issues
show
introduced by
The condition is_array($notifications) is always true.
Loading history...
149
			return $data;
150
		}
151
152
		$option = $this->get_option();
153
154
		foreach ( $notifications as $notification ) {
155
156
			// The message and license should never be empty, if they are, ignore.
157
			if ( empty( $notification['content'] ) || empty( $notification['type'] ) ) {
158
				continue;
159
			}
160
161
			// Ignore if license type does not match.
162
			$license_type = MonsterInsights()->license->get_license_type() ? MonsterInsights()->license->get_license_type() : 'lite';
0 ignored issues
show
Bug Best Practice introduced by
The property $license is declared protected in MonsterInsights_Lite. Since you implement __get, consider adding a @property or @property-read.
Loading history...
163
164
			if ( ! in_array( $license_type, $notification['type'] ) ) {
165
				continue;
166
			}
167
168
			// Ignore if notification is not ready to display(based on start time).
169
			if ( ! empty( $notification['start'] ) && time() < strtotime( $notification['start'] ) ) {
170
				continue;
171
			}
172
173
			// Ignore if expired.
174
			if ( ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ) ) {
175
				continue;
176
			}
177
178
			// Ignore if notification has already been dismissed.
179
			$notification_already_dismissed = false;
180
			if ( is_array( $option['dismissed'] ) && ! empty( $option['dismissed'] ) ) {
181
				foreach ( $option['dismissed'] as $dismiss_notification ) {
182
					if ( $notification['id'] === $dismiss_notification['id'] ) {
183
						$notification_already_dismissed = true;
184
						break;
185
					}
186
				}
187
			}
188
189
			if ( true === $notification_already_dismissed ) {
190
				continue;
191
			}
192
193
			// Ignore if notification existed before installing MonsterInsights.
194
			// Prevents bombarding the user with notifications after activation.
195
			$over_time = get_option( 'monsterinsights_over_time', array() );
196
197
			if (
198
				! empty( $over_time['installed_date'] ) &&
199
				! empty( $notification['start'] ) &&
200
				$over_time['installed_date'] > strtotime( $notification['start'] )
201
			) {
202
				continue;
203
			}
204
205
			$data[] = $notification;
206
		}
207
208
		return $data;
209
	}
210
211
	/**
212
	 * Verify saved notification data for active notifications.
213
	 *
214
	 * @param array $notifications Array of notifications items to verify.
215
	 *
216
	 * @return array
217
	 * @since {VERSION}
218
	 *
219
	 */
220
	public function verify_active( $notifications ) {
221
222
		if ( ! is_array( $notifications ) || empty( $notifications ) ) {
0 ignored issues
show
introduced by
The condition is_array($notifications) is always true.
Loading history...
223
			return array();
224
		}
225
226
		$license_type = MonsterInsights()->license->get_license_type() ? MonsterInsights()->license->get_license_type() : 'lite';
0 ignored issues
show
Bug Best Practice introduced by
The property $license is declared protected in MonsterInsights_Lite. Since you implement __get, consider adding a @property or @property-read.
Loading history...
227
228
		// Remove notifications that are not active, or if the license type not exists
229
		foreach ( $notifications as $key => $notification ) {
230
			if (
231
				( ! empty( $notification['start'] ) && time() < strtotime( $notification['start'] ) ) ||
232
				( ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ) ) ||
233
				( ! empty( $notification['type'] ) && ! in_array( $license_type, $notification['type'] ) )
234
			) {
235
				unset( $notifications[ $key ] );
236
			}
237
		}
238
239
		return $notifications;
240
	}
241
242
	/**
243
	 * Get notification data.
244
	 *
245
	 * @return array
246
	 * @since {VERSION}
247
	 *
248
	 */
249
	public function get() {
250
251
		if ( ! $this->has_access() ) {
252
			return array();
253
		}
254
255
		$option = $this->get_option();
256
257
		// Update notifications using async task.
258
		if ( empty( $option['update'] ) || time() > $option['update'] + DAY_IN_SECONDS ) {
259
			if ( false === wp_next_scheduled( 'monsterinsights_admin_notifications_update' ) ) {
260
				wp_schedule_single_event( time(), 'monsterinsights_admin_notifications_update' );
261
			}
262
		}
263
264
		$events = ! empty( $option['events'] ) ? $this->verify_active( $option['events'] ) : array();
265
		$feed   = ! empty( $option['feed'] ) ? $this->verify_active( $option['feed'] ) : array();
266
267
		$notifications              = array();
268
		$notifications['active']    = array_merge( $events, $feed );
269
		$notifications['active']    = $this->get_notifications_with_human_readeable_start_time( $notifications['active'] );
270
		$notifications['active']    = $this->get_notifications_with_formatted_content( $notifications['active'] );
271
		$notifications['dismissed'] = ! empty( $option['dismissed'] ) ? $option['dismissed'] : array();
272
		$notifications['dismissed'] = $this->get_notifications_with_human_readeable_start_time( $notifications['dismissed'] );
273
		$notifications['dismissed'] = $this->get_notifications_with_formatted_content( $notifications['dismissed'] );
274
275
		return $notifications;
276
	}
277
278
	/**
279
	 * Improve format of the content of notifications before display. By default just runs wpautop.
280
	 *
281
	 * @param array $notifications The notifications to be parsed.
282
	 *
283
	 * @return mixed
284
	 */
285
	public function get_notifications_with_formatted_content( $notifications ) {
286
		if ( ! is_array( $notifications ) || empty( $notifications ) ) {
0 ignored issues
show
introduced by
The condition is_array($notifications) is always true.
Loading history...
287
			return $notifications;
288
		}
289
290
		foreach ( $notifications as $key => $notification ) {
291
			if ( ! empty( $notification['content'] ) ) {
292
				$notifications[ $key ]['content'] = wpautop( $notification['content'] );
293
				$notifications[ $key ]['content'] = apply_filters( 'monsterinsights_notification_content_display', $notifications[ $key ]['content'] );
294
			}
295
		}
296
297
		return $notifications;
298
	}
299
300
	/**
301
	 * Get notifications start time with human time difference
302
	 *
303
	 * @return array $notifications
304
	 *
305
	 * @since 7.12.3
306
	 */
307
	public function get_notifications_with_human_readeable_start_time( $notifications ) {
308
		if ( ! is_array( $notifications ) || empty( $notifications ) ) {
309
			return;
310
		}
311
312
		foreach ( $notifications as $key => $notification ) {
313
			if ( ! isset( $notification['start'] ) || empty( $notification['start'] ) ) {
314
				continue;
315
			}
316
317
			// Translators: Readable time to display
318
			$modified_start_time            = sprintf( __( '%1$s ago', 'google-analytics-for-wordpress' ), human_time_diff( strtotime( $notification['start'] ), current_time( 'timestamp' ) ) );
319
			$notifications[ $key ]['start'] = $modified_start_time;
320
		}
321
322
		return $notifications;
323
	}
324
325
	/**
326
	 * Get active notifications.
327
	 *
328
	 * @return array $notifications['active'] active notifications
329
	 *
330
	 * @since 7.12.3
331
	 */
332
	public function get_active_notifications() {
333
		$notifications = $this->get();
334
335
		return isset( $notifications['active'] ) ? $notifications['active'] : array();
336
	}
337
338
	/**
339
	 * Get dismissed notifications.
340
	 *
341
	 * @return array $notifications['dismissed'] dismissed notifications
342
	 *
343
	 * @since 7.12.3
344
	 */
345
	public function get_dismissed_notifications() {
346
		$notifications = $this->get();
347
348
		return isset( $notifications['dismissed'] ) ? $notifications['dismissed'] : array();
349
	}
350
351
	/**
352
	 * Get notification count.
353
	 *
354
	 * @return int
355
	 * @since {VERSION}
356
	 *
357
	 */
358
	public function get_count() {
359
360
		return count( $this->get_active_notifications() );
361
	}
362
363
	/**
364
	 * Add a manual notification event.
365
	 *
366
	 * @param array $notification Notification data.
367
	 *
368
	 * @since {VERSION}
369
	 *
370
	 */
371
	public function add( $notification ) {
372
373
		if ( empty( $notification['id'] ) ) {
374
			return;
375
		}
376
377
		$option = $this->get_option();
378
379
		foreach ( $option['dismissed'] as $item ) {
380
			if ( $item['id'] === $notification['id'] ) {
381
				return;
382
			}
383
		}
384
385
		foreach ( $option['events'] as $item ) {
386
			if ( $item['id'] === $notification['id'] ) {
387
				return;
388
			}
389
		}
390
391
		$notification = $this->verify( array( $notification ) );
392
393
		update_option(
394
			$this->option_name,
395
			array(
396
				'update'    => $option['update'],
397
				'feed'      => $option['feed'],
398
				'events'    => array_merge( $notification, $option['events'] ),
399
				'dismissed' => $option['dismissed'],
400
			),
401
			false
402
		);
403
	}
404
405
	/**
406
	 * Update notification data from feed.
407
	 *
408
	 * @param array $option (Optional) Added @since 7.13.2
409
	 *
410
	 * @since {VERSION}
411
	 */
412
	public function update() {
413
414
		$feed   = $this->fetch_feed();
415
		$option = $this->get_option();
416
417
		update_option(
418
			$this->option_name,
419
			array(
420
				'update'    => time(),
421
				'feed'      => $feed,
422
				'events'    => $option['events'],
423
				'dismissed' => array_slice( $option['dismissed'], 0, 30 ), // Limit dismissed notifications to last 30.
424
			),
425
			false
426
		);
427
	}
428
429
	/**
430
	 * Dismiss notification via AJAX.
431
	 *
432
	 * @since {VERSION}
433
	 */
434
	public function dismiss() {
435
		// Run a security check.
436
		check_ajax_referer( 'mi-admin-nonce', 'nonce' );
437
438
		// Check for access and required param.
439
		if ( ! $this->has_access() || empty( $_POST['id'] ) ) {
440
			wp_send_json_error();
441
		}
442
443
		$id     = sanitize_text_field( wp_unslash( $_POST['id'] ) );
444
		$option = $this->get_option();
445
446
		// Dismiss all notifications and add them to dissmiss array.
447
		if ( 'all' === $id ) {
448
			if ( is_array( $option['feed'] ) && ! empty( $option['feed'] ) ) {
449
				foreach ( $option['feed'] as $key => $notification ) {
450
					array_unshift( $option['dismissed'], $notification );
451
					unset( $option['feed'][ $key ] );
452
				}
453
			}
454
			if ( is_array( $option['events'] ) && ! empty( $option['events'] ) ) {
455
				foreach ( $option['events'] as $key => $notification ) {
456
					array_unshift( $option['dismissed'], $notification );
457
					unset( $option['events'][ $key ] );
458
				}
459
			}
460
		}
461
462
		$type = is_numeric( $id ) ? 'feed' : 'events';
463
464
		// Remove notification and add in dismissed array.
465
		if ( is_array( $option[ $type ] ) && ! empty( $option[ $type ] ) ) {
466
			foreach ( $option[ $type ] as $key => $notification ) {
467
				if ( $notification['id'] == $id ) { // phpcs:ignore WordPress.PHP.StrictComparisons
468
					// Add notification to dismissed array.
469
					array_unshift( $option['dismissed'], $notification );
470
					// Remove notification from feed or events.
471
					unset( $option[ $type ][ $key ] );
472
					break;
473
				}
474
			}
475
		}
476
477
		update_option( $this->option_name, $option, false );
478
479
		wp_send_json_success();
480
	}
481
482
	/**
483
	 * This generates the markup for the notifications indicator if needed.
484
	 *
485
	 * @return string
486
	 */
487
	public function get_menu_count() {
488
489
		if ( $this->get_count() > 0 ) {
490
			return '<span class="monsterinsights-menu-notification-indicator update-plugins">' . $this->get_count() . '</span>';
491
		}
492
493
		return '';
494
495
	}
496
497
	/**
498
	 * Retrieve the notifications via an ajax call.
499
	 */
500
	public function ajax_get_notifications() {
501
502
		// Run a security check.
503
		check_ajax_referer( 'mi-admin-nonce', 'nonce' );
504
505
		$notifications_data = array(
506
			'notifications' => $this->get_active_notifications(),
507
			'dismissed'     => $this->get_dismissed_notifications(),
508
			'view_url'      => $this->get_view_url( 'monsterinsights-report-overview', 'monsterinsights_reports' ),
509
			'sidebar_url'   => $this->get_sidebar_url(),
510
		);
511
512
		wp_send_json_success( $notifications_data );
513
	}
514
515
	/**
516
	 * Get the URL for the page where users can see/read notifications.
517
	 *
518
	 * @return string
519
	 */
520
	public function get_view_url( $scroll_to, $page, $tab='' ) {
521
		$disabled   = monsterinsights_get_option( 'dashboards_disabled', false );
522
523
		$url = add_query_arg( array(
524
			'page' => $page,
525
			'monsterinsights-scroll' => $scroll_to,
526
			'monsterinsights-highlight' => $scroll_to,
527
		), admin_url( 'admin.php' ) );
528
529
		if ( ! empty( $tab ) ) {
530
			$url .= '#/'. $tab;
531
		}
532
533
		if ( false !== $disabled ) {
0 ignored issues
show
introduced by
The condition false !== $disabled is always true.
Loading history...
534
			$url = is_multisite() ? network_admin_url( 'admin.php?page=monsterinsights_network' ) : admin_url( 'admin.php?page=monsterinsights_settings' );
535
		}
536
537
		return $url;
538
539
	}
540
541
	/**
542
	 * Get the notification sidebar URL for the page where users can see/read notifications.
543
	 *
544
	 * @return string
545
	 */
546
	public function get_sidebar_url() {
547
548
		$disabled = monsterinsights_get_option( 'dashboards_disabled', false );
549
550
		$url = add_query_arg(
551
			array(
552
				'page' => 'monsterinsights_reports',
553
				'open' => 'monsterinsights_notification_sidebar',
554
			),
555
			admin_url( 'admin.php' )
556
		);
557
558
		if ( false !== $disabled ) {
0 ignored issues
show
introduced by
The condition false !== $disabled is always true.
Loading history...
559
			$url = is_multisite() ? network_admin_url( 'admin.php?page=monsterinsights_network' ) : admin_url( 'admin.php?page=monsterinsights_settings' );
560
		}
561
562
		return $url;
563
	}
564
565
	/**
566
	 * Delete the notification options.
567
	 */
568
	public function delete_notifications_data() {
569
570
		delete_option( $this->option_name );
571
572
		// Delete old notices option.
573
		delete_option( 'monsterinsights_notices' );
574
575
		monsterinsights_notification_event_runner()->delete_data();
576
577
	}
578
}
579