Completed
Push — fix/widget-extend-and-fix-on-u... ( c91e85...434ae4 )
by
unknown
10:16
created

Jetpack_Widgets::move_widget_to_sidebar()   D

Complexity

Conditions 9
Paths 24

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 17
nc 24
nop 3
dl 0
loc 33
rs 4.909
c 0
b 0
f 0
1
<?php
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
 * @autounit api widgets
11
 */
12
13
class Jetpack_Widgets {
14
15
	/**
16
	 * Returns the `sidebars_widgets` option with the `array_version` element removed.
17
	 *
18
	 * @return array The current value of sidebars_widgets
19
	 */
20
	public static function get_sidebars_widgets() {
21
		$sidebars = get_option( 'sidebars_widgets', array() );
22
		if ( isset( $sidebars['array_version'] ) ) {
23
			unset( $sidebars['array_version'] );
24
		}
25
		return $sidebars;
26
	}
27
28
	/**
29
	 * Format widget data for output and for use by other widget functions.
30
	 *
31
	 * The output looks like:
32
	 *
33
	 * array(
34
	 *	'id' => 'text-3',
35
	 *	'sidebar' => 'sidebar-1',
36
	 *	'position' => '0',
37
	 *	'settings' => array(
38
	 *		'title' => 'hello world'
39
	 *	)
40
	 * )
41
	 *
42
	 *
43
	 * @param string|integer $position The position of the widget in its sidebar.
44
	 * @param string $widget_id The widget's id (eg: 'text-3').
45
	 * @param string $sidebar The widget's sidebar id (eg: 'sidebar-1').
46
	 * @param array (Optional) $settings The settings for the widget.
47
	 *
48
	 * @return array A normalized array representing this widget.
49
	 */
50
	public static function format_widget( $position, $widget_id, $sidebar, $settings = null ) {
51
		if ( ! $settings ) {
52
			$all_settings = get_option( self::get_widget_option_name( $widget_id ) );
53
			$instance = self::get_widget_instance_key( $widget_id );
54
			$settings = $all_settings[$instance];
55
		}
56
		$widget = array();
57
58
		$widget['id']       = $widget_id;
59
		$widget['id_base']  = self::get_widget_id_base( $widget_id );
60
		$widget['settings'] = $settings;
61
		$widget['sidebar']  = $sidebar;
62
		$widget['position'] = $position;
63
64
		return $widget;
65
	}
66
67
	/**
68
	 * Return a widget's id_base from its id.
69
	 *
70
	 * @param string $widget_id The id of a widget. (eg: 'text-3')
71
	 *
72
	 * @return string The id_base of a widget (eg: 'text').
73
	 */
74
	public static function get_widget_id_base( $widget_id ) {
75
		// Grab what's before the hyphen.
76
		return substr( $widget_id, 0, strrpos( $widget_id, '-' ) );
77
	}
78
79
	/**
80
	 * Determine a widget's option name (the WP option where the widget's settings
81
	 * are stored - generally `widget_` + the widget's id_base).
82
	 *
83
	 * @param string $widget_id The id of a widget. (eg: 'text-3')
84
	 *
85
	 * @return string The option name of the widget's settings. (eg: 'widget_text')
86
	 */
87
	public static function get_widget_option_name( $widget_id ) {
88
		return 'widget_' . self::get_widget_id_base( $widget_id );
89
	}
90
91
	/**
92
	 * Determine a widget instance key from its ID. (eg: 'text-3' becomes '3').
93
	 * Used to access the widget's settings.
94
	 *
95
	 * @param string $widget_id The id of a widget.
96
	 *
97
	 * @return integer The instance key of that widget.
98
	 */
99
	public static function get_widget_instance_key( $widget_id ) {
100
		// Grab all numbers from the end of the id.
101
		preg_match('/(\d+)$/', $widget_id, $matches );
102
103
		return intval( $matches[0] );
104
	}
105
106
	/**
107
	 * Return a widget by ID (formatted for output) or null if nothing is found.
108
	 *
109
	 * @param string $widget_id The id of a widget to look for.
110
	 *
111
	 * @return array|null The matching formatted widget (see format_widget).
112
	 */
113
	public static function get_widget_by_id( $widget_id ) {
114
		$found = null;
115
		foreach ( self::get_all_widgets() as $widget ) {
116
			if ( $widget['id'] === $widget_id ) {
117
				$found = $widget;
118
			}
119
		}
120
		return $found;
121
	}
122
123
	/**
124
	 * Return a widget by base ID or null if nothing is found.
125
	 *
126
	 * @param string $id_base The id of a widget to look for.
127
	 *
128
	 * @return array|null The matching formatted widget (see format_widget).
129
	 */
130
	public static function get_widget_by_id_base( $id_base ) {
131
		$found = null;
132
		foreach ( self::get_all_widgets() as $widget ) {
133
			if ( $widget['id_base'] === $id_base ) {
134
				$found = $widget;
135
			}
136
		}
137
		return $found;
138
	}
139
140
	/**
141
	 * Return an array of all widgets (active and inactive) formatted for output.
142
	 *
143
	 * @return array An array of all widgets (see format_widget).
144
	 */
145
	public static function get_all_widgets() {
146
		$all_widgets = array();
147
		$sidebars_widgets = self::get_all_sidebars();
148
149
		foreach ( $sidebars_widgets as $sidebar => $widgets ) {
150
			if ( ! is_array( $widgets ) ) {
151
				continue;
152
			}
153
			foreach ( $widgets as $key => $widget_id ) {
154
				array_push( $all_widgets, self::format_widget( $key, $widget_id, $sidebar ) );
155
			}
156
		}
157
158
		return $all_widgets;
159
	}
160
161
	/**
162
	 * Return an array of all active widgets formatted for output.
163
	 *
164
	 * @return array An array of all active widgets (see format_widget).
165
	 */
166
	public static function get_active_widgets() {
167
		$active_widgets = array();
168
		$all_widgets = self::get_all_widgets();
169
		foreach( $all_widgets as $widget ) {
170
			if ( 'wp_inactive_widgets' === $widget['sidebar'] ) {
171
				continue;
172
			}
173
			array_push( $active_widgets, $widget );
174
		}
175
		return $active_widgets;
176
	}
177
178
	/**
179
	 * Return an array of all widget IDs (active and inactive)
180
	 *
181
	 * @return array An array of all widget IDs.
182
	 */
183
	public static function get_all_widget_ids() {
184
		$all_widgets = array();
185
		$sidebars_widgets = self::get_all_sidebars();
186
		foreach ( array_values( $sidebars_widgets ) as $widgets ) {
187
			if ( ! is_array( $widgets ) ) {
188
				continue;
189
			}
190
			foreach ( array_values( $widgets ) as $widget_id ) {
191
				array_push( $all_widgets, $widget_id );
192
			}
193
		}
194
		return $all_widgets;
195
	}
196
197
	/**
198
	 * Return an array of widgets with a specific id_base (eg: `text`).
199
	 *
200
	 * @param string $id_base The id_base of a widget type.
201
	 *
202
	 * @return array All the formatted widgets matching that widget type (see format_widget).
203
	 */
204
	public static function get_widgets_with_id_base( $id_base ) {
205
		$matching_widgets = array();
206
		foreach ( self::get_all_widgets() as $widget ) {
207
			if ( self::get_widget_id_base( $widget['id'] ) === $id_base ) {
208
				array_push( $matching_widgets, $widget );
209
			}
210
		}
211
		return $matching_widgets;
212
	}
213
214
	/**
215
	 * Return the array of widget IDs in a sidebar or null if that sidebar does
216
	 * not exist. Will return an empty array for an existing empty sidebar.
217
	 *
218
	 * @param string $sidebar The id of a sidebar.
219
	 *
220
	 * @return array|null The array of widget IDs in the sidebar.
221
	 */
222
	public static function get_widgets_in_sidebar( $sidebar ) {
223
		$sidebars = self::get_all_sidebars();
224
225
226
		if ( ! $sidebars || ! is_array( $sidebars ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sidebars of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
227
			return null;
228
		}
229
		if ( ! $sidebars[ $sidebar ] && array_key_exists( $sidebar, $sidebars ) ) {
230
			return array();
231
		}
232
		return $sidebars[ $sidebar ];
233
	}
234
235
	/**
236
	 * Return an associative array of all registered sidebars for this theme,
237
	 * active and inactive, including the hidden disabled widgets sidebar (keyed
238
	 * by `wp_inactive_widgets`). Each sidebar is keyed by the ID of the sidebar
239
	 * and its value is an array of widget IDs for that sidebar.
240
	 *
241
	 * @return array An associative array of all sidebars and their widget IDs.
242
	 */
243
	public static function get_all_sidebars() {
244
		$sidebars_widgets = self::get_sidebars_widgets();
245
246
		if ( ! is_array( $sidebars_widgets ) ) {
247
			return array();
248
		}
249
		return $sidebars_widgets;
250
	}
251
252
	/**
253
	 * Return an associative array of all active sidebars for this theme, Each
254
	 * sidebar is keyed by the ID of the sidebar and its value is an array of
255
	 * widget IDs for that sidebar.
256
	 *
257
	 * @return array An associative array of all active sidebars and their widget IDs.
258
	 */
259
	public static function get_active_sidebars() {
260
		$sidebars = array();
261
		foreach ( self::get_all_sidebars() as $sidebar => $widgets ) {
262
			if ( 'wp_inactive_widgets' === $sidebar || ! isset( $widgets ) || ! is_array( $widgets ) ) {
263
				continue;
264
			}
265
			$sidebars[ $sidebar ] = $widgets;
266
		}
267
		return $sidebars;
268
	}
269
270
	/**
271
	 * Activates a widget in a sidebar. Does not validate that the sidebar exists,
272
	 * so please do that first. Also does not save the widget's settings. Please
273
	 * do that with `set_widget_settings`.
274
	 *
275
	 * If position is not set, it will be set to the next available position.
276
	 *
277
	 * @param string         $widget_id The newly-formed id of the widget to be added.
278
	 * @param string         $sidebar   The id of the sidebar where the widget will be added.
279
	 * @param string|integer $position  (Optional) The position within the sidebar where the widget will be added.
280
	 *
281
	 * @return bool
282
	 */
283
	public static function add_widget_to_sidebar( $widget_id, $sidebar, $position ) {
284
		return self::move_widget_to_sidebar( array( 'id' => $widget_id ), $sidebar, $position );
285
	}
286
287
	/**
288
	 * Removes a widget from a sidebar. Does not validate that the sidebar exists
289
	 * or remove any settings from the widget, so please do that separately.
290
	 *
291
	 * @param array $widget The widget to be removed.
292
	 */
293
	public static function remove_widget_from_sidebar( $widget ) {
294
		$sidebars_widgets = self::get_sidebars_widgets();
295
		// Remove the widget from its old location and reflow the positions of the remaining widgets.
296
		array_splice( $sidebars_widgets[ $widget['sidebar'] ], $widget['position'], 1 );
297
298
		update_option( 'sidebars_widgets', $sidebars_widgets );
299
	}
300
301
	/**
302
	 * Moves a widget to a sidebar. Does not validate that the sidebar exists,
303
	 * so please do that first. Also does not save the widget's settings. Please
304
	 * do that with `set_widget_settings`. The first argument should be a
305
	 * widget as returned by `format_widget` including `id`, `sidebar`, and
306
	 * `position`.
307
	 *
308
	 * If $position is not set, it will be set to the next available position.
309
	 *
310
	 * Can be used to add a new widget to a sidebar if
311
	 * $widget['sidebar'] === NULL
312
	 *
313
	 * Can be used to move a widget within a sidebar as well if
314
	 * $widget['sidebar'] === $sidebar.
315
	 *
316
	 * @param array          $widget   The widget to be moved (see format_widget).
317
	 * @param string         $sidebar  The sidebar where this widget will be moved.
318
	 * @param string|integer $position (Optional) The position where this widget will be moved in the sidebar.
319
	 *
320
	 * @return bool
321
	 */
322
	public static function move_widget_to_sidebar( $widget, $sidebar, $position ) {
323
		$sidebars_widgets = self::get_sidebars_widgets();
324
325
		// If a position is passed and the sidebar isn't empty,
326
		// splice the widget into the sidebar, update the sidebar option, and return the result
327
		if ( isset( $widget['sidebar'] ) && isset( $widget['position'] ) ) {
328
			array_splice( $sidebars_widgets[ $widget['sidebar'] ], $widget['position'], 1 );
329
		}
330
331
		// Sometimes an existing empty sidebar is NULL, so initialize it.
332
		if ( array_key_exists( $sidebar, $sidebars_widgets ) && ! is_array( $sidebars_widgets[ $sidebar ] ) ) {
333
			$sidebars_widgets[ $sidebar ] = array();
334
		}
335
336
		// If no position is passed, set one from items in sidebar
337
		if ( ! isset( $position ) ) {
338
			$position = 0;
339
			$last_position = self::get_last_position_in_sidebar( $sidebar );
340
			if ( isset( $last_position ) && is_numeric( $last_position ) ) {
341
				$position = $last_position + 1;
342
			}
343
		}
344
345
		// Add the widget to the sidebar and reflow the positions of the other widgets.
346
		if ( empty( $sidebars_widgets[ $sidebar ] ) ) {
347
			$sidebars_widgets[ $sidebar ][] = $widget['id'];
348
		} else {
349
			array_splice( $sidebars_widgets[ $sidebar ], (int)$position, 0, $widget['id'] );
350
		}
351
352
		set_theme_mod( 'sidebars_widgets', array( 'time' => time(), 'data' => $sidebars_widgets ) );
353
		return update_option( 'sidebars_widgets', $sidebars_widgets );
354
	}
355
356
	/**
357
	 * Return an integer containing the largest position number in a sidebar or
358
	 * null if there are no widgets in that sidebar.
359
	 *
360
	 * @param string $sidebar The id of a sidebar.
361
	 *
362
	 * @return integer|null The last index position of a widget in that sidebar.
363
	 */
364
	public static function get_last_position_in_sidebar( $sidebar ) {
365
		$widgets = self::get_widgets_in_sidebar( $sidebar );
366
		if ( ! $widgets ) {
367
			return null;
368
		}
369
		$last_position = 0;
370
		foreach ( $widgets as $widget_id ) {
371
			$widget = self::get_widget_by_id( $widget_id );
372
			if ( intval( $widget['position'] ) > intval( $last_position ) ) {
373
				$last_position = intval( $widget['position'] );
374
			}
375
		}
376
		return $last_position;
377
	}
378
379
	/**
380
	 * Saves settings for a widget. Does not add that widget to a sidebar. Please
381
	 * do that with `move_widget_to_sidebar` first. Will merge the settings of
382
	 * any existing widget with the same `$widget_id`.
383
	 *
384
	 * @param string $widget_id The id of a widget.
385
	 * @param array $settings An associative array of settings to merge with any existing settings on this widget.
386
	 *
387
	 * @return boolean|WP_Error True if update was successful.
388
	 */
389
	public static function set_widget_settings( $widget_id, $settings ) {
390
		$widget_option_name = self::get_widget_option_name( $widget_id );
391
		$widget_settings = get_option( $widget_option_name );
392
		$instance_key = self::get_widget_instance_key( $widget_id );
393
394
		// if the widget's content already exists
395
		if ( isset( $widget_settings[ $instance_key ] ) ) {
396
			$old_settings = $widget_settings[ $instance_key ];
397
398
			if ( ! $settings = self::sanitize_widget_settings( $widget_id, $settings, $old_settings ) ) {
399
				return new WP_Error( 'invalid_data', 'Update failed.', 500 );
400
			}
401
			if ( is_array( $old_settings ) ) {
402
				// array_filter prevents empty arguments from replacing existing ones
403
				$settings = wp_parse_args( array_filter( $settings ), $old_settings );
404
			}
405
		}
406
		$widget_settings[ $instance_key ] = $settings;
407
408
		return update_option( $widget_option_name, $widget_settings );
409
	}
410
411
	/**
412
	 * Sanitize an associative array for saving.
413
	 *
414
	 * @param string $widget_id The id of a widget.
415
	 * @param array $settings A widget settings array.
416
	 * @param array $old_settings The existing widget settings array.
417
	 *
418
	 * @return array|false The settings array sanitized by `WP_Widget::update` or false if sanitization failed.
419
	 */
420
	private static function sanitize_widget_settings( $widget_id, $settings, $old_settings ) {
421
		if ( ! $widget = self::get_registered_widget_object( self::get_widget_id_base( $widget_id ) ) ) {
422
			return false;
423
		}
424
		$new_settings = $widget->update( $settings, $old_settings );
425
		if ( ! is_array( $new_settings ) ) {
426
			return false;
427
		}
428
		return $new_settings;
429
	}
430
431
	/**
432
	 * Deletes settings for a widget. Does not remove that widget to a sidebar. Please
433
	 * do that with `remove_widget_from_sidebar` first.
434
	 *
435
	 * @param array $widget The widget which will have its settings removed (see format_widget).
436
	 */
437
	public static function remove_widget_settings( $widget ) {
438
		$widget_option_name = self::get_widget_option_name( $widget['id'] );
439
		$widget_settings = get_option( $widget_option_name );
440
		unset( $widget_settings[ self::get_widget_instance_key( $widget['id'] ) ] );
441
		update_option( $widget_option_name, $widget_settings );
442
	}
443
444
	/**
445
	 * Update a widget's settings, sidebar, and position. Returns the (updated)
446
	 * formatted widget if successful or a WP_Error if it fails.
447
	 *
448
	 * @param string $widget_id The id of a widget to update.
449
	 * @param string $sidebar (Optional) A sidebar to which this widget will be moved.
450
	 * @param string|integer (Optional) A new position to which this widget will be moved within its new or existing sidebar.
451
	 * @param array|object|string $settings Settings to merge with the existing settings of the widget (will be passed through `decode_settings`).
452
	 *
453
	 * @return array|WP_Error The newly added widget as an associative array with all the above properties.
454
	 */
455
	public static function update_widget( $widget_id, $sidebar, $position, $settings ) {
456
		$settings = self::decode_settings( $settings );
457
		if ( isset( $settings ) && ! is_array( $settings ) ) {
458
			return new WP_Error( 'invalid_data', 'Invalid settings', 400 );
459
		}
460
		// Default to an empty array if nothing is specified.
461
		if ( ! is_array( $settings ) ) {
462
			$settings = array();
463
		}
464
		$widget = self::get_widget_by_id( $widget_id );
465
		if ( ! $widget ) {
466
			return new WP_Error( 'not_found', 'No widget found.', 400 );
467
		}
468
		if ( ! $sidebar ) {
469
			$sidebar = $widget['sidebar'];
470
		}
471
		if ( ! isset( $position ) ) {
472
			$position = $widget['position'];
473
		}
474
		if ( ! is_numeric( $position ) ) {
475
			return new WP_Error( 'invalid_data', 'Invalid position', 400 );
476
		}
477
		$widgets_in_sidebar = self::get_widgets_in_sidebar( $sidebar );
478
		if ( ! isset( $widgets_in_sidebar ) ) {
479
			return new WP_Error( 'invalid_data', 'No such sidebar exists', 400 );
480
		}
481
		$widget_save_status = self::set_widget_settings( $widget_id, $settings );
482
		if ( is_wp_error( $widget_save_status ) ) {
483
			return $widget_save_status;
484
		}
485
		return self::get_widget_by_id( $widget_id );
486
	}
487
488
	/**
489
	 * Deletes a widget entirely including all its settings. Returns a WP_Error if
490
	 * the widget could not be found. Otherwise returns an empty array.
491
	 *
492
	 * @param string $widget_id The id of a widget to delete. (eg: 'text-2')
493
	 *
494
	 * @return array|WP_Error An empty array if successful.
495
	 */
496
	public static function delete_widget( $widget_id ) {
497
		$widget = self::get_widget_by_id( $widget_id );
498
		if ( ! $widget ) {
499
			return new WP_Error( 'not_found', 'No widget found.', 400 );
500
		}
501
		self::remove_widget_from_sidebar( $widget );
502
		self::remove_widget_settings( $widget );
503
		return array();
504
	}
505
506
	/**
507
	 * Return an array of settings. The input can be either an object, a JSON
508
	 * string, or an array.
509
	 *
510
	 * @param array|string|object $settings The settings of a widget as passed into the API.
511
	 *
512
	 * @return array Decoded associative array of settings.
513
	 */
514
	public static function decode_settings( $settings ) {
515
		// Treat as string in case JSON was passed
516
		if ( is_object( $settings ) && property_exists( $settings, 'scalar' ) ) {
517
			$settings = $settings->scalar;
518
		}
519
		if ( is_object( $settings ) ) {
520
			$settings = (array) $settings;
521
		}
522
		// Attempt to decode JSON string
523
		if ( is_string( $settings ) ) {
524
			$settings = (array) json_decode( $settings );
525
		}
526
		return $settings;
527
	}
528
529
	/**
530
	 * Activate a new widget.
531
	 *
532
	 * @param string $id_base The id_base of the new widget (eg: 'text')
533
	 * @param string $sidebar The id of the sidebar where this widget will go. Dependent on theme. (eg: 'sidebar-1')
534
	 * @param string|integer $position (Optional) The position of the widget in the sidebar. Defaults to the last position.
535
	 * @param array|object|string $settings (Optional) An associative array of settings for this widget (will be passed through `decode_settings`). Varies by widget.
536
	 *
537
	 * @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'.
538
	 */
539
	public static function activate_widget( $id_base, $sidebar, $position, $settings ) {
540
		if ( ! isset( $id_base ) || ! self::validate_id_base( $id_base ) ) {
541
			return new WP_Error( 'invalid_data', 'Invalid ID base', 400 );
542
		}
543
544
		if ( ! isset( $sidebar ) ) {
545
			return new WP_Error( 'invalid_data', 'No sidebar provided', 400 );
546
		}
547
548
		if ( isset( $position ) && ! is_numeric( $position ) ) {
549
			return new WP_Error( 'invalid_data', 'Invalid position', 400 );
550
		}
551
552
		$settings = self::decode_settings( $settings );
553
		if ( isset( $settings ) && ! is_array( $settings ) ) {
554
			return new WP_Error( 'invalid_data', 'Invalid settings', 400 );
555
		}
556
557
		// Default to an empty array if nothing is specified.
558
		if ( ! is_array( $settings ) ) {
559
			$settings = array();
560
		}
561
562
		$widget_counter = 1 + self::get_last_widget_instance_key_with_id_base( $id_base );
563
		$widget_id = $id_base . '-' . $widget_counter;
564
		if ( 0 >= $widget_counter ) {
565
			return new WP_Error( 'invalid_data', 'Error creating widget ID' . $widget_id, 500 );
566
		}
567
		if ( self::get_widget_by_id( $widget_id ) ) {
568
			return new WP_Error( 'invalid_data', 'Widget ID already exists', 500 );
569
		}
570
571
		self::add_widget_to_sidebar( $widget_id, $sidebar, $position );
572
		$widget_save_status = self::set_widget_settings( $widget_id, $settings );
573
		if ( is_wp_error( $widget_save_status ) ) {
574
			return $widget_save_status;
575
		}
576
577
		// Add a Tracks event for non-Headstart activity.
578
		if ( ! defined( 'HEADSTART' ) ) {
579
			jetpack_require_lib( 'tracks/client' );
580
			jetpack_tracks_record_event( wp_get_current_user(), 'wpcom_widgets_activate_widget', array(
581
				'widget' => $id_base,
582
				'settings' => json_encode( $settings ),
583
			) );
584
		}
585
586
		return self::get_widget_by_id( $widget_id );
587
	}
588
589
	/**
590
	 * Activate an array of new widgets. Like calling `activate_widget` multiple times.
591
	 *
592
	 * @param array $widgets An array of widget arrays. Each sub-array must be of the format required by `activate_widget`.
593
	 *
594
	 * @return array|WP_Error The newly added widgets in the form returned by `get_all_widgets`.
595
	 */
596
	public static function activate_widgets( $widgets ) {
597
		if ( ! is_array( $widgets ) ) {
598
			return new WP_Error( 'invalid_data', 'Invalid widgets', 400 );
599
		}
600
601
		$added_widgets = array();
602
603
		foreach( $widgets as $widget ) {
604
			$added_widgets[] = self::activate_widget( $widget['id_base'], $widget['sidebar'], $widget['position'], $widget['settings'] );
605
		}
606
607
		return $added_widgets;
608
	}
609
610
	/**
611
	 * Return the last instance key (integer) of an existing widget matching
612
	 * `$id_base`. So if you pass in `text`, and there is a widget with the id
613
	 * `text-2`, this function will return `2`.
614
	 *
615
	 * @param string $id_base The id_base of a type of widget. (eg: 'rss')
616
	 *
617
	 * @return integer The last instance key of that type of widget.
618
	 */
619
	public static function get_last_widget_instance_key_with_id_base( $id_base ) {
620
		$similar_widgets = self::get_widgets_with_id_base( $id_base );
621
622
		if ( ! empty( $similar_widgets ) ) {
623
			// If the last widget with the same name is `text-3`, we want `text-4`
624
			usort( $similar_widgets, __CLASS__ . '::sort_widgets' );
625
626
			$last_widget = array_pop( $similar_widgets );
627
			$last_val = intval( self::get_widget_instance_key( $last_widget['id'] ) );
628
629
			return $last_val;
630
		}
631
632
		return 0;
633
	}
634
635
	/**
636
	 * Method used to sort widgets
637
	 *
638
	 * @since 5.4
639
	 *
640
	 * @param array $a
641
	 * @param array $b
642
	 *
643
	 * @return int
644
	 */
645
	public static function sort_widgets( $a, $b ) {
646
		$a_val = intval( self::get_widget_instance_key( $a['id'] ) );
647
		$b_val = intval( self::get_widget_instance_key( $b['id'] ) );
648
		if ( $a_val > $b_val ) {
649
			return 1;
650
		}
651
		if ( $a_val < $b_val ) {
652
			return -1;
653
		}
654
		return 0;
655
	}
656
657
	/**
658
	 * Retrieve a given widget object instance by ID base (eg. 'text' or 'archives').
659
	 *
660
	 * @param string $id_base The id_base of a type of widget.
661
	 *
662
	 * @return WP_Widget|false The found widget object or false if the id_base was not found.
663
	 */
664
	public static function get_registered_widget_object( $id_base ) {
665
		if ( ! $id_base ) {
666
			return false;
667
		}
668
669
		// Get all of the registered widgets.
670
		global $wp_widget_factory;
671
		if ( ! isset( $wp_widget_factory ) ) {
672
			return false;
673
		}
674
675
		$registered_widgets = $wp_widget_factory->widgets;
676
		if ( empty( $registered_widgets ) ) {
677
			return false;
678
		}
679
680
		foreach ( array_values( $registered_widgets ) as $registered_widget_object ) {
681
			if ( $registered_widget_object->id_base === $id_base ) {
682
				return $registered_widget_object;
683
			}
684
		}
685
		return false;
686
	}
687
688
	/**
689
	 * Validate a given widget ID base (eg. 'text' or 'archives').
690
	 *
691
	 * @param string $id_base The id_base of a type of widget.
692
	 *
693
	 * @return boolean True if the widget is of a known type.
694
	 */
695
	public static function validate_id_base( $id_base ) {
696
		return ( false !== self::get_registered_widget_object( $id_base ) );
697
	}
698
}
699