Completed
Push — add/jpo-save-business-address ( aa6ccf...ea3a90 )
by
unknown
62:49 queued 41:17
created

Jetpack_Widgets::get_last_position_in_sidebar()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 4
nop 1
dl 0
loc 14
rs 9.2
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
		foreach ( self::get_all_widgets() as $widget ) {
115
			if ( $widget['id'] === $widget_id ) {
116
				return $widget;
117
			}
118
		}
119
		return null;
120
	}
121
122
	/**
123
	 * Return a widget by base ID or null if nothing is found.
124
	 *
125
	 * @param string $id_base The id of a widget to look for.
126
	 *
127
	 * @return array|null The matching formatted widget (see format_widget).
128
	 */
129
	public static function get_widget_by_id_base( $id_base ) {
130
		foreach ( self::get_all_widgets() as $widget ) {
131
			if ( $widget['id_base'] === $id_base ) {
132
				return $widget;
133
			}
134
		}
135
		return null;
136
	}
137
138
	/**
139
	 * Return an array of all widgets (active and inactive) formatted for output.
140
	 *
141
	 * @return array An array of all widgets (see format_widget).
142
	 */
143
	public static function get_all_widgets() {
144
		$all_widgets = array();
145
		$sidebars_widgets = self::get_all_sidebars();
146
147
		foreach ( $sidebars_widgets as $sidebar => $widgets ) {
148
			if ( ! is_array( $widgets ) ) {
149
				continue;
150
			}
151
			foreach ( $widgets as $key => $widget_id ) {
152
				array_push( $all_widgets, self::format_widget( $key, $widget_id, $sidebar ) );
153
			}
154
		}
155
156
		return $all_widgets;
157
	}
158
159
	/**
160
	 * Return an array of all active widgets formatted for output.
161
	 *
162
	 * @return array An array of all active widgets (see format_widget).
163
	 */
164
	public static function get_active_widgets() {
165
		$active_widgets = array();
166
		$all_widgets = self::get_all_widgets();
167
		foreach( $all_widgets as $widget ) {
168
			if ( 'wp_inactive_widgets' === $widget['sidebar'] ) {
169
				continue;
170
			}
171
			array_push( $active_widgets, $widget );
172
		}
173
		return $active_widgets;
174
	}
175
176
	/**
177
	 * Return an array of all widget IDs (active and inactive)
178
	 *
179
	 * @return array An array of all widget IDs.
180
	 */
181
	public static function get_all_widget_ids() {
182
		$all_widgets = array();
183
		$sidebars_widgets = self::get_all_sidebars();
184
		foreach ( array_values( $sidebars_widgets ) as $widgets ) {
185
			if ( ! is_array( $widgets ) ) {
186
				continue;
187
			}
188
			foreach ( array_values( $widgets ) as $widget_id ) {
189
				array_push( $all_widgets, $widget_id );
190
			}
191
		}
192
		return $all_widgets;
193
	}
194
195
	/**
196
	 * Return an array of widgets with a specific id_base (eg: `text`).
197
	 *
198
	 * @param string $id_base The id_base of a widget type.
199
	 *
200
	 * @return array All the formatted widgets matching that widget type (see format_widget).
201
	 */
202
	public static function get_widgets_with_id_base( $id_base ) {
203
		$matching_widgets = array();
204
		foreach ( self::get_all_widgets() as $widget ) {
205
			if ( self::get_widget_id_base( $widget['id'] ) === $id_base ) {
206
				array_push( $matching_widgets, $widget );
207
			}
208
		}
209
		return $matching_widgets;
210
	}
211
212
	/**
213
	 * Return the array of widget IDs in a sidebar or null if that sidebar does
214
	 * not exist. Will return an empty array for an existing empty sidebar.
215
	 *
216
	 * @param string $sidebar The id of a sidebar.
217
	 *
218
	 * @return array|null The array of widget IDs in the sidebar.
219
	 */
220
	public static function get_widgets_in_sidebar( $sidebar ) {
221
		$sidebars = self::get_all_sidebars();
222
223
		if ( ! $sidebars || ! is_array( $sidebars ) ) {
224
			return null;
225
		}
226
		if ( ! $sidebars[ $sidebar ] && array_key_exists( $sidebar, $sidebars ) ) {
227
			return array();
228
		}
229
		return $sidebars[ $sidebar ];
230
	}
231
232
	/**
233
	 * Return an associative array of all registered sidebars for this theme,
234
	 * active and inactive, including the hidden disabled widgets sidebar (keyed
235
	 * by `wp_inactive_widgets`). Each sidebar is keyed by the ID of the sidebar
236
	 * and its value is an array of widget IDs for that sidebar.
237
	 *
238
	 * @return array An associative array of all sidebars and their widget IDs.
239
	 */
240
	public static function get_all_sidebars() {
241
		$sidebars_widgets = self::get_sidebars_widgets();
242
243
		if ( ! is_array( $sidebars_widgets ) ) {
244
			return array();
245
		}
246
		return $sidebars_widgets;
247
	}
248
249
	/**
250
	 * Return an associative array of all active sidebars for this theme, Each
251
	 * sidebar is keyed by the ID of the sidebar and its value is an array of
252
	 * widget IDs for that sidebar.
253
	 *
254
	 * @return array An associative array of all active sidebars and their widget IDs.
255
	 */
256
	public static function get_active_sidebars() {
257
		$sidebars = array();
258
		foreach ( self::get_all_sidebars() as $sidebar => $widgets ) {
259
			if ( 'wp_inactive_widgets' === $sidebar || ! isset( $widgets ) || ! is_array( $widgets ) ) {
260
				continue;
261
			}
262
			$sidebars[ $sidebar ] = $widgets;
263
		}
264
		return $sidebars;
265
	}
266
267
	/**
268
	 * Activates a widget in a sidebar. Does not validate that the sidebar exists,
269
	 * so please do that first. Also does not save the widget's settings. Please
270
	 * do that with `set_widget_settings`.
271
	 *
272
	 * If position is not set, it will be set to the next available position.
273
	 *
274
	 * @param string         $widget_id The newly-formed id of the widget to be added.
275
	 * @param string         $sidebar   The id of the sidebar where the widget will be added.
276
	 * @param string|integer $position  (Optional) The position within the sidebar where the widget will be added.
277
	 *
278
	 * @return bool
279
	 */
280
	public static function add_widget_to_sidebar( $widget_id, $sidebar, $position ) {
281
		return self::move_widget_to_sidebar( array( 'id' => $widget_id ), $sidebar, $position );
282
	}
283
284
	/**
285
	 * Removes a widget from a sidebar. Does not validate that the sidebar exists
286
	 * or remove any settings from the widget, so please do that separately.
287
	 *
288
	 * @param array $widget The widget to be removed.
289
	 */
290
	public static function remove_widget_from_sidebar( $widget ) {
291
		$sidebars_widgets = self::get_sidebars_widgets();
292
		// Remove the widget from its old location and reflow the positions of the remaining widgets.
293
		array_splice( $sidebars_widgets[ $widget['sidebar'] ], $widget['position'], 1 );
294
295
		update_option( 'sidebars_widgets', $sidebars_widgets );
296
	}
297
298
	/**
299
	 * Moves a widget to a sidebar. Does not validate that the sidebar exists,
300
	 * so please do that first. Also does not save the widget's settings. Please
301
	 * do that with `set_widget_settings`. The first argument should be a
302
	 * widget as returned by `format_widget` including `id`, `sidebar`, and
303
	 * `position`.
304
	 *
305
	 * If $position is not set, it will be set to the next available position.
306
	 *
307
	 * Can be used to add a new widget to a sidebar if
308
	 * $widget['sidebar'] === NULL
309
	 *
310
	 * Can be used to move a widget within a sidebar as well if
311
	 * $widget['sidebar'] === $sidebar.
312
	 *
313
	 * @param array          $widget   The widget to be moved (see format_widget).
314
	 * @param string         $sidebar  The sidebar where this widget will be moved.
315
	 * @param string|integer $position (Optional) The position where this widget will be moved in the sidebar.
316
	 *
317
	 * @return bool
318
	 */
319
	public static function move_widget_to_sidebar( $widget, $sidebar, $position ) {
320
		$sidebars_widgets = self::get_sidebars_widgets();
321
322
		// If a position is passed and the sidebar isn't empty,
323
		// splice the widget into the sidebar, update the sidebar option, and return the result
324
		if ( isset( $widget['sidebar'] ) && isset( $widget['position'] ) ) {
325
			array_splice( $sidebars_widgets[ $widget['sidebar'] ], $widget['position'], 1 );
326
		}
327
328
		// Sometimes an existing empty sidebar is NULL, so initialize it.
329
		if ( array_key_exists( $sidebar, $sidebars_widgets ) && ! is_array( $sidebars_widgets[ $sidebar ] ) ) {
330
			$sidebars_widgets[ $sidebar ] = array();
331
		}
332
333
		// If no position is passed, set one from items in sidebar
334
		if ( ! isset( $position ) ) {
335
			$position = 0;
336
			$last_position = self::get_last_position_in_sidebar( $sidebar );
337
			if ( isset( $last_position ) && is_numeric( $last_position ) ) {
338
				$position = $last_position + 1;
339
			}
340
		}
341
342
		// Add the widget to the sidebar and reflow the positions of the other widgets.
343
		if ( empty( $sidebars_widgets[ $sidebar ] ) ) {
344
			$sidebars_widgets[ $sidebar ][] = $widget['id'];
345
		} else {
346
			array_splice( $sidebars_widgets[ $sidebar ], (int)$position, 0, $widget['id'] );
347
		}
348
349
		set_theme_mod( 'sidebars_widgets', array( 'time' => time(), 'data' => $sidebars_widgets ) );
350
		return update_option( 'sidebars_widgets', $sidebars_widgets );
351
	}
352
353
	/**
354
	 * Return an integer containing the largest position number in a sidebar or
355
	 * null if there are no widgets in that sidebar.
356
	 *
357
	 * @param string $sidebar The id of a sidebar.
358
	 *
359
	 * @return integer|null The last index position of a widget in that sidebar.
360
	 */
361
	public static function get_last_position_in_sidebar( $sidebar ) {
362
		$widgets = self::get_widgets_in_sidebar( $sidebar );
363
		if ( ! $widgets ) {
364
			return null;
365
		}
366
		$last_position = 0;
367
		foreach ( $widgets as $widget_id ) {
368
			$widget = self::get_widget_by_id( $widget_id );
369
			if ( intval( $widget['position'] ) > intval( $last_position ) ) {
370
				$last_position = intval( $widget['position'] );
371
			}
372
		}
373
		return $last_position;
374
	}
375
376
	/**
377
	 * Saves settings for a widget. Does not add that widget to a sidebar. Please
378
	 * do that with `move_widget_to_sidebar` first. Will merge the settings of
379
	 * any existing widget with the same `$widget_id`.
380
	 *
381
	 * @param string $widget_id The id of a widget.
382
	 * @param array $settings An associative array of settings to merge with any existing settings on this widget.
383
	 *
384
	 * @return boolean|WP_Error True if update was successful.
385
	 */
386
	public static function set_widget_settings( $widget_id, $settings ) {
387
		$widget_option_name = self::get_widget_option_name( $widget_id );
388
		$widget_settings = get_option( $widget_option_name );
389
		$instance_key = self::get_widget_instance_key( $widget_id );
390
391
		if ( ! $widget = self::get_registered_widget_object( self::get_widget_id_base( $widget_id ) ) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
392
			return new WP_Error( 'invalid_data', 'Invalid ID base.', 400 );
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_RETURN
Loading history...
393
		}
394
395
		// if the widget's content already exists
396
		if ( isset( $widget_settings[ $instance_key ] ) ) {
397
			$old_settings = $widget_settings[ $instance_key ];
398
399
			if ( ! $settings = self::sanitize_widget_settings( $widget_id, $settings, $old_settings ) ) {
400
				return new WP_Error( 'invalid_data', 'Update failed.', 400 );
401
			}
402
			if ( is_array( $old_settings ) ) {
403
				// array_filter prevents empty arguments from replacing existing ones
404
				$settings = wp_parse_args( array_filter( $settings ), $old_settings );
405
			}
406
		}
407
		$widget_settings[ $instance_key ] = $settings;
408
409
		return update_option( $widget_option_name, $widget_settings );
410
	}
411
412
	/**
413
	 * Sanitize an associative array for saving.
414
	 *
415
	 * @param string $widget_id The id of a widget.
416
	 * @param array $settings A widget settings array.
417
	 * @param array $old_settings The existing widget settings array.
418
	 *
419
	 * @return array|false The settings array sanitized by `WP_Widget::update` or false if sanitization failed.
420
	 */
421
	private static function sanitize_widget_settings( $widget_id, $settings, $old_settings ) {
422
		if ( ! $widget = self::get_registered_widget_object( self::get_widget_id_base( $widget_id ) ) ) {
423
			return false;
424
		}
425
		$new_settings = $widget->update( $settings, $old_settings );
426
		if ( ! is_array( $new_settings ) ) {
427
			return false;
428
		}
429
		return $new_settings;
430
	}
431
432
	/**
433
	 * Deletes settings for a widget. Does not remove that widget to a sidebar. Please
434
	 * do that with `remove_widget_from_sidebar` first.
435
	 *
436
	 * @param array $widget The widget which will have its settings removed (see format_widget).
437
	 */
438
	public static function remove_widget_settings( $widget ) {
439
		$widget_option_name = self::get_widget_option_name( $widget['id'] );
440
		$widget_settings = get_option( $widget_option_name );
441
		unset( $widget_settings[ self::get_widget_instance_key( $widget['id'] ) ] );
442
		update_option( $widget_option_name, $widget_settings );
443
	}
444
445
	/**
446
	 * Update a widget's settings, sidebar, and position. Returns the (updated)
447
	 * formatted widget if successful or a WP_Error if it fails.
448
	 *
449
	 * @param string $widget_id The id of a widget to update.
450
	 * @param string $sidebar (Optional) A sidebar to which this widget will be moved.
451
	 * @param string|integer (Optional) A new position to which this widget will be moved within its new or existing sidebar.
452
	 * @param array|object|string $settings Settings to merge with the existing settings of the widget (will be passed through `decode_settings`).
453
	 *
454
	 * @return array|WP_Error The newly added widget as an associative array with all the above properties.
455
	 */
456
	public static function update_widget( $widget_id, $sidebar, $position, $settings ) {
457
		$settings = self::decode_settings( $settings );
458
		if ( isset( $settings ) && ! is_array( $settings ) ) {
459
			return new WP_Error( 'invalid_data', 'Invalid settings', 400 );
460
		}
461
		// Default to an empty array if nothing is specified.
462
		if ( ! is_array( $settings ) ) {
463
			$settings = array();
464
		}
465
		$widget = self::get_widget_by_id( $widget_id );
466
		if ( ! $widget ) {
467
			return new WP_Error( 'not_found', 'No widget found.', 400 );
468
		}
469
		if ( ! $sidebar ) {
470
			$sidebar = $widget['sidebar'];
471
		}
472
		if ( ! isset( $position ) ) {
473
			$position = $widget['position'];
474
		}
475
		if ( ! is_numeric( $position ) ) {
476
			return new WP_Error( 'invalid_data', 'Invalid position', 400 );
477
		}
478
479
		// return if no widgets are present in the sidebar
480
		$widgets_in_sidebar = self::get_widgets_in_sidebar( $sidebar );
481
		if ( ! isset( $widgets_in_sidebar ) ) {
482
			return new WP_Error( 'invalid_data', 'No widgets in sidebar', 400 );
483
		}
484
485
		// call when placing widget in a new sidebar, else obsolete
486
		if ( ! self::is_widget_in_sidebar( $widgets_in_sidebar, $widget_id ) ) {
487
			self::move_widget_to_sidebar( $widget, $sidebar, $position );
488
		}
489
490
		$widget_save_status = self::set_widget_settings( $widget_id, $settings );
491
		if ( is_wp_error( $widget_save_status ) ) {
492
			return $widget_save_status;
493
		}
494
		return self::get_widget_by_id( $widget_id );
495
	}
496
497
	/**
498
	 * Find a widget in a list of all widgets retrieved from a sidebar
499
	 * using get_widgets_in_sidebar()
500
	 * @param string $widget The widget we want to look up in the sidebar
501
	 *
502
	 * @param string $widgets_all the array of widget is' in a given sidebar
503
	 *
504
	 * @return bool Whether present.
505
	 */
506
507
	public static function is_widget_in_sidebar( $widgets_all, $widget_id ) {
508
		foreach ( $widgets_all as $widget_el_id ) {
509
			if ( $widget_el_id  === $widget_id ) {
510
				return true;
511
			}
512
		}
513
		return false;
514
	}
515
516
	/**
517
	 * Deletes a widget entirely including all its settings. Returns a WP_Error if
518
	 * the widget could not be found. Otherwise returns an empty array.
519
	 *
520
	 * @param string $widget_id The id of a widget to delete. (eg: 'text-2')
521
	 *
522
	 * @return array|WP_Error An empty array if successful.
523
	 */
524
	public static function delete_widget( $widget_id ) {
525
		$widget = self::get_widget_by_id( $widget_id );
526
		if ( ! $widget ) {
527
			return new WP_Error( 'not_found', 'No widget found.', 400 );
528
		}
529
		self::remove_widget_from_sidebar( $widget );
530
		self::remove_widget_settings( $widget );
531
		return array();
532
	}
533
534
	/**
535
	 * Return an array of settings. The input can be either an object, a JSON
536
	 * string, or an array.
537
	 *
538
	 * @param array|string|object $settings The settings of a widget as passed into the API.
539
	 *
540
	 * @return array Decoded associative array of settings.
541
	 */
542
	public static function decode_settings( $settings ) {
543
		// Treat as string in case JSON was passed
544
		if ( is_object( $settings ) && property_exists( $settings, 'scalar' ) ) {
545
			$settings = $settings->scalar;
546
		}
547
		if ( is_object( $settings ) ) {
548
			$settings = (array) $settings;
549
		}
550
		// Attempt to decode JSON string
551
		if ( is_string( $settings ) ) {
552
			$settings = (array) json_decode( $settings );
553
		}
554
		return $settings;
555
	}
556
557
	/**
558
	 * Activate a new widget.
559
	 *
560
	 * @param string $id_base The id_base of the new widget (eg: 'text')
561
	 * @param string $sidebar The id of the sidebar where this widget will go. Dependent on theme. (eg: 'sidebar-1')
562
	 * @param string|integer $position (Optional) The position of the widget in the sidebar. Defaults to the last position.
563
	 * @param array|object|string $settings (Optional) An associative array of settings for this widget (will be passed through `decode_settings`). Varies by widget.
564
	 *
565
	 * @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'.
566
	 */
567
	public static function activate_widget( $id_base, $sidebar, $position, $settings ) {
568
		if ( ! isset( $id_base ) || ! self::validate_id_base( $id_base ) ) {
569
			return new WP_Error( 'invalid_data', 'Invalid ID base', 400 );
570
		}
571
572
		if ( ! isset( $sidebar ) ) {
573
			return new WP_Error( 'invalid_data', 'No sidebar provided', 400 );
574
		}
575
576
		if ( isset( $position ) && ! is_numeric( $position ) ) {
577
			return new WP_Error( 'invalid_data', 'Invalid position', 400 );
578
		}
579
580
		$settings = self::decode_settings( $settings );
581
		if ( isset( $settings ) && ! is_array( $settings ) ) {
582
			return new WP_Error( 'invalid_data', 'Invalid settings', 400 );
583
		}
584
585
		// Default to an empty array if nothing is specified.
586
		if ( ! is_array( $settings ) ) {
587
			$settings = array();
588
		}
589
590
		$widget_counter = 1 + self::get_last_widget_instance_key_with_id_base( $id_base );
591
		$widget_id = $id_base . '-' . $widget_counter;
592
		if ( 0 >= $widget_counter ) {
593
			return new WP_Error( 'invalid_data', 'Error creating widget ID' . $widget_id, 500 );
594
		}
595
		if ( self::get_widget_by_id( $widget_id ) ) {
596
			return new WP_Error( 'invalid_data', 'Widget ID already exists', 500 );
597
		}
598
599
		self::add_widget_to_sidebar( $widget_id, $sidebar, $position );
600
		$widget_save_status = self::set_widget_settings( $widget_id, $settings );
601
		if ( is_wp_error( $widget_save_status ) ) {
602
			return $widget_save_status;
603
		}
604
605
		// Add a Tracks event for non-Headstart activity.
606
		if ( ! defined( 'HEADSTART' ) ) {
607
			jetpack_require_lib( 'tracks/client' );
608
			jetpack_tracks_record_event( wp_get_current_user(), 'wpcom_widgets_activate_widget', array(
609
				'widget' => $id_base,
610
				'settings' => json_encode( $settings ),
611
			) );
612
		}
613
614
		return self::get_widget_by_id( $widget_id );
615
	}
616
617
	/**
618
	 * Activate an array of new widgets. Like calling `activate_widget` multiple times.
619
	 *
620
	 * @param array $widgets An array of widget arrays. Each sub-array must be of the format required by `activate_widget`.
621
	 *
622
	 * @return array|WP_Error The newly added widgets in the form returned by `get_all_widgets`.
623
	 */
624
	public static function activate_widgets( $widgets ) {
625
		if ( ! is_array( $widgets ) ) {
626
			return new WP_Error( 'invalid_data', 'Invalid widgets', 400 );
627
		}
628
629
		$added_widgets = array();
630
631
		foreach( $widgets as $widget ) {
632
			$added_widgets[] = self::activate_widget( $widget['id_base'], $widget['sidebar'], $widget['position'], $widget['settings'] );
633
		}
634
635
		return $added_widgets;
636
	}
637
638
	/**
639
	 * Return the last instance key (integer) of an existing widget matching
640
	 * `$id_base`. So if you pass in `text`, and there is a widget with the id
641
	 * `text-2`, this function will return `2`.
642
	 *
643
	 * @param string $id_base The id_base of a type of widget. (eg: 'rss')
644
	 *
645
	 * @return integer The last instance key of that type of widget.
646
	 */
647
	public static function get_last_widget_instance_key_with_id_base( $id_base ) {
648
		$similar_widgets = self::get_widgets_with_id_base( $id_base );
649
650
		if ( ! empty( $similar_widgets ) ) {
651
			// If the last widget with the same name is `text-3`, we want `text-4`
652
			usort( $similar_widgets, __CLASS__ . '::sort_widgets' );
653
654
			$last_widget = array_pop( $similar_widgets );
655
			$last_val = intval( self::get_widget_instance_key( $last_widget['id'] ) );
656
657
			return $last_val;
658
		}
659
660
		return 0;
661
	}
662
663
	/**
664
	 * Method used to sort widgets
665
	 *
666
	 * @since 5.4
667
	 *
668
	 * @param array $a
669
	 * @param array $b
670
	 *
671
	 * @return int
672
	 */
673
	public static function sort_widgets( $a, $b ) {
674
		$a_val = intval( self::get_widget_instance_key( $a['id'] ) );
675
		$b_val = intval( self::get_widget_instance_key( $b['id'] ) );
676
		if ( $a_val > $b_val ) {
677
			return 1;
678
		}
679
		if ( $a_val < $b_val ) {
680
			return -1;
681
		}
682
		return 0;
683
	}
684
685
	/**
686
	 * Retrieve a given widget object instance by ID base (eg. 'text' or 'archives').
687
	 *
688
	 * @param string $id_base The id_base of a type of widget.
689
	 *
690
	 * @return WP_Widget|false The found widget object or false if the id_base was not found.
691
	 */
692
	public static function get_registered_widget_object( $id_base ) {
693
		if ( ! $id_base ) {
694
			return false;
695
		}
696
697
		// Get all of the registered widgets.
698
		global $wp_widget_factory;
699
		if ( ! isset( $wp_widget_factory ) ) {
700
			return false;
701
		}
702
703
		$registered_widgets = $wp_widget_factory->widgets;
704
		if ( empty( $registered_widgets ) ) {
705
			return false;
706
		}
707
708
		foreach ( array_values( $registered_widgets ) as $registered_widget_object ) {
709
			if ( $registered_widget_object->id_base === $id_base ) {
710
				return $registered_widget_object;
711
			}
712
		}
713
		return false;
714
	}
715
716
	/**
717
	 * Validate a given widget ID base (eg. 'text' or 'archives').
718
	 *
719
	 * @param string $id_base The id_base of a type of widget.
720
	 *
721
	 * @return boolean True if the widget is of a known type.
722
	 */
723
	public static function validate_id_base( $id_base ) {
724
		return ( false !== self::get_registered_widget_object( $id_base ) );
725
	}
726
}
727