PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | _inc/lib/widgets.php +787 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,787 @@
1 +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 +/**
3 + * Widgets and Sidebars Library
4 + *
5 + * Helper functions for manipulating widgets on a per-blog basis.
6 + * Only helpful on `wp_loaded` or later (currently requires widgets to be registered and the theme context to already be loaded).
7 + *
8 + * Used by the REST API
9 + *
10 + * @package automattic/jetpack
11 + */
12 +
13 +/**
14 + * Widgets and Sidebars Library
15 + */
16 +class Jetpack_Widgets {
17 +
18 + /**
19 + * Returns the `sidebars_widgets` option with the `array_version` element removed.
20 + *
21 + * @return array The current value of sidebars_widgets
22 + */
23 + public static function get_sidebars_widgets() {
24 + $sidebars = get_option( 'sidebars_widgets', array() );
25 + if ( isset( $sidebars['array_version'] ) ) {
26 + unset( $sidebars['array_version'] );
27 + }
28 + return $sidebars;
29 + }
30 +
31 + /**
32 + * Format widget data for output and for use by other widget functions.
33 + *
34 + * The output looks like:
35 + *
36 + * array(
37 + * 'id' => 'text-3',
38 + * 'sidebar' => 'sidebar-1',
39 + * 'position' => '0',
40 + * 'settings' => array(
41 + * 'title' => 'hello world'
42 + * )
43 + * )
44 + *
45 + * @param string|integer $position The position of the widget in its sidebar.
46 + * @param string $widget_id The widget's id (eg: 'text-3').
47 + * @param string $sidebar The widget's sidebar id (eg: 'sidebar-1').
48 + * @param array|null $settings The settings for the widget.
49 + *
50 + * @return array A normalized array representing this widget.
51 + */
52 + public static function format_widget( $position, $widget_id, $sidebar, $settings = null ) {
53 + if ( ! $settings ) {
54 + $all_settings = get_option( self::get_widget_option_name( $widget_id ) );
55 + $instance = self::get_widget_instance_key( $widget_id );
56 + $settings = $all_settings[ $instance ];
57 + }
58 + $widget = array();
59 +
60 + $widget['id'] = $widget_id;
61 + $widget['id_base'] = self::get_widget_id_base( $widget_id );
62 + $widget['settings'] = $settings;
63 + $widget['sidebar'] = $sidebar;
64 + $widget['position'] = $position;
65 +
66 + return $widget;
67 + }
68 +
69 + /**
70 + * Return a widget's id_base from its id.
71 + *
72 + * @param string $widget_id The id of a widget. (eg: 'text-3').
73 + *
74 + * @return string The id_base of a widget (eg: 'text').
75 + */
76 + public static function get_widget_id_base( $widget_id ) {
77 + // Grab what's before the hyphen.
78 + return substr( $widget_id, 0, strrpos( $widget_id, '-' ) );
79 + }
80 +
81 + /**
82 + * Determine a widget's option name (the WP option where the widget's settings
83 + * are stored - generally `widget_` + the widget's id_base).
84 + *
85 + * @param string $widget_id The id of a widget. (eg: 'text-3').
86 + *
87 + * @return string The option name of the widget's settings. (eg: 'widget_text')
88 + */
89 + public static function get_widget_option_name( $widget_id ) {
90 + return 'widget_' . self::get_widget_id_base( $widget_id );
91 + }
92 +
93 + /**
94 + * Determine a widget instance key from its ID. (eg: 'text-3' becomes '3').
95 + * Used to access the widget's settings.
96 + *
97 + * @param string $widget_id The id of a widget.
98 + *
99 + * @return integer The instance key of that widget.
100 + */
101 + public static function get_widget_instance_key( $widget_id ) {
102 + // Grab all numbers from the end of the id.
103 + preg_match( '/(\d+)$/', $widget_id, $matches );
104 +
105 + return (int) $matches[0];
106 + }
107 +
108 + /**
109 + * Return a widget by ID (formatted for output) or null if nothing is found.
110 + *
111 + * @param string $widget_id The id of a widget to look for.
112 + *
113 + * @return array|null The matching formatted widget (see format_widget).
114 + */
115 + public static function get_widget_by_id( $widget_id ) {
116 + $found = null;
117 + foreach ( self::get_all_widgets() as $widget ) {
118 + if ( $widget['id'] === $widget_id ) {
119 + $found = $widget;
120 + }
121 + }
122 + return $found;
123 + }
124 +
125 + /**
126 + * Return an array of all widgets (active and inactive) formatted for output.
127 + *
128 + * @return array An array of all widgets (see format_widget).
129 + */
130 + public static function get_all_widgets() {
131 + $all_widgets = array();
132 + $sidebars_widgets = self::get_all_sidebars();
133 +
134 + foreach ( $sidebars_widgets as $sidebar => $widgets ) {
135 + if ( ! is_array( $widgets ) ) {
136 + continue;
137 + }
138 + foreach ( $widgets as $key => $widget_id ) {
139 + array_push( $all_widgets, self::format_widget( $key, $widget_id, $sidebar ) );
140 + }
141 + }
142 +
143 + return $all_widgets;
144 + }
145 +
146 + /**
147 + * Return an array of all active widgets formatted for output.
148 + *
149 + * @return array An array of all active widgets (see format_widget).
150 + */
151 + public static function get_active_widgets() {
152 + $active_widgets = array();
153 + $all_widgets = self::get_all_widgets();
154 + foreach ( $all_widgets as $widget ) {
155 + if ( 'wp_inactive_widgets' === $widget['sidebar'] ) {
156 + continue;
157 + }
158 + array_push( $active_widgets, $widget );
159 + }
160 + return $active_widgets;
161 + }
162 +
163 + /**
164 + * Return an array of all widget IDs (active and inactive)
165 + *
166 + * @return array An array of all widget IDs.
167 + */
168 + public static function get_all_widget_ids() {
169 + $all_widgets = array();
170 + $sidebars_widgets = self::get_all_sidebars();
171 + foreach ( array_values( $sidebars_widgets ) as $widgets ) {
172 + if ( ! is_array( $widgets ) ) {
173 + continue;
174 + }
175 + foreach ( array_values( $widgets ) as $widget_id ) {
176 + array_push( $all_widgets, $widget_id );
177 + }
178 + }
179 + return $all_widgets;
180 + }
181 +
182 + /**
183 + * Return an array of widgets with a specific id_base (eg: `text`).
184 + *
185 + * @param string $id_base The id_base of a widget type.
186 + *
187 + * @return array All the formatted widgets matching that widget type (see format_widget).
188 + */
189 + public static function get_widgets_with_id_base( $id_base ) {
190 + $matching_widgets = array();
191 + foreach ( self::get_all_widgets() as $widget ) {
192 + if ( self::get_widget_id_base( $widget['id'] ) === $id_base ) {
193 + array_push( $matching_widgets, $widget );
194 + }
195 + }
196 + return $matching_widgets;
197 + }
198 +
199 + /**
200 + * Return the array of widget IDs in a sidebar or null if that sidebar does
201 + * not exist. Will return an empty array for an existing empty sidebar.
202 + *
203 + * @param string $sidebar The id of a sidebar.
204 + *
205 + * @return array|null The array of widget IDs in the sidebar.
206 + */
207 + public static function get_widgets_in_sidebar( $sidebar ) {
208 + $sidebars = self::get_all_sidebars();
209 +
210 + if ( ! $sidebars || ! is_array( $sidebars ) ) {
211 + return null;
212 + }
213 + if ( ! $sidebars[ $sidebar ] && array_key_exists( $sidebar, $sidebars ) ) {
214 + return array();
215 + }
216 + return $sidebars[ $sidebar ];
217 + }
218 +
219 + /**
220 + * Return an associative array of all registered sidebars for this theme,
221 + * active and inactive, including the hidden disabled widgets sidebar (keyed
222 + * by `wp_inactive_widgets`). Each sidebar is keyed by the ID of the sidebar
223 + * and its value is an array of widget IDs for that sidebar.
224 + *
225 + * @return array An associative array of all sidebars and their widget IDs.
226 + */
227 + public static function get_all_sidebars() {
228 + $sidebars_widgets = self::get_sidebars_widgets();
229 +
230 + if ( ! is_array( $sidebars_widgets ) ) {
231 + return array();
232 + }
233 + return $sidebars_widgets;
234 + }
235 +
236 + /**
237 + * Return an associative array of all active sidebars for this theme, Each
238 + * sidebar is keyed by the ID of the sidebar and its value is an array of
239 + * widget IDs for that sidebar.
240 + *
241 + * @return array An associative array of all active sidebars and their widget IDs.
242 + */
243 + public static function get_active_sidebars() {
244 + $sidebars = array();
245 + foreach ( self::get_all_sidebars() as $sidebar => $widgets ) {
246 + if ( 'wp_inactive_widgets' === $sidebar || ! isset( $widgets ) || ! is_array( $widgets ) ) {
247 + continue;
248 + }
249 + $sidebars[ $sidebar ] = $widgets;
250 + }
251 + return $sidebars;
252 + }
253 +
254 + /**
255 + * Activates a widget in a sidebar. Does not validate that the sidebar exists,
256 + * so please do that first. Also does not save the widget's settings. Please
257 + * do that with `set_widget_settings`.
258 + *
259 + * If position is not set, it will be set to the next available position.
260 + *
261 + * @param string $widget_id The newly-formed id of the widget to be added.
262 + * @param string $sidebar The id of the sidebar where the widget will be added.
263 + * @param string|integer $position (Optional) The position within the sidebar where the widget will be added.
264 + *
265 + * @return bool
266 + */
267 + public static function add_widget_to_sidebar( $widget_id, $sidebar, $position ) {
268 + return self::move_widget_to_sidebar( array( 'id' => $widget_id ), $sidebar, $position );
269 + }
270 +
271 + /**
272 + * Removes a widget from a sidebar. Does not validate that the sidebar exists
273 + * or remove any settings from the widget, so please do that separately.
274 + *
275 + * @param array $widget The widget to be removed.
276 + */
277 + public static function remove_widget_from_sidebar( $widget ) {
278 + $sidebars_widgets = self::get_sidebars_widgets();
279 + // Remove the widget from its old location and reflow the positions of the remaining widgets.
280 + array_splice( $sidebars_widgets[ $widget['sidebar'] ], $widget['position'], 1 );
281 +
282 + update_option( 'sidebars_widgets', $sidebars_widgets );
283 + }
284 +
285 + /**
286 + * Moves a widget to a sidebar. Does not validate that the sidebar exists,
287 + * so please do that first. Also does not save the widget's settings. Please
288 + * do that with `set_widget_settings`. The first argument should be a
289 + * widget as returned by `format_widget` including `id`, `sidebar`, and
290 + * `position`.
291 + *
292 + * If $position is not set, it will be set to the next available position.
293 + *
294 + * Can be used to add a new widget to a sidebar if
295 + * $widget['sidebar'] === NULL
296 + *
297 + * Can be used to move a widget within a sidebar as well if
298 + * $widget['sidebar'] === $sidebar.
299 + *
300 + * @param array $widget The widget to be moved (see format_widget).
301 + * @param string $sidebar The sidebar where this widget will be moved.
302 + * @param string|integer $position (Optional) The position where this widget will be moved in the sidebar.
303 + *
304 + * @return bool
305 + */
306 + public static function move_widget_to_sidebar( $widget, $sidebar, $position ) {
307 + $sidebars_widgets = self::get_sidebars_widgets();
308 +
309 + /*
310 + * If a position is passed and the sidebar isn't empty,
311 + * splice the widget into the sidebar,
312 + * update the sidebar option, and return the result.
313 + */
314 + if ( isset( $widget['sidebar'] ) && isset( $widget['position'] ) ) {
315 + array_splice( $sidebars_widgets[ $widget['sidebar'] ], $widget['position'], 1 );
316 + }
317 +
318 + // Sometimes an existing empty sidebar is NULL, so initialize it.
319 + if ( array_key_exists( $sidebar, $sidebars_widgets ) && ! is_array( $sidebars_widgets[ $sidebar ] ) ) {
320 + $sidebars_widgets[ $sidebar ] = array();
321 + }
322 +
323 + // If no position is passed, set one from items in sidebar.
324 + if ( ! isset( $position ) ) {
325 + $position = 0;
326 + $last_position = self::get_last_position_in_sidebar( $sidebar );
327 + if ( isset( $last_position ) && is_numeric( $last_position ) ) {
328 + $position = $last_position + 1;
329 + }
330 + }
331 +
332 + // Add the widget to the sidebar and reflow the positions of the other widgets.
333 + if ( empty( $sidebars_widgets[ $sidebar ] ) ) {
334 + $sidebars_widgets[ $sidebar ][] = $widget['id'];
335 + } else {
336 + array_splice( $sidebars_widgets[ $sidebar ], (int) $position, 0, $widget['id'] );
337 + }
338 +
339 + set_theme_mod(
340 + 'sidebars_widgets',
341 + array(
342 + 'time' => time(),
343 + 'data' => $sidebars_widgets,
344 + )
345 + );
346 + return update_option( 'sidebars_widgets', $sidebars_widgets );
347 + }
348 +
349 + /**
350 + * Return an integer containing the largest position number in a sidebar or
351 + * null if there are no widgets in that sidebar.
352 + *
353 + * @param string $sidebar The id of a sidebar.
354 + *
355 + * @return integer|null The last index position of a widget in that sidebar.
356 + */
357 + public static function get_last_position_in_sidebar( $sidebar ) {
358 + $widgets = self::get_widgets_in_sidebar( $sidebar );
359 + if ( ! $widgets ) {
360 + return null;
361 + }
362 + $last_position = 0;
363 + foreach ( $widgets as $widget_id ) {
364 + $widget = self::get_widget_by_id( $widget_id );
365 + if ( (int) $widget['position'] > $last_position ) {
366 + $last_position = (int) $widget['position'];
367 + }
368 + }
369 + return $last_position;
370 + }
371 +
372 + /**
373 + * Saves settings for a widget. Does not add that widget to a sidebar. Please
374 + * do that with `move_widget_to_sidebar` first. Will merge the settings of
375 + * any existing widget with the same `$widget_id`.
376 + *
377 + * @param string $widget_id The id of a widget.
378 + * @param array $settings An associative array of settings to merge with any existing settings on this widget.
379 + *
380 + * @return boolean|WP_Error True if update was successful.
381 + */
382 + public static function set_widget_settings( $widget_id, $settings ) {
383 + $widget_option_name = self::get_widget_option_name( $widget_id );
384 + $widget_settings = get_option( $widget_option_name );
385 + $instance_key = self::get_widget_instance_key( $widget_id );
386 + $old_settings = $widget_settings[ $instance_key ] ?? array();
387 + $settings = self::sanitize_widget_settings( $widget_id, $settings, $old_settings );
388 +
389 + if ( ! $settings ) {
390 + return new WP_Error( 'invalid_data', 'Update failed.', 500 );
391 + }
392 + if ( is_array( $old_settings ) ) {
393 + // array_filter prevents empty arguments from replacing existing ones.
394 + $settings = wp_parse_args( array_filter( $settings ), $old_settings );
395 + }
396 +
397 + $widget_settings[ $instance_key ] = $settings;
398 +
399 + return update_option( $widget_option_name, $widget_settings );
400 + }
401 +
402 + /**
403 + * Sanitize an associative array for saving.
404 + *
405 + * @param string $widget_id The id of a widget.
406 + * @param array $settings A widget settings array.
407 + * @param array $old_settings The existing widget settings array.
408 + *
409 + * @return array|false The settings array sanitized by `WP_Widget::update` or false if sanitization failed.
410 + */
411 + private static function sanitize_widget_settings( $widget_id, $settings, $old_settings ) {
412 + $widget = self::get_registered_widget_object( self::get_widget_id_base( $widget_id ) );
413 +
414 + if ( ! $widget ) {
415 + return false;
416 + }
417 + $new_settings = $widget->update( $settings, $old_settings );
418 + if ( ! is_array( $new_settings ) ) {
419 + return false;
420 + }
421 + return $new_settings;
422 + }
423 +
424 + /**
425 + * Deletes settings for a widget. Does not remove that widget to a sidebar. Please
426 + * do that with `remove_widget_from_sidebar` first.
427 + *
428 + * @param array $widget The widget which will have its settings removed (see format_widget).
429 + */
430 + public static function remove_widget_settings( $widget ) {
431 + $widget_option_name = self::get_widget_option_name( $widget['id'] );
432 + $widget_settings = get_option( $widget_option_name );
433 + unset( $widget_settings[ self::get_widget_instance_key( $widget['id'] ) ] );
434 + update_option( $widget_option_name, $widget_settings );
435 + }
436 +
437 + /**
438 + * Update a widget's settings, sidebar, and position. Returns the (updated)
439 + * formatted widget if successful or a WP_Error if it fails.
440 + *
441 + * @param string $widget_id The id of a widget to update.
442 + * @param string $sidebar (Optional) A sidebar to which this widget will be moved.
443 + * @param string|integer $position (Optional) A new position to which this widget will be moved within its new or existing sidebar.
444 + * @param array|object|string $settings Settings to merge with the existing settings of the widget (will be passed through `decode_settings`).
445 + *
446 + * @return array|WP_Error The newly added widget as an associative array with all the above properties.
447 + */
448 + public static function update_widget( $widget_id, $sidebar, $position, $settings ) {
449 + $settings = self::decode_settings( $settings );
450 + if ( isset( $settings ) && ! is_array( $settings ) ) {
451 + return new WP_Error( 'invalid_data', 'Invalid settings', 400 );
452 + }
453 + // Default to an empty array if nothing is specified.
454 + if ( ! is_array( $settings ) ) {
455 + $settings = array();
456 + }
457 + $widget = self::get_widget_by_id( $widget_id );
458 + if ( ! $widget ) {
459 + return new WP_Error( 'not_found', 'No widget found.', 400 );
460 + }
461 + if ( ! $sidebar ) {
462 + $sidebar = $widget['sidebar'];
463 + }
464 + if ( ! isset( $position ) ) {
465 + $position = $widget['position'];
466 + }
467 + if ( ! is_numeric( $position ) ) {
468 + return new WP_Error( 'invalid_data', 'Invalid position', 400 );
469 + }
470 + $widgets_in_sidebar = self::get_widgets_in_sidebar( $sidebar );
471 + if ( ! isset( $widgets_in_sidebar ) ) {
472 + return new WP_Error( 'invalid_data', 'No such sidebar exists', 400 );
473 + }
474 + self::move_widget_to_sidebar( $widget, $sidebar, $position );
475 + $widget_save_status = self::set_widget_settings( $widget_id, $settings );
476 + if ( is_wp_error( $widget_save_status ) ) {
477 + return $widget_save_status;
478 + }
479 + return self::get_widget_by_id( $widget_id );
480 + }
481 +
482 + /**
483 + * Deletes a widget entirely including all its settings. Returns a WP_Error if
484 + * the widget could not be found. Otherwise returns an empty array.
485 + *
486 + * @param string $widget_id The id of a widget to delete. (eg: 'text-2').
487 + *
488 + * @return array|WP_Error An empty array if successful.
489 + */
490 + public static function delete_widget( $widget_id ) {
491 + $widget = self::get_widget_by_id( $widget_id );
492 + if ( ! $widget ) {
493 + return new WP_Error( 'not_found', 'No widget found.', 400 );
494 + }
495 + self::remove_widget_from_sidebar( $widget );
496 + self::remove_widget_settings( $widget );
497 + return array();
498 + }
499 +
500 + /**
501 + * Return an array of settings. The input can be either an object, a JSON
502 + * string, or an array.
503 + *
504 + * @param array|string|object $settings The settings of a widget as passed into the API.
505 + *
506 + * @return array Decoded associative array of settings.
507 + */
508 + public static function decode_settings( $settings ) {
509 + // Treat as string in case JSON was passed.
510 + if ( is_object( $settings ) && property_exists( $settings, 'scalar' ) ) {
511 + $settings = $settings->scalar;
512 + }
513 + if ( is_object( $settings ) ) {
514 + $settings = (array) $settings;
515 + }
516 + // Attempt to decode JSON string.
517 + if ( is_string( $settings ) ) {
518 + $settings = (array) json_decode( $settings );
519 + }
520 + return $settings;
521 + }
522 +
523 + /**
524 + * Activate a new widget.
525 + *
526 + * @param string $id_base The id_base of the new widget (eg: 'text').
527 + * @param string $sidebar The id of the sidebar where this widget will go. Dependent on theme. (eg: 'sidebar-1').
528 + * @param string|integer $position (Optional) The position of the widget in the sidebar. Defaults to the last position.
529 + * @param array|object|string $settings (Optional) An associative array of settings for this widget (will be passed through `decode_settings`). Varies by widget.
530 + *
531 + * @return array|WP_Error The newly added widget as an associative array with all the above properties except 'id_base' replaced with the generated 'id'.
532 + */
533 + public static function activate_widget( $id_base, $sidebar, $position, $settings ) {
534 + if ( ! isset( $id_base ) || ! self::validate_id_base( $id_base ) ) {
535 + return new WP_Error( 'invalid_data', 'Invalid ID base', 400 );
536 + }
537 +
538 + if ( ! isset( $sidebar ) ) {
539 + return new WP_Error( 'invalid_data', 'No sidebar provided', 400 );
540 + }
541 +
542 + if ( isset( $position ) && ! is_numeric( $position ) ) {
543 + return new WP_Error( 'invalid_data', 'Invalid position', 400 );
544 + }
545 +
546 + $settings = self::decode_settings( $settings );
547 + if ( isset( $settings ) && ! is_array( $settings ) ) {
548 + return new WP_Error( 'invalid_data', 'Invalid settings', 400 );
549 + }
550 +
551 + // Default to an empty array if nothing is specified.
552 + if ( ! is_array( $settings ) ) {
553 + $settings = array();
554 + }
555 +
556 + $widget_counter = 1 + self::get_last_widget_instance_key_with_id_base( $id_base );
557 + $widget_id = $id_base . '-' . $widget_counter;
558 + if ( 0 >= $widget_counter ) {
559 + return new WP_Error( 'invalid_data', 'Error creating widget ID' . $widget_id, 500 );
560 + }
561 + if ( self::get_widget_by_id( $widget_id ) ) {
562 + return new WP_Error( 'invalid_data', 'Widget ID already exists', 500 );
563 + }
564 +
565 + self::add_widget_to_sidebar( $widget_id, $sidebar, $position );
566 + $widget_save_status = self::set_widget_settings( $widget_id, $settings );
567 + if ( is_wp_error( $widget_save_status ) ) {
568 + return $widget_save_status;
569 + }
570 +
571 + // Add a Tracks event for non-Headstart activity.
572 + if ( ! defined( 'HEADSTART' ) ) {
573 + $tracking = new Automattic\Jetpack\Tracking();
574 + $tracking->tracks_record_event(
575 + wp_get_current_user(),
576 + 'wpcom_widgets_activate_widget',
577 + array(
578 + 'widget' => $id_base,
579 + 'settings' => wp_json_encode( $settings, JSON_UNESCAPED_SLASHES ),
580 + )
581 + );
582 + }
583 +
584 + return self::get_widget_by_id( $widget_id );
585 + }
586 +
587 + /**
588 + * Activate an array of new widgets. Like calling `activate_widget` multiple times.
589 + *
590 + * @param array $widgets An array of widget arrays. Each sub-array must be of the format required by `activate_widget`.
591 + *
592 + * @return array|WP_Error The newly added widgets in the form returned by `get_all_widgets`.
593 + */
594 + public static function activate_widgets( $widgets ) {
595 + if ( ! is_array( $widgets ) ) {
596 + return new WP_Error( 'invalid_data', 'Invalid widgets', 400 );
597 + }
598 +
599 + $added_widgets = array();
600 +
601 + foreach ( $widgets as $widget ) {
602 + $added_widgets[] = self::activate_widget( $widget['id_base'], $widget['sidebar'], $widget['position'], $widget['settings'] );
603 + }
604 +
605 + return $added_widgets;
606 + }
607 +
608 + /**
609 + * Return the last instance key (integer) of an existing widget matching
610 + * `$id_base`. So if you pass in `text`, and there is a widget with the id
611 + * `text-2`, this function will return `2`.
612 + *
613 + * @param string $id_base The id_base of a type of widget. (eg: 'rss').
614 + *
615 + * @return integer The last instance key of that type of widget.
616 + */
617 + public static function get_last_widget_instance_key_with_id_base( $id_base ) {
618 + $similar_widgets = self::get_widgets_with_id_base( $id_base );
619 +
620 + if ( ! empty( $similar_widgets ) ) {
621 + // If the last widget with the same name is `text-3`, we want `text-4`.
622 + usort( $similar_widgets, __CLASS__ . '::sort_widgets' );
623 +
624 + $last_widget = array_pop( $similar_widgets );
625 + $last_val = (int) self::get_widget_instance_key( $last_widget['id'] );
626 +
627 + return $last_val;
628 + }
629 +
630 + return 0;
631 + }
632 +
633 + /**
634 + * Method used to sort widgets
635 + *
636 + * @since 5.4
637 + *
638 + * @param array $a A normalized array representing a widget.
639 + * @param array $b A normalized array representing a widget.
640 + *
641 + * @return int
642 + */
643 + public static function sort_widgets( $a, $b ) {
644 + $a_val = (int) self::get_widget_instance_key( $a['id'] );
645 + $b_val = (int) self::get_widget_instance_key( $b['id'] );
646 + return $a_val <=> $b_val;
647 + }
648 +
649 + /**
650 + * Retrieve a given widget object instance by ID base (eg. 'text' or 'archives').
651 + *
652 + * @param string $id_base The id_base of a type of widget.
653 + *
654 + * @return WP_Widget|false The found widget object or false if the id_base was not found.
655 + */
656 + public static function get_registered_widget_object( $id_base ) {
657 + if ( ! $id_base ) {
658 + return false;
659 + }
660 +
661 + // Get all of the registered widgets.
662 + global $wp_widget_factory;
663 + if ( ! isset( $wp_widget_factory ) ) {
664 + return false;
665 + }
666 +
667 + $registered_widgets = $wp_widget_factory->widgets;
668 + if ( empty( $registered_widgets ) ) {
669 + return false;
670 + }
671 +
672 + foreach ( array_values( $registered_widgets ) as $registered_widget_object ) {
673 + if ( $registered_widget_object->id_base === $id_base ) {
674 + return $registered_widget_object;
675 + }
676 + }
677 + return false;
678 + }
679 +
680 + /**
681 + * Validate a given widget ID base (eg. 'text' or 'archives').
682 + *
683 + * @param string $id_base The id_base of a type of widget.
684 + *
685 + * @return boolean True if the widget is of a known type.
686 + */
687 + public static function validate_id_base( $id_base ) {
688 + return ( false !== self::get_registered_widget_object( $id_base ) );
689 + }
690 +
691 + /**
692 + * Insert a new widget in a given sidebar.
693 + *
694 + * @param string $widget_id ID of the widget.
695 + * @param array $widget_options Content of the widget.
696 + * @param string $sidebar ID of the sidebar to which the widget will be added.
697 + *
698 + * @return WP_Error|true True when data has been saved correctly, error otherwise.
699 + */
700 + public static function insert_widget_in_sidebar( $widget_id, $widget_options, $sidebar ) {
701 + // Retrieve sidebars, widgets and their instances.
702 + $sidebars_widgets = get_option( 'sidebars_widgets', array() );
703 + $widget_instances = get_option( 'widget_' . $widget_id, array() );
704 +
705 + // Retrieve the key of the next widget instance.
706 + $numeric_keys = array_filter( array_keys( $widget_instances ), 'is_int' );
707 + $next_key = $numeric_keys ? max( $numeric_keys ) + 1 : 2;
708 +
709 + // Add this widget to the sidebar.
710 + if ( ! isset( $sidebars_widgets[ $sidebar ] ) ) {
711 + $sidebars_widgets[ $sidebar ] = array();
712 + }
713 + $sidebars_widgets[ $sidebar ][] = $widget_id . '-' . $next_key;
714 +
715 + // Add the new widget instance.
716 + $widget_instances[ $next_key ] = $widget_options;
717 +
718 + // Store updated sidebars, widgets and their instances.
719 + if (
720 + ! ( update_option( 'sidebars_widgets', $sidebars_widgets ) )
721 + || ( ! ( update_option( 'widget_' . $widget_id, $widget_instances ) ) )
722 + ) {
723 + return new WP_Error( 'widget_update_failed', 'Failed to update widget or sidebar.', 400 );
724 + }
725 +
726 + return true;
727 + }
728 +
729 + /**
730 + * Update the content of an existing widget in a given sidebar.
731 + *
732 + * @param string $widget_id ID of the widget.
733 + * @param array $widget_options New content for the update.
734 + * @param string $sidebar ID of the sidebar to which the widget will be added.
735 + *
736 + * @return WP_Error|true True when data has been updated correctly, error otherwise.
737 + */
738 + public static function update_widget_in_sidebar( $widget_id, $widget_options, $sidebar ) {
739 + // Retrieve sidebars, widgets and their instances.
740 + $sidebars_widgets = get_option( 'sidebars_widgets', array() );
741 + $widget_instances = get_option( 'widget_' . $widget_id, array() );
742 +
743 + // Retrieve index of first widget instance in that sidebar.
744 + $widget_key = false;
745 + foreach ( $sidebars_widgets[ $sidebar ] as $widget ) {
746 + if ( str_contains( $widget, $widget_id ) ) {
747 + $widget_key = absint( str_replace( $widget_id . '-', '', $widget ) );
748 + break;
749 + }
750 + }
751 +
752 + // There is no widget instance.
753 + if ( ! $widget_key ) {
754 + return new WP_Error( 'invalid_data', 'No such widget.', 400 );
755 + }
756 +
757 + // Update the widget instance and option if the data has changed.
758 + if ( $widget_instances[ $widget_key ]['title'] !== $widget_options['title']
759 + || $widget_instances[ $widget_key ]['address'] !== $widget_options['address']
760 + ) {
761 +
762 + $widget_instances[ $widget_key ] = array_merge( $widget_instances[ $widget_key ], $widget_options );
763 +
764 + // Store updated widget instances and return Error when not successful.
765 + if ( ! ( update_option( 'widget_' . $widget_id, $widget_instances ) ) ) {
766 + return new WP_Error( 'widget_update_failed', 'Failed to update widget.', 400 );
767 + }
768 + }
769 + return true;
770 + }
771 +
772 + /**
773 + * Retrieve the first active sidebar.
774 + *
775 + * @return string|WP_Error First active sidebar, error if none exists.
776 + */
777 + public static function get_first_sidebar() {
778 + $active_sidebars = get_option( 'sidebars_widgets', array() );
779 + unset( $active_sidebars['wp_inactive_widgets'], $active_sidebars['array_version'] );
780 +
781 + if ( empty( $active_sidebars ) ) {
782 + return false;
783 + }
784 + $active_sidebars_keys = array_keys( $active_sidebars );
785 + return array_shift( $active_sidebars_keys );
786 + }
787 +}