Passed
Push — master ( 59650c...0b7aa3 )
by Chris
11:04 queued 06:15
created

MonsterInsights_Notifications::has_access()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 9
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
			if ( ! in_array( MonsterInsights()->license->get_license_type(), $notification['type'] ) ) {
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
				continue;
164
			}
165
166
			// Ignore if expired.
167
			if ( ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ) ) {
168
				continue;
169
			}
170
171
			// Ignore if notification has already been dismissed.
172
			if ( ! empty( $option['dismissed'] ) && in_array( $notification['id'], $option['dismissed'] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
173
				continue;
174
			}
175
176
			// Ignore if notification existed before installing MonsterInsights.
177
			// Prevents bombarding the user with notifications after activation.
178
			$over_time = get_option( 'monsterinsights_over_time', array() );
179
180
			if (
181
				! empty( $over_time['installed_date'] ) &&
182
				! empty( $notification['start'] ) &&
183
				$over_time['installed_date'] > strtotime( $notification['start'] )
184
			) {
185
				continue;
186
			}
187
188
			$data[] = $notification;
189
		}
190
191
		return $data;
192
	}
193
194
	/**
195
	 * Verify saved notification data for active notifications.
196
	 *
197
	 * @param array $notifications Array of notifications items to verify.
198
	 *
199
	 * @return array
200
	 * @since {VERSION}
201
	 *
202
	 */
203
	public function verify_active( $notifications ) {
204
205
		if ( ! is_array( $notifications ) || empty( $notifications ) ) {
0 ignored issues
show
introduced by
The condition is_array($notifications) is always true.
Loading history...
206
			return array();
207
		}
208
209
		// Remove notifications that are not active.
210
		foreach ( $notifications as $key => $notification ) {
211
			if (
212
				( ! empty( $notification['start'] ) && time() < strtotime( $notification['start'] ) ) ||
213
				( ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ) )
214
			) {
215
				unset( $notifications[ $key ] );
216
			}
217
		}
218
219
		return $notifications;
220
	}
221
222
	/**
223
	 * Get notification data.
224
	 *
225
	 * @return array
226
	 * @since {VERSION}
227
	 *
228
	 */
229
	public function get() {
230
231
		if ( ! $this->has_access() ) {
232
			return array();
233
		}
234
235
		$option = $this->get_option();
236
237
		// Update notifications using async task.
238
		if ( empty( $option['update'] ) || time() > $option['update'] + DAY_IN_SECONDS ) {
239
			if ( false === wp_next_scheduled( 'monsterinsights_admin_notifications_update' ) ) {
240
				wp_schedule_single_event( time(), 'monsterinsights_admin_notifications_update' );
241
			}
242
		}
243
244
		$events = ! empty( $option['events'] ) ? $this->verify_active( $option['events'] ) : array();
245
		$feed   = ! empty( $option['feed'] ) ? $this->verify_active( $option['feed'] ) : array();
246
247
		return array_merge( $events, $feed );
248
	}
249
250
	/**
251
	 * Get notification count.
252
	 *
253
	 * @return int
254
	 * @since {VERSION}
255
	 *
256
	 */
257
	public function get_count() {
258
259
		return count( $this->get() );
260
	}
261
262
	/**
263
	 * Add a manual notification event.
264
	 *
265
	 * @param array $notification Notification data.
266
	 *
267
	 * @since {VERSION}
268
	 *
269
	 */
270
	public function add( $notification ) {
271
272
		if ( empty( $notification['id'] ) ) {
273
			return;
274
		}
275
276
		$option = $this->get_option();
277
278
		if ( in_array( $notification['id'], $option['dismissed'] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
279
			return;
280
		}
281
282
		foreach ( $option['events'] as $item ) {
283
			if ( $item['id'] === $notification['id'] ) {
284
				return;
285
			}
286
		}
287
288
		$notification = $this->verify( array( $notification ) );
289
290
		update_option(
291
			$this->option_name,
292
			array(
293
				'update'    => $option['update'],
294
				'feed'      => $option['feed'],
295
				'events'    => array_merge( $notification, $option['events'] ),
296
				'dismissed' => $option['dismissed'],
297
			)
298
		);
299
	}
300
301
	/**
302
	 * Update notification data from feed.
303
	 *
304
	 * @since {VERSION}
305
	 */
306
	public function update() {
307
308
		$feed   = $this->fetch_feed();
309
		$option = $this->get_option();
310
311
		update_option(
312
			$this->option_name,
313
			array(
314
				'update'    => time(),
315
				'feed'      => $feed,
316
				'events'    => $option['events'],
317
				'dismissed' => $option['dismissed'],
318
			)
319
		);
320
	}
321
322
	/**
323
	 * Dismiss notification via AJAX.
324
	 *
325
	 * @since {VERSION}
326
	 */
327
	public function dismiss() {
328
329
		// Run a security check.
330
		check_ajax_referer( 'mi-admin-nonce', 'nonce' );
331
332
		// Check for access and required param.
333
		if ( ! $this->has_access() || empty( $_POST['id'] ) ) {
334
			wp_send_json_error();
335
		}
336
337
		$id     = sanitize_text_field( wp_unslash( $_POST['id'] ) );
338
		$option = $this->get_option();
339
		$type   = is_numeric( $id ) ? 'feed' : 'events';
340
341
		$option['dismissed'][] = $id;
342
		$option['dismissed']   = array_unique( $option['dismissed'] );
343
344
		// Remove notification.
345
		if ( is_array( $option[ $type ] ) && ! empty( $option[ $type ] ) ) {
346
			foreach ( $option[ $type ] as $key => $notification ) {
347
				if ( $notification['id'] == $id ) { // phpcs:ignore WordPress.PHP.StrictComparisons
348
					unset( $option[ $type ][ $key ] );
349
					break;
350
				}
351
			}
352
		}
353
354
		update_option( $this->option_name, $option );
355
356
		wp_send_json_success();
357
	}
358
359
	/**
360
	 * This generates the markup for the notifications indicator if needed.
361
	 *
362
	 * @return string
363
	 */
364
	public function get_menu_count() {
365
366
		if ( $this->get_count() > 0 ) {
367
			return '<span class="monsterinsights-menu-notification-indicator"></span>';
368
		}
369
370
		return '';
371
372
	}
373
374
	/**
375
	 * Retrieve the notifications via an ajax call.
376
	 */
377
	public function ajax_get_notifications() {
378
379
		// Run a security check.
380
		check_ajax_referer( 'mi-admin-nonce', 'nonce' );
381
382
		$notifications_data = array(
383
			'notifications' => $this->get(),
384
			'view_url'      => $this->get_view_url(),
385
		);
386
387
		wp_send_json_success( $notifications_data );
388
	}
389
390
	/**
391
	 * Get the URL for the page where users can see/read notifications.
392
	 *
393
	 * @return string
394
	 */
395
	public function get_view_url() {
396
397
		$disabled = monsterinsights_get_option( 'dashboards_disabled', false );
398
399
		$url = add_query_arg( 'page', 'monsterinsights_reports', admin_url( 'admin.php' ) );
400
401
		if ( false !== $disabled ) {
0 ignored issues
show
introduced by
The condition false !== $disabled is always true.
Loading history...
402
			$url = is_multisite() ? network_admin_url( 'admin.php?page=monsterinsights_network' ) : admin_url( 'admin.php?page=monsterinsights_settings' );
403
		}
404
405
		return $url;
406
407
	}
408
}
409