PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.3
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.3
28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 19.1 All 128 releases
wordpress-seo / admin / class-yoast-notification.php

class-yoast-notification.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.3, at admin/class-yoast-notification.php

447 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Notifications
6 * @since 1.5.3
7 */
8
9 /**
10 * Implements individual notification.
11 */
12 class Yoast_Notification {
13
14 /**
15 * Type of capability check.
16 *
17 * @var string
18 */
19 public const MATCH_ALL = 'all';
20
21 /**
22 * Type of capability check.
23 *
24 * @var string
25 */
26 public const MATCH_ANY = 'any';
27
28 /**
29 * Notification type.
30 *
31 * @var string
32 */
33 public const ERROR = 'error';
34
35 /**
36 * Notification type.
37 *
38 * @var string
39 */
40 public const WARNING = 'warning';
41
42 /**
43 * Notification type.
44 *
45 * @var string
46 */
47 public const UPDATED = 'updated';
48
49 /**
50 * Options of this Notification.
51 *
52 * Contains optional arguments:
53 *
54 * - type: The notification type, i.e. 'updated' or 'error'
55 * - id: The ID of the notification
56 * - nonce: Security nonce to use in case of dismissible notice.
57 * - priority: From 0 to 1, determines the order of Notifications.
58 * - dismissal_key: Option name to save dismissal information in, ID will be used if not supplied.
59 * - capabilities: Capabilities that a user must have for this Notification to show.
60 * - capability_check: How to check capability pass: all or any.
61 * - wpseo_page_only: Only display on wpseo page or on every page.
62 * - yoast_branding: Whether to show the Yoast SEO branding in the notification.
63 * - resolve_nonce: Security nonce to use in case of resolving the notification.
64 *
65 * @var array
66 */
67 private $options = [];
68
69 /**
70 * Contains default values for the optional arguments.
71 *
72 * @var array
73 */
74 private $defaults = [
75 'type' => self::UPDATED,
76 'id' => '',
77 'user_id' => null,
78 'nonce' => null,
79 'priority' => 0.5,
80 'data_json' => [],
81 'dismissal_key' => null,
82 'capabilities' => [],
83 'capability_check' => self::MATCH_ALL,
84 'yoast_branding' => false,
85 'resolve_nonce' => '',
86 ];
87
88 /**
89 * The message for the notification.
90 *
91 * @var string
92 */
93 private $message;
94
95 /**
96 * Notification class constructor.
97 *
98 * @param string $message Message string.
99 * @param array $options Set of options.
100 */
101 public function __construct( $message, $options = [] ) {
102 $this->message = $message;
103 $this->options = $this->normalize_options( $options );
104 }
105
106 /**
107 * Retrieve notification ID string.
108 *
109 * @return string
110 */
111 public function get_id() {
112 return $this->options['id'];
113 }
114
115 /**
116 * Retrieve the user to show the notification for.
117 *
118 * @deprecated 21.6
119 * @codeCoverageIgnore
120 *
121 * @return WP_User|null The user to show this notification for.
122 */
123 public function get_user() {
124 _deprecated_function( __METHOD__, 'Yoast SEO 21.6' );
125 return null;
126 }
127
128 /**
129 * Retrieve the id of the user to show the notification for.
130 *
131 * Returns the id of the current user if not user has been sent.
132 *
133 * @return int The user id
134 */
135 public function get_user_id() {
136 return ( $this->options['user_id'] ?? get_current_user_id() );
137 }
138
139 /**
140 * Retrieve nonce identifier.
141 *
142 * @return string|null Nonce for this Notification.
143 */
144 public function get_nonce() {
145 if ( $this->options['id'] && empty( $this->options['nonce'] ) ) {
146 $this->options['nonce'] = wp_create_nonce( $this->options['id'] );
147 }
148
149 return $this->options['nonce'];
150 }
151
152 /**
153 * Make sure the nonce is up to date.
154 *
155 * @return void
156 */
157 public function refresh_nonce() {
158 if ( $this->options['id'] ) {
159 $this->options['nonce'] = wp_create_nonce( $this->options['id'] );
160 }
161 }
162
163 /**
164 * Get the type of the notification.
165 *
166 * @return string
167 */
168 public function get_type() {
169 return $this->options['type'];
170 }
171
172 /**
173 * Priority of the notification.
174 *
175 * Relative to the type.
176 *
177 * @return float Returns the priority between 0 and 1.
178 */
179 public function get_priority() {
180 return $this->options['priority'];
181 }
182
183 /**
184 * Get the nonce to resolve the alert.
185 *
186 * @return string
187 */
188 public function get_resolve_nonce() {
189 return $this->options['resolve_nonce'];
190 }
191
192 /**
193 * Get the User Meta key to check for dismissal of notification.
194 *
195 * @return string User Meta Option key that registers dismissal.
196 */
197 public function get_dismissal_key() {
198 if ( empty( $this->options['dismissal_key'] ) ) {
199 return $this->options['id'];
200 }
201
202 return $this->options['dismissal_key'];
203 }
204
205 /**
206 * Is this Notification persistent.
207 *
208 * @return bool True if persistent, False if fire and forget.
209 */
210 public function is_persistent() {
211 $id = $this->get_id();
212
213 return ! empty( $id );
214 }
215
216 /**
217 * Check if the notification is relevant for the current user.
218 *
219 * @return bool True if a user needs to see this notification, false if not.
220 */
221 public function display_for_current_user() {
222 // If the notification is for the current page only, always show.
223 if ( ! $this->is_persistent() ) {
224 return true;
225 }
226
227 // If the current user doesn't match capabilities.
228 return $this->match_capabilities();
229 }
230
231 /**
232 * Does the current user match required capabilities.
233 *
234 * @return bool
235 */
236 public function match_capabilities() {
237 // Super Admin can do anything.
238 if ( is_multisite() && is_super_admin( $this->options['user_id'] ) ) {
239 return true;
240 }
241
242 /**
243 * Filter capabilities that enable the displaying of this notification.
244 *
245 * @param array $capabilities The capabilities that must be present for this notification.
246 * @param Yoast_Notification $notification The notification object.
247 *
248 * @return array Array of capabilities or empty for no restrictions.
249 *
250 * @since 3.2
251 */
252 $capabilities = apply_filters( 'wpseo_notification_capabilities', $this->options['capabilities'], $this );
253
254 // Should be an array.
255 if ( ! is_array( $capabilities ) ) {
256 $capabilities = (array) $capabilities;
257 }
258
259 /**
260 * Filter capability check to enable all or any capabilities.
261 *
262 * @param string $capability_check The type of check that will be used to determine if an capability is present.
263 * @param Yoast_Notification $notification The notification object.
264 *
265 * @return string self::MATCH_ALL or self::MATCH_ANY.
266 *
267 * @since 3.2
268 */
269 $capability_check = apply_filters( 'wpseo_notification_capability_check', $this->options['capability_check'], $this );
270
271 if ( ! in_array( $capability_check, [ self::MATCH_ALL, self::MATCH_ANY ], true ) ) {
272 $capability_check = self::MATCH_ALL;
273 }
274
275 if ( ! empty( $capabilities ) ) {
276
277 $has_capabilities = array_filter( $capabilities, [ $this, 'has_capability' ] );
278
279 switch ( $capability_check ) {
280 case self::MATCH_ALL:
281 return $has_capabilities === $capabilities;
282 case self::MATCH_ANY:
283 return ! empty( $has_capabilities );
284 }
285 }
286
287 return true;
288 }
289
290 /**
291 * Array filter function to find matched capabilities.
292 *
293 * @param string $capability Capability to test.
294 *
295 * @return bool
296 */
297 private function has_capability( $capability ) {
298 $user_id = $this->options['user_id'];
299 if ( ! is_numeric( $user_id ) ) {
300 return false;
301 }
302 $user = get_user_by( 'id', $user_id );
303 if ( ! $user ) {
304 return false;
305 }
306
307 return $user->has_cap( $capability );
308 }
309
310 /**
311 * Return the object properties as an array.
312 *
313 * @return array
314 */
315 public function to_array() {
316 return [
317 'message' => $this->message,
318 'options' => $this->options,
319 ];
320 }
321
322 /**
323 * Adds string (view) behaviour to the notification.
324 *
325 * @return string
326 */
327 public function __toString() {
328 return $this->render();
329 }
330
331 /**
332 * Renders the notification as a string.
333 *
334 * @return string The rendered notification.
335 */
336 public function render() {
337 $attributes = [];
338
339 // Default notification classes.
340 $classes = [
341 'yoast-notification',
342 ];
343
344 // Maintain WordPress visualisation of notifications when they are not persistent.
345 if ( ! $this->is_persistent() ) {
346 $classes[] = 'notice';
347 $classes[] = $this->get_type();
348 }
349
350 if ( ! empty( $classes ) ) {
351 $attributes['class'] = implode( ' ', $classes );
352 }
353
354 // Combined attribute key and value into a string.
355 array_walk( $attributes, [ $this, 'parse_attributes' ] );
356
357 $message = null;
358 if ( $this->options['yoast_branding'] ) {
359 $message = $this->wrap_yoast_seo_icon( $this->message );
360 }
361
362 $message ??= wpautop( $this->message );
363
364 // Build the output DIV.
365 return '<div ' . implode( ' ', $attributes ) . '>' . $message . '</div>' . PHP_EOL;
366 }
367
368 /**
369 * Get the message for the notification.
370 *
371 * @return string The message.
372 */
373 public function get_message() {
374 return wpautop( $this->message );
375 }
376
377 /**
378 * Wraps the message with a Yoast SEO icon.
379 *
380 * @param string $message The message to wrap.
381 *
382 * @return string The wrapped message.
383 */
384 private function wrap_yoast_seo_icon( $message ) {
385 $out = sprintf(
386 '<img src="%1$s" height="%2$d" width="%3$d" class="yoast-seo-icon" />',
387 esc_url( plugin_dir_url( WPSEO_FILE ) . 'packages/js/images/Yoast_SEO_Icon.svg' ),
388 60,
389 60,
390 );
391 $out .= '<div class="yoast-seo-icon-wrap">';
392 $out .= $message;
393 $out .= '</div>';
394
395 return $out;
396 }
397
398 /**
399 * Get the JSON if provided.
400 *
401 * @return string|false
402 */
403 public function get_json() {
404 if ( empty( $this->options['data_json'] ) ) {
405 return '';
406 }
407
408 return WPSEO_Utils::format_json_encode( $this->options['data_json'] );
409 }
410
411 /**
412 * Make sure we only have values that we can work with.
413 *
414 * @param array $options Options to normalize.
415 *
416 * @return array
417 */
418 private function normalize_options( $options ) {
419 $options = wp_parse_args( $options, $this->defaults );
420
421 // Should not exceed 0 or 1.
422 $options['priority'] = min( 1, max( 0, $options['priority'] ) );
423
424 // Set default capabilities when not supplied.
425 if ( empty( $options['capabilities'] ) || $options['capabilities'] === [] ) {
426 $options['capabilities'] = [ 'wpseo_manage_options' ];
427 }
428
429 // Set to the id of the current user if not supplied.
430 $options['user_id'] ??= get_current_user_id();
431
432 return $options;
433 }
434
435 /**
436 * Format HTML element attributes.
437 *
438 * @param string $value Attribute value.
439 * @param string $key Attribute name.
440 *
441 * @return void
442 */
443 private function parse_attributes( &$value, $key ) {
444 $value = sprintf( '%s="%s"', sanitize_key( $key ), esc_attr( $value ) );
445 }
446 }
447