Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 253 |
|
0.00% |
0 / 32 |
CRAP | |
0.00% |
0 / 1 |
| Jetpack_Widgets | |
0.00% |
0 / 253 |
|
0.00% |
0 / 32 |
13340 | |
0.00% |
0 / 1 |
| get_sidebars_widgets | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| format_widget | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
| get_widget_id_base | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_widget_option_name | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_widget_instance_key | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| get_widget_by_id | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
| get_all_widgets | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| get_active_widgets | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
| get_all_widget_ids | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| get_widgets_with_id_base | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
| get_widgets_in_sidebar | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 | |||
| get_all_sidebars | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| get_active_sidebars | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 | |||
| add_widget_to_sidebar | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| remove_widget_from_sidebar | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| move_widget_to_sidebar | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
90 | |||
| get_last_position_in_sidebar | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
| set_widget_settings | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
| sanitize_widget_settings | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
| remove_widget_settings | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| update_widget | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
110 | |||
| delete_widget | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
| decode_settings | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
| activate_widget | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
182 | |||
| activate_widgets | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
| get_last_widget_instance_key_with_id_base | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
| sort_widgets | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| get_registered_widget_object | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
| validate_id_base | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| insert_widget_in_sidebar | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
| update_widget_in_sidebar | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
56 | |||
| get_first_sidebar | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
| 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 ]; |
| 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 ), |
| 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 | } |