PluginProbe
Stream – Activity Log & Audit Trail / 3.8.0
Stream – Activity Log & Audit Trail v3.8.0
4.4.0 4.3.0 4.2.2 4.2.1 trunk 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.1 3.1.1 3.10.0 3.2.0 3.2.1 3.2.2 3.2.3 All 50 releases
stream / connectors / class-connector-widgets.php

class-connector-widgets.php in Stream – Activity Log & Audit Trail 3.8.0, at connectors/class-connector-widgets.php

870 lines 29.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Connector for Widgets
4 *
5 * @package WP_Stream
6 */
7
8 namespace WP_Stream;
9
10 /**
11 * Class - Connector_Widgets
12 */
13 class Connector_Widgets extends Connector {
14
15 /**
16 * Whether or not 'created' and 'deleted' actions should be logged. Normally
17 * the sidebar 'added' and 'removed' actions will correspond with these.
18 * See note below with usage.
19 *
20 * @var bool
21 */
22 public $verbose_widget_created_deleted_actions = false;
23
24 /**
25 * Connector slug
26 *
27 * @var string
28 */
29 public $name = 'widgets';
30
31 /**
32 * Actions registered for this connector
33 *
34 * @var array
35 */
36 public $actions = array(
37 'update_option_sidebars_widgets',
38 'updated_option',
39 );
40
41 /**
42 * Store the initial sidebars_widgets option when the customizer does its
43 * multiple rounds of saving to the sidebars_widgets option.
44 *
45 * @var array
46 */
47 protected $customizer_initial_sidebars_widgets = null;
48
49 /**
50 * Return translated connector label
51 *
52 * @return string Translated connector label
53 */
54 public function get_label() {
55 return esc_html__( 'Widgets', 'stream' );
56 }
57
58 /**
59 * Return translated action labels
60 *
61 * @return array Action label translations
62 */
63 public function get_action_labels() {
64 return array(
65 'added' => esc_html__( 'Added', 'stream' ),
66 'removed' => esc_html__( 'Removed', 'stream' ),
67 'moved' => esc_html__( 'Moved', 'stream' ),
68 'created' => esc_html__( 'Created', 'stream' ),
69 'deleted' => esc_html__( 'Deleted', 'stream' ),
70 'deactivated' => esc_html__( 'Deactivated', 'stream' ),
71 'reactivated' => esc_html__( 'Reactivated', 'stream' ),
72 'updated' => esc_html__( 'Updated', 'stream' ),
73 'sorted' => esc_html__( 'Sorted', 'stream' ),
74 );
75 }
76
77 /**
78 * Return translated context labels
79 *
80 * @return array Context label translations
81 */
82 public function get_context_labels() {
83 global $wp_registered_sidebars;
84
85 $labels = array();
86
87 foreach ( $wp_registered_sidebars as $sidebar ) {
88 $labels[ $sidebar['id'] ] = $sidebar['name'];
89 }
90
91 $labels['wp_inactive_widgets'] = esc_html__( 'Inactive Widgets', 'stream' );
92 $labels['orphaned_widgets'] = esc_html__( 'Orphaned Widgets', 'stream' );
93
94 return $labels;
95 }
96
97 /**
98 * Add action links to Stream drop row in admin list screen
99 *
100 * @filter wp_stream_action_links_{connector}
101 *
102 * @param array $links Previous links registered.
103 * @param Record $record Stream record.
104 *
105 * @return array Action links
106 */
107 public function action_links( $links, $record ) {
108 $sidebar = $record->get_meta( 'sidebar_id', true );
109 if ( $sidebar ) {
110 global $wp_registered_sidebars;
111
112 if ( array_key_exists( $sidebar, $wp_registered_sidebars ) ) {
113 $links[ esc_html__( 'Edit Widget Area', 'stream' ) ] = admin_url( 'widgets.php#' . $sidebar ); // xss ok (@todo fix WPCS rule).
114 }
115 // @todo Also old_sidebar_id and new_sidebar_id.
116 // @todo Add Edit Widget link.
117 }
118
119 return $links;
120 }
121
122 /**
123 * Tracks addition/deletion/reordering/deactivation of widgets from sidebars
124 *
125 * @action update_option_sidebars_widgets
126 *
127 * @param array $old Old sidebars widgets.
128 * @param array $new New sidebars widgets.
129 *
130 * @return void
131 */
132 public function callback_update_option_sidebars_widgets( $old, $new ) {
133 // Disable listener if we're switching themes.
134 if ( did_action( 'after_switch_theme' ) ) {
135 return;
136 }
137
138 if ( did_action( 'customize_save' ) ) {
139 if ( is_null( $this->customizer_initial_sidebars_widgets ) ) {
140 $this->customizer_initial_sidebars_widgets = $old;
141 add_action( 'customize_save_after', array( $this, 'callback_customize_save_after' ) );
142 }
143 } else {
144 $this->handle_sidebars_widgets_changes( $old, $new );
145 }
146 }
147
148 /**
149 * Since the sidebars_widgets may get updated multiple times when saving
150 * changes to Widgets in the Customizer, defer handling the changes until
151 * customize_save_after.
152 *
153 * @see callback_update_option_sidebars_widgets()
154 */
155 public function callback_customize_save_after() {
156 $old_sidebars_widgets = $this->customizer_initial_sidebars_widgets;
157 $new_sidebars_widgets = get_option( 'sidebars_widgets' );
158
159 $this->handle_sidebars_widgets_changes( $old_sidebars_widgets, $new_sidebars_widgets );
160 }
161
162 /**
163 * Processes tracked widget actions
164 *
165 * @param array $old Old sidebar widgets.
166 * @param array $new New sidebar widgets.
167 */
168 protected function handle_sidebars_widgets_changes( $old, $new ) {
169 unset( $old['array_version'] );
170 unset( $new['array_version'] );
171
172 if ( $old === $new ) {
173 return;
174 }
175
176 $this->handle_deactivated_widgets( $old, $new );
177 $this->handle_reactivated_widgets( $old, $new );
178 $this->handle_widget_removal( $old, $new );
179 $this->handle_widget_addition( $old, $new );
180 $this->handle_widget_reordering( $old, $new );
181 $this->handle_widget_moved( $old, $new );
182 }
183
184 /**
185 * Track deactivation of widgets from sidebars
186 *
187 * @param array $old Old sidebars widgets.
188 * @param array $new New sidebars widgets.
189 * @return void
190 */
191 protected function handle_deactivated_widgets( $old, $new ) {
192 $new_deactivated_widget_ids = array_diff( $new['wp_inactive_widgets'], $old['wp_inactive_widgets'] );
193
194 foreach ( $new_deactivated_widget_ids as $widget_id ) {
195 $sidebar_id = '';
196
197 foreach ( $old as $old_sidebar_id => $old_widget_ids ) {
198 if ( in_array( $widget_id, $old_widget_ids, true ) ) {
199 $sidebar_id = $old_sidebar_id;
200 break;
201 }
202 }
203
204 $action = 'deactivated';
205 $name = $this->get_widget_name( $widget_id );
206 $title = $this->get_widget_title( $widget_id );
207 $labels = $this->get_context_labels();
208 $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
209
210 if ( $name && $title ) {
211 /* translators: %1$s: a widget name, %2$s: a widget title, %3$s: a sidebar name (e.g. "Archives", "Browse", "Footer Area 1") */
212 $message = _x( '%1$s widget named "%2$s" from "%3$s" deactivated', '1: Name, 2: Title, 3: Sidebar Name', 'stream' );
213 } elseif ( $name ) {
214 // Empty title, but we have the name.
215 /* translators: %1$s: a widget name, %3$s: a sidebar name (e.g. "Archives", "Footer Area 1") */
216 $message = _x( '%1$s widget from "%3$s" deactivated', '1: Name, 3: Sidebar Name', 'stream' );
217 } elseif ( $title ) {
218 // Likely a single widget since no name is available.
219 /* translators: %1$s: a widget title, %2$s: a sidebar name (e.g. "Browse", "Footer Area 1") */
220 $message = _x( 'Unknown widget type named "%2$s" from "%3$s" deactivated', '2: Title, 3: Sidebar Name', 'stream' );
221 } else {
222 // Neither a name nor a title are available, so use the widget ID.
223 /* translators: %4$s: a widget ID, %3$s: a sidebar name (e.g. "42", "Footer Area 1") */
224 $message = _x( '%4$s widget from "%3$s" deactivated', '4: Widget ID, 3: Sidebar Name', 'stream' );
225 }
226
227 $message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
228
229 $this->log(
230 $message,
231 compact( 'title', 'name', 'widget_id', 'sidebar_id' ),
232 null,
233 'wp_inactive_widgets',
234 $action
235 );
236 }
237 }
238
239 /**
240 * Track reactivation of widgets from sidebars
241 *
242 * @param array $old Old sidebars widgets.
243 * @param array $new New sidebars widgets.
244 * @return void
245 */
246 protected function handle_reactivated_widgets( $old, $new ) {
247 $new_reactivated_widget_ids = array_diff( $old['wp_inactive_widgets'], $new['wp_inactive_widgets'] );
248
249 foreach ( $new_reactivated_widget_ids as $widget_id ) {
250 $sidebar_id = '';
251
252 foreach ( $new as $new_sidebar_id => $new_widget_ids ) {
253 if ( in_array( $widget_id, $new_widget_ids, true ) ) {
254 $sidebar_id = $new_sidebar_id;
255 break;
256 }
257 }
258
259 $action = 'reactivated';
260 $name = $this->get_widget_name( $widget_id );
261 $title = $this->get_widget_title( $widget_id );
262
263 if ( $name && $title ) {
264 /* translators: %1$s: a widget name, %2$s: a widget title (e.g. "Archives", "Browse") */
265 $message = _x( '%1$s widget named "%2$s" reactivated', '1: Name, 2: Title', 'stream' );
266 } elseif ( $name ) {
267 // Empty title, but we have the name.
268 /* translators: %1$s: a widget name (e.g. "Archives") */
269 $message = _x( '%1$s widget reactivated', '1: Name', 'stream' );
270 } elseif ( $title ) {
271 // Likely a single widget since no name is available.
272 /* translators: %2$s: a widget title (e.g. "Browse") */
273 $message = _x( 'Unknown widget type named "%2$s" reactivated', '2: Title', 'stream' );
274 } else {
275 // Neither a name nor a title are available, so use the widget ID.
276 /* translators: %3$s: a widget ID (e.g. "42") */
277 $message = _x( '%3$s widget reactivated', '3: Widget ID', 'stream' );
278 }
279
280 $message = sprintf( $message, $name, $title, $widget_id );
281
282 $this->log(
283 $message,
284 compact( 'title', 'name', 'widget_id', 'sidebar_id' ),
285 null,
286 $sidebar_id,
287 $action
288 );
289 }
290 }
291
292 /**
293 * Track deletion of widgets from sidebars
294 *
295 * @param array $old Old sidebars widgets.
296 * @param array $new New sidebars widgets.
297 * @return void
298 */
299 protected function handle_widget_removal( $old, $new ) {
300 $all_old_widget_ids = array_unique( call_user_func_array( 'array_merge', $old ) );
301 $all_new_widget_ids = array_unique( call_user_func_array( 'array_merge', $new ) );
302 // @todo In the customizer, moving widgets to other sidebars is problematic because each sidebar is registered as a separate setting; so we need to make sure that all $_POST['customized'] are applied?
303 // @todo The widget option is getting updated before the sidebars_widgets are updated, so we need to hook into the option update to try to cache any deletions for future lookup
304
305 $deleted_widget_ids = array_diff( $all_old_widget_ids, $all_new_widget_ids );
306
307 foreach ( $deleted_widget_ids as $widget_id ) {
308 $sidebar_id = '';
309
310 foreach ( $old as $old_sidebar_id => $old_widget_ids ) {
311 if ( in_array( $widget_id, $old_widget_ids, true ) ) {
312 $sidebar_id = $old_sidebar_id;
313 break;
314 }
315 }
316
317 $action = 'removed';
318 $name = $this->get_widget_name( $widget_id );
319 $title = $this->get_widget_title( $widget_id );
320 $labels = $this->get_context_labels();
321 $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
322
323 if ( $name && $title ) {
324 /* translators: %1$s: a widget name, %2$s: a widget title, %3$s: a sidebar name (e.g. "Archives", "Browse", "Footer Area 1") */
325 $message = _x( '%1$s widget named "%2$s" removed from "%3$s"', '1: Name, 2: Title, 3: Sidebar Name', 'stream' );
326 } elseif ( $name ) {
327 // Empty title, but we have the name.
328 /* translators: %1$s: a widget name, %3$s: a sidebar name (e.g. "Archives", "Footer Area 1") */
329 $message = _x( '%1$s widget removed from "%3$s"', '1: Name, 3: Sidebar Name', 'stream' );
330 } elseif ( $title ) {
331 // Likely a single widget since no name is available.
332 /* translators: %2$s: a widget title, %3$s: a sidebar name (e.g. "Browse", "Footer Area 1") */
333 $message = _x( 'Unknown widget type named "%2$s" removed from "%3$s"', '2: Title, 3: Sidebar Name', 'stream' );
334 } else {
335 // Neither a name nor a title are available, so use the widget ID.
336 /* translators: %4$s: a widget ID, %3$s: a sidebar name (e.g. "42", "Footer Area 1") */
337 $message = _x( '%4$s widget removed from "%3$s"', '4: Widget ID, 3: Sidebar Name', 'stream' );
338 }
339
340 $message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
341
342 $this->log(
343 $message,
344 compact( 'widget_id', 'sidebar_id' ),
345 null,
346 $sidebar_id,
347 $action
348 );
349 }
350 }
351
352 /**
353 * Track reactivation of widgets from sidebars
354 *
355 * @param array $old Old sidebars widgets.
356 * @param array $new New sidebars widgets.
357 * @return void
358 */
359 protected function handle_widget_addition( $old, $new ) {
360 $all_old_widget_ids = array_unique( call_user_func_array( 'array_merge', $old ) );
361 $all_new_widget_ids = array_unique( call_user_func_array( 'array_merge', $new ) );
362 $added_widget_ids = array_diff( $all_new_widget_ids, $all_old_widget_ids );
363
364 foreach ( $added_widget_ids as $widget_id ) {
365 $sidebar_id = '';
366
367 foreach ( $new as $new_sidebar_id => $new_widget_ids ) {
368 if ( in_array( $widget_id, $new_widget_ids, true ) ) {
369 $sidebar_id = $new_sidebar_id;
370 break;
371 }
372 }
373
374 $action = 'added';
375 $name = $this->get_widget_name( $widget_id );
376 $title = $this->get_widget_title( $widget_id );
377 $labels = $this->get_context_labels();
378 $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
379
380 if ( $name && $title ) {
381 /* translators: %1$s: a widget name, %2$s: a widget title, %3$s: a sidebar name (e.g. "Archives", "Browse", "Footer Area 1") */
382 $message = _x( '%1$s widget named "%2$s" added to "%3$s"', '1: Name, 2: Title, 3: Sidebar Name', 'stream' );
383 } elseif ( $name ) {
384 // Empty title, but we have the name.
385 /* translators: %1$s: a widget name, %3$s: a sidebar name (e.g. "Archives", "Footer Area 1") */
386 $message = _x( '%1$s widget added to "%3$s"', '1: Name, 3: Sidebar Name', 'stream' );
387 } elseif ( $title ) {
388 // Likely a single widget since no name is available.
389 /* translators: %2$s: a widget title, %3$s: a sidebar name (e.g. "Browse", "Footer Area 1") */
390 $message = _x( 'Unknown widget type named "%2$s" added to "%3$s"', '2: Title, 3: Sidebar Name', 'stream' );
391 } else {
392 // Neither a name nor a title are available, so use the widget ID.
393 /* translators: %4$s: a widget ID, %3$s: a sidebar name (e.g. "42", "Footer Area 1") */
394 $message = _x( '%4$s widget added to "%3$s"', '4: Widget ID, 3: Sidebar Name', 'stream' );
395 }
396
397 $message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
398
399 $this->log(
400 $message,
401 compact( 'widget_id', 'sidebar_id' ), // @todo Do we care about sidebar_id in meta if it is already context? But there is no 'context' for what the context signifies
402 null,
403 $sidebar_id,
404 $action
405 );
406 }
407 }
408
409 /**
410 * Track reordering of widgets
411 *
412 * @param array $old Old sidebars widgets.
413 * @param array $new New sidebars widgets.
414 * @return void
415 */
416 protected function handle_widget_reordering( $old, $new ) {
417 $all_sidebar_ids = array_intersect( array_keys( $old ), array_keys( $new ) );
418
419 foreach ( $all_sidebar_ids as $sidebar_id ) {
420 if ( $old[ $sidebar_id ] === $new[ $sidebar_id ] ) {
421 continue;
422 }
423
424 // Use intersect to ignore widget additions and removals.
425 $all_widget_ids = array_unique( array_merge( $old[ $sidebar_id ], $new[ $sidebar_id ] ) );
426 $common_widget_ids = array_intersect( $old[ $sidebar_id ], $new[ $sidebar_id ] );
427 $uncommon_widget_ids = array_diff( $all_widget_ids, $common_widget_ids );
428 $new_widget_ids = array_values( array_diff( $new[ $sidebar_id ], $uncommon_widget_ids ) );
429 $old_widget_ids = array_values( array_diff( $old[ $sidebar_id ], $uncommon_widget_ids ) );
430 $widget_order_changed = ( $new_widget_ids !== $old_widget_ids );
431
432 if ( $widget_order_changed ) {
433 $labels = $this->get_context_labels();
434 $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
435 $old_widget_ids = $old[ $sidebar_id ];
436
437 /* translators: %s: a sidebar name (e.g. "Footer Area 1") */
438 $message = _x( 'Widgets reordered in "%s"', 'Sidebar name', 'stream' );
439 $message = sprintf( $message, $sidebar_name );
440
441 $this->log(
442 $message,
443 compact( 'sidebar_id', 'old_widget_ids' ),
444 null,
445 $sidebar_id,
446 'sorted'
447 );
448 }
449 }
450
451 }
452
453 /**
454 * Track movement of widgets to other sidebars
455 *
456 * @param array $old Old sidebars widgets.
457 * @param array $new New sidebars widgets.
458 * @return void
459 */
460 protected function handle_widget_moved( $old, $new ) {
461 $all_sidebar_ids = array_intersect( array_keys( $old ), array_keys( $new ) );
462
463 foreach ( $all_sidebar_ids as $new_sidebar_id ) {
464 if ( $old[ $new_sidebar_id ] === $new[ $new_sidebar_id ] ) {
465 continue;
466 }
467
468 $new_widget_ids = array_diff( $new[ $new_sidebar_id ], $old[ $new_sidebar_id ] );
469
470 foreach ( $new_widget_ids as $widget_id ) {
471 // Now find the sidebar that the widget was originally located in, as long it is not wp_inactive_widgets.
472 $old_sidebar_id = null;
473 foreach ( $old as $sidebar_id => $old_widget_ids ) {
474 if ( in_array( $widget_id, $old_widget_ids, true ) ) {
475 $old_sidebar_id = $sidebar_id;
476 break;
477 }
478 }
479
480 if ( ! $old_sidebar_id || 'wp_inactive_widgets' === $old_sidebar_id || 'wp_inactive_widgets' === $new_sidebar_id ) {
481 continue;
482 }
483
484 assert( $old_sidebar_id !== $new_sidebar_id );
485
486 $name = $this->get_widget_name( $widget_id );
487 $title = $this->get_widget_title( $widget_id );
488 $labels = $this->get_context_labels();
489 $old_sidebar_name = isset( $labels[ $old_sidebar_id ] ) ? $labels[ $old_sidebar_id ] : $old_sidebar_id;
490 $new_sidebar_name = isset( $labels[ $new_sidebar_id ] ) ? $labels[ $new_sidebar_id ] : $new_sidebar_id;
491
492 if ( $name && $title ) {
493 /* translators: %1$s: a widget name, %2$s: a widget title, %4$s: a sidebar name, %5$s: another sidebar name (e.g. "Archives", "Browse", "Footer Area 1", "Footer Area 2") */
494 $message = _x( '%1$s widget named "%2$s" moved from "%4$s" to "%5$s"', '1: Name, 2: Title, 4: Old Sidebar Name, 5: New Sidebar Name', 'stream' );
495 } elseif ( $name ) {
496 // Empty title, but we have the name.
497 /* translators: %1$s: a widget name, %4$s: a sidebar name, %5$s: another sidebar name (e.g. "Archives", "Footer Area 1", "Footer Area 2") */
498 $message = _x( '%1$s widget moved from "%4$s" to "%5$s"', '1: Name, 4: Old Sidebar Name, 5: New Sidebar Name', 'stream' );
499 } elseif ( $title ) {
500 // Likely a single widget since no name is available.
501 /* translators: %2$s: a widget title, %4$s: a sidebar name, %5$s: another sidebar name (e.g. "Browse", "Footer Area 1", "Footer Area 2") */
502 $message = _x( 'Unknown widget type named "%2$s" moved from "%4$s" to "%5$s"', '2: Title, 4: Old Sidebar Name, 5: New Sidebar Name', 'stream' );
503 } else {
504 // Neither a name nor a title are available, so use the widget ID.
505 /* translators: %3$s: a widget ID, %4$s: a sidebar name, %5$s: another sidebar name (e.g. "42", "Footer Area 1", "Footer Area 2") */
506 $message = _x( '%3$s widget moved from "%4$s" to "%5$s"', '3: Widget ID, 4: Old Sidebar Name, 5: New Sidebar Name', 'stream' );
507 }
508
509 $message = sprintf( $message, $name, $title, $widget_id, $old_sidebar_name, $new_sidebar_name );
510 $sidebar_id = $new_sidebar_id;
511
512 $this->log(
513 $message,
514 compact( 'widget_id', 'sidebar_id', 'old_sidebar_id' ),
515 null,
516 $sidebar_id,
517 'moved'
518 );
519 }
520 }
521
522 }
523
524 /**
525 * Track changes to widgets
526 *
527 * @action updated_option
528 *
529 * @param string $option_name Option key.
530 * @param array $old_value Old value.
531 * @param array $new_value New value.
532 */
533 public function callback_updated_option( $option_name, $old_value, $new_value ) {
534 if ( ! preg_match( '/^widget_(.+)$/', $option_name, $matches ) || ! is_array( $new_value ) ) {
535 return;
536 }
537
538 $is_multi = ! empty( $new_value['_multiwidget'] );
539 $widget_id_base = $matches[1];
540
541 $creates = array();
542 $updates = array();
543 $deletes = array();
544
545 if ( $is_multi ) {
546 $widget_id_format = "$widget_id_base-%d";
547
548 unset( $new_value['_multiwidget'] );
549 unset( $old_value['_multiwidget'] );
550
551 /**
552 * Created widgets
553 */
554 $created_widget_numbers = array_diff( array_keys( $new_value ), array_keys( $old_value ) );
555
556 foreach ( $created_widget_numbers as $widget_number ) {
557 $instance = $new_value[ $widget_number ];
558 $widget_id = sprintf( $widget_id_format, $widget_number );
559 $name = $this->get_widget_name( $widget_id );
560 $title = ! empty( $instance['title'] ) ? $instance['title'] : null;
561 $sidebar_id = $this->get_widget_sidebar_id( $widget_id ); // @todo May not be assigned yet
562
563 $creates[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'instance' );
564 }
565
566 /**
567 * Updated widgets
568 */
569 $updated_widget_numbers = array_intersect( array_keys( $old_value ), array_keys( $new_value ) );
570
571 foreach ( $updated_widget_numbers as $widget_number ) {
572 $new_instance = $new_value[ $widget_number ];
573 $old_instance = $old_value[ $widget_number ];
574
575 if ( $old_instance !== $new_instance ) {
576 $widget_id = sprintf( $widget_id_format, $widget_number );
577 $name = $this->get_widget_name( $widget_id );
578 $title = ! empty( $new_instance['title'] ) ? $new_instance['title'] : null;
579 $sidebar_id = $this->get_widget_sidebar_id( $widget_id );
580 $labels = $this->get_context_labels();
581 $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
582
583 $updates[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'old_instance', 'sidebar_name' );
584 }
585 }
586
587 /**
588 * Deleted widgets
589 */
590 $deleted_widget_numbers = array_diff( array_keys( $old_value ), array_keys( $new_value ) );
591
592 foreach ( $deleted_widget_numbers as $widget_number ) {
593 $instance = $old_value[ $widget_number ];
594 $widget_id = sprintf( $widget_id_format, $widget_number );
595 $name = $this->get_widget_name( $widget_id );
596 $title = ! empty( $instance['title'] ) ? $instance['title'] : null;
597 $sidebar_id = $this->get_widget_sidebar_id( $widget_id ); // @todo May not be assigned anymore
598
599 $deletes[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'instance' );
600 }
601 } else {
602 // Doing our best guess for tracking changes to old single widgets, assuming their options start with 'widget_'.
603 $widget_id = $widget_id_base;
604 $name = $widget_id; // There aren't names available for single widgets.
605 $title = ! empty( $new_value['title'] ) ? $new_value['title'] : null;
606 $sidebar_id = $this->get_widget_sidebar_id( $widget_id );
607 $old_instance = $old_value;
608 $labels = $this->get_context_labels();
609 $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
610
611 $updates[] = compact( 'widget_id', 'title', 'name', 'sidebar_id', 'old_instance', 'sidebar_name' );
612 }
613
614 /**
615 * Log updated actions
616 */
617 foreach ( $updates as $update ) {
618 if ( $update['name'] && $update['title'] ) {
619 /* translators: %1$s: a widget name, %2$s: a widget title, %3$s: a sidebar name (e.g. "Archives", "Browse", "Footer Area 1") */
620 $message = _x( '%1$s widget named "%2$s" in "%3$s" updated', '1: Name, 2: Title, 3: Sidebar Name', 'stream' );
621 } elseif ( $update['name'] ) {
622 // Empty title, but we have the name.
623 /* translators: %1$s: a widget name, %3$s: a sidebar name (e.g. "Archives", "Footer Area 1") */
624 $message = _x( '%1$s widget in "%3$s" updated', '1: Name, 3: Sidebar Name', 'stream' );
625 } elseif ( $update['title'] ) {
626 // Likely a single widget since no name is available.
627 /* translators: %2$s: a widget title, %3$s: a sidebar name (e.g. "Browse", "Footer Area 1") */
628 $message = _x( 'Unknown widget type named "%2$s" in "%3$s" updated', '2: Title, 3: Sidebar Name', 'stream' );
629 } else {
630 // Neither a name nor a title are available, so use the widget ID.
631 /* translators: %4$s: a widget ID, %3$s: a sidebar name (e.g. "42", "Footer Area 1") */
632 $message = _x( '%4$s widget in "%3$s" updated', '4: Widget ID, 3: Sidebar Name', 'stream' );
633 }
634
635 $message = sprintf( $message, $update['name'], $update['title'], $update['sidebar_name'], $update['widget_id'] );
636
637 unset( $update['title'], $update['name'] );
638
639 $this->log(
640 $message,
641 $update,
642 null,
643 $update['sidebar_id'],
644 'updated'
645 );
646 }
647
648 /**
649 * In the normal case, widgets are never created or deleted in a vacuum.
650 * Created widgets are immediately assigned to a sidebar, and deleted
651 * widgets are immediately removed from their assigned sidebar. If,
652 * however, widget instances get manipulated programmatically, it is
653 * possible that they could be orphaned, in which case the following
654 * actions would be useful to log.
655 */
656 if ( $this->verbose_widget_created_deleted_actions ) {
657 // We should only do these if not captured by an update to the sidebars_widgets option.
658 /**
659 * Log created actions
660 */
661 foreach ( $creates as $create ) {
662 if ( $create['name'] && $create['title'] ) {
663 /* translators: %1$s: a widget name, %2$s: a widget title (e.g. "Archives", "Browse") */
664 $message = _x( '%1$s widget named "%2$s" created', '1: Name, 2: Title', 'stream' );
665 } elseif ( $create['name'] ) {
666 // Empty title, but we have the name.
667 /* translators: %1$s: a widget name (e.g. "Archives") */
668 $message = _x( '%1$s widget created', '1: Name', 'stream' );
669 } elseif ( $create['title'] ) {
670 // Likely a single widget since no name is available.
671 /* translators: %2$s: a widget title (e.g. "Browse") */
672 $message = _x( 'Unknown widget type named "%2$s" created', '2: Title', 'stream' );
673 } else {
674 // Neither a name nor a title are available, so use the widget ID.
675 /* translators: %3$s: a widget ID (e.g. "42") */
676 $message = _x( '%3$s widget created', '3: Widget ID', 'stream' );
677 }
678
679 $message = sprintf( $message, $create['name'], $create['title'], $create['widget_id'] );
680
681 unset( $create['title'], $create['name'] );
682
683 $this->log(
684 $message,
685 $create,
686 null,
687 $create['sidebar_id'],
688 'created'
689 );
690 }
691
692 /**
693 * Log deleted actions
694 */
695 foreach ( $deletes as $delete ) {
696 if ( $delete['name'] && $delete['title'] ) {
697 /* translators: %1$s: a widget name, %2$s: a widget title (e.g. "Archives", "Browse") */
698 $message = _x( '%1$s widget named "%2$s" deleted', '1: Name, 2: Title', 'stream' );
699 } elseif ( $delete['name'] ) {
700 // Empty title, but we have the name.
701 /* translators: %1$s: a widget name (e.g. "Archives") */
702 $message = _x( '%1$s widget deleted', '1: Name', 'stream' );
703 } elseif ( $delete['title'] ) {
704 // Likely a single widget since no name is available.
705 /* translators: %2$s: a widget title (e.g. "Browse") */
706 $message = _x( 'Unknown widget type named "%2$s" deleted', '2: Title', 'stream' );
707 } else {
708 // Neither a name nor a title are available, so use the widget ID.
709 /* translators: %3$s: a widget ID (e.g. "42") */
710 $message = _x( '%3$s widget deleted', '3: Widget ID', 'stream' );
711 }
712
713 $message = sprintf( $message, $delete['name'], $delete['title'], $delete['widget_id'] );
714
715 unset( $delete['title'], $delete['name'] );
716
717 $this->log(
718 $message,
719 $delete,
720 null,
721 $delete['sidebar_id'],
722 'deleted'
723 );
724 }
725 }
726 }
727
728 /**
729 * Returns widget title.
730 *
731 * @param string $widget_id Widget instance ID.
732 *
733 * @return string
734 */
735 public function get_widget_title( $widget_id ) {
736 $instance = $this->get_widget_instance( $widget_id );
737 return ! empty( $instance['title'] ) ? $instance['title'] : null;
738 }
739
740 /**
741 * Returns widget name.
742 *
743 * @param string $widget_id Widget instance ID.
744 *
745 * @return string|null
746 */
747 public function get_widget_name( $widget_id ) {
748 $widget_obj = $this->get_widget_object( $widget_id );
749 return $widget_obj ? $widget_obj->name : null;
750 }
751
752 /**
753 * Parses widget instance ID and widget type data.
754 *
755 * @param string $widget_id Widget instance ID.
756 *
757 * @return array|null
758 */
759 public function parse_widget_id( $widget_id ) {
760 if ( preg_match( '/^(.+)-(\d+)$/', $widget_id, $matches ) ) {
761 return array(
762 'id_base' => $matches[1],
763 'widget_number' => intval( $matches[2] ),
764 );
765 } else {
766 return null;
767 }
768 }
769
770 /**
771 * Returns widget object.
772 *
773 * @param string $widget_id Widget instance ID.
774 *
775 * @return \WP_Widget|null
776 */
777 public function get_widget_object( $widget_id ) {
778 global $wp_widget_factory;
779
780 $parsed_widget_id = $this->parse_widget_id( $widget_id );
781
782 if ( ! $parsed_widget_id ) {
783 return null;
784 }
785
786 $id_base = $parsed_widget_id['id_base'];
787
788 $id_base_to_widget_class_map = array_combine(
789 wp_list_pluck( $wp_widget_factory->widgets, 'id_base' ),
790 array_keys( $wp_widget_factory->widgets )
791 );
792
793 if ( ! isset( $id_base_to_widget_class_map[ $id_base ] ) ) {
794 return null;
795 }
796
797 return $wp_widget_factory->widgets[ $id_base_to_widget_class_map[ $id_base ] ];
798 }
799
800 /**
801 * Returns widget instance settings
802 *
803 * @param string $widget_id Widget ID, ex: pages-1.
804 *
805 * @return array|null Widget instance
806 */
807 public function get_widget_instance( $widget_id ) {
808 $instance = null;
809 $parsed_widget_id = $this->parse_widget_id( $widget_id );
810 $widget_obj = $this->get_widget_object( $widget_id );
811
812 if ( $widget_obj && $parsed_widget_id ) {
813 $settings = $widget_obj->get_settings();
814 $multi_number = $parsed_widget_id['widget_number'];
815
816 if ( isset( $settings[ $multi_number ] ) && ! empty( $settings[ $multi_number ]['title'] ) ) {
817 $instance = $settings[ $multi_number ];
818 }
819 } else {
820 // Single widgets, try our best guess at the option used.
821 $potential_instance = get_option( "widget_{$widget_id}" );
822
823 if ( ! empty( $potential_instance ) && ! empty( $potential_instance['title'] ) ) {
824 $instance = $potential_instance;
825 }
826 }
827
828 return $instance;
829 }
830
831 /**
832 * Get global sidebars widgets
833 *
834 * @return array
835 */
836 public function get_sidebars_widgets() {
837 /**
838 * Filter allows for insertion of sidebar widgets
839 *
840 * @todo Do we need this filter?
841 *
842 * @param array Sidebar Widgets in Options table
843 * @param array Inserted Sidebar Widgets
844 * @return array Array of updated Sidebar Widgets
845 */
846 return apply_filters( 'sidebars_widgets', get_option( 'sidebars_widgets', array() ) );
847 }
848
849 /**
850 * Return the sidebar of a certain widget, based on widget_id
851 *
852 * @param string $widget_id Widget id, ex: pages-1.
853 *
854 * @return string Sidebar id
855 */
856 public function get_widget_sidebar_id( $widget_id ) {
857 $sidebars_widgets = $this->get_sidebars_widgets();
858
859 unset( $sidebars_widgets['array_version'] );
860
861 foreach ( $sidebars_widgets as $sidebar_id => $widget_ids ) {
862 if ( in_array( $widget_id, $widget_ids, true ) ) {
863 return $sidebar_id;
864 }
865 }
866
867 return 'orphaned_widgets';
868 }
869 }
870