PluginProbe
Stream – Activity Log & Audit Trail / trunk
Stream – Activity Log & Audit Trail vtrunk
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 / classes / class-list-table.php

class-list-table.php in Stream – Activity Log & Audit Trail trunk, at classes/class-list-table.php

1,228 lines 33.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Generates a filterable list of provided records to be displayed HTML Table.
4 *
5 * @package WP_Stream
6 */
7
8 namespace WP_Stream;
9
10 /**
11 * Class - List_Table
12 */
13 class List_Table extends \WP_List_Table {
14
15 /**
16 * Holds Instance of plugin object
17 *
18 * @var Plugin
19 */
20 public $plugin;
21
22 /**
23 * Class constructor.
24 *
25 * @param Plugin $plugin Instance of plugin object.
26 * @param array $args Argument to filter rows by.
27 */
28 public function __construct( $plugin, $args = array() ) {
29 $this->plugin = $plugin;
30
31 $screen_id = isset( $args['screen'] ) ? $args['screen'] : null;
32
33 /**
34 * Filter the list table screen ID
35 *
36 * @return string
37 */
38 $screen_id = apply_filters( 'wp_stream_list_table_screen_id', $screen_id );
39
40 parent::__construct(
41 array(
42 'post_type' => 'stream',
43 'plural' => 'records',
44 'screen' => $screen_id,
45 )
46 );
47
48 add_screen_option(
49 'per_page',
50 array(
51 'default' => 20,
52 'label' => __( 'Records per page', 'stream' ),
53 'option' => 'edit_stream_per_page',
54 )
55 );
56
57 // Check for default hidden columns.
58 $this->get_hidden_columns();
59
60 add_filter(
61 'screen_settings',
62 array(
63 $this,
64 'screen_controls',
65 ),
66 10,
67 2
68 );
69 add_filter(
70 'set-screen-option',
71 array(
72 $this,
73 'set_screen_option',
74 ),
75 10,
76 3
77 );
78
79 set_screen_options();
80 }
81
82 /**
83 * Renders extra from navigation options.
84 *
85 * @param string $which Page location.
86 * @return void
87 */
88 public function extra_tablenav( $which ) {
89 if ( 'top' === $which ) {
90 echo '<div class="alignleft actions">';
91 $this->render_filters_form();
92 echo '</div>';
93 }
94 }
95
96 /**
97 * Renders "No items found" message.
98 *
99 * @return void
100 */
101 public function no_items() {
102 ?>
103 <div class="stream-list-table-no-items">
104 <p><?php esc_html_e( 'No activity records were found.', 'stream' ); ?></p>
105 </div>
106 <?php
107 }
108
109 /**
110 * Returns the table columns to be rendered.
111 *
112 * @return array
113 */
114 public function get_columns() {
115 /**
116 * Allows devs to add new columns to table
117 *
118 * @return array
119 */
120 return apply_filters(
121 'wp_stream_list_table_columns',
122 array(
123 'date' => __( 'Date', 'stream' ),
124 'summary' => __( 'Summary', 'stream' ),
125 'user_id' => __( 'User', 'stream' ),
126 'context' => __( 'Context', 'stream' ),
127 'action' => __( 'Action', 'stream' ),
128 'ip' => __( 'IP Address', 'stream' ),
129 )
130 );
131 }
132
133 /**
134 * Returns the columns the items can be sort by.
135 *
136 * @return array
137 */
138 public function get_sortable_columns() {
139 return array(
140 'date' => array( 'date', false ),
141 );
142 }
143
144 /**
145 * Returns columns hidden from all except specific users.
146 *
147 * @return array|bool
148 */
149 public function get_hidden_columns() {
150 $user = wp_get_current_user();
151 if ( ! $user ) {
152 return array();
153 }
154
155 // Directly checking the user meta; to check whether user has changed screen option or not.
156 $hidden = get_user_meta( $user->ID, 'manage' . $this->screen->id . 'columnshidden', true );
157
158 // If user meta is not found; add the default hidden column 'id'.
159 if ( ! $hidden ) {
160 $hidden = array( 'id' );
161 update_user_meta( $user->ID, 'manage' . $this->screen->id . 'columnshidden', $hidden );
162 }
163
164 return $hidden;
165 }
166
167 /**
168 * Prepares table columns and data for render
169 *
170 * @return void
171 */
172 public function prepare_items() {
173 $columns = $this->get_columns();
174 $sortable = $this->get_sortable_columns();
175 $hidden = $this->get_hidden_columns();
176 $primary = $columns['summary'];
177
178 $this->_column_headers = array(
179 $columns,
180 $hidden,
181 $sortable,
182 $primary,
183 );
184
185 $this->items = $this->get_records();
186
187 $total_items = $this->get_total_found_rows();
188
189 $this->set_pagination_args(
190 array(
191 'total_items' => $total_items,
192 'per_page' => $this->get_items_per_page( 'edit_stream_per_page', 20 ),
193 )
194 );
195 }
196
197 /**
198 * Returns records to be displayed
199 *
200 * @return array
201 */
202 public function get_records() {
203 $args = array();
204
205 // Parse sorting params.
206 $order = wp_stream_filter_input( INPUT_GET, 'order' );
207 if ( $order ) {
208 $args['order'] = $order;
209 }
210
211 $orderby = wp_stream_filter_input( INPUT_GET, 'orderby' );
212 if ( $orderby ) {
213 $args['orderby'] = $orderby;
214 }
215
216 $params = array(
217 'search',
218 'date',
219 'date_from',
220 'date_to',
221 'date_after',
222 'date_before',
223 );
224
225 foreach ( $params as $param ) {
226 $value = wp_stream_filter_input( INPUT_GET, $param );
227
228 if ( $value ) {
229 $args[ $param ] = $value;
230 }
231 }
232
233 // Additional filter properties.
234 $properties = array(
235 'record',
236 'site_id',
237 'blog_id',
238 'object_id',
239 'user_id',
240 'user_role',
241 'ip',
242 'connector',
243 'context',
244 'action',
245 );
246
247 // Add property fields to defaults, including their __in/__not_in variations.
248 foreach ( $properties as $property ) {
249 $value = wp_stream_filter_input( INPUT_GET, $property );
250
251 // Allow 0 values.
252 if ( isset( $value ) && '' !== $value && false !== $value ) {
253 $args[ $property ] = $value;
254 }
255
256 $value_in = wp_stream_filter_input( INPUT_GET, $property . '__in' );
257
258 if ( $value_in ) {
259 $args[ $property . '__in' ] = explode( ',', $value_in );
260 }
261
262 $value_not_in = wp_stream_filter_input( INPUT_GET, $property . '__not_in' );
263
264 if ( $value_not_in ) {
265 $args[ $property . '__not_in' ] = explode( ',', $value_not_in );
266 }
267 }
268
269 $args['paged'] = $this->get_pagenum();
270
271 if ( isset( $args['context'] ) && 0 === strpos( $args['context'], 'group-' ) ) {
272 $args['connector'] = str_replace( 'group-', '', $args['context'] );
273 $args['context'] = '';
274 }
275
276 if ( ! isset( $args['records_per_page'] ) ) {
277 $args['records_per_page'] = $this->get_items_per_page( 'edit_stream_per_page', 20 );
278 }
279 $args['records_per_page'] = apply_filters( 'stream_records_per_page', $args['records_per_page'] );
280
281 $items = $this->plugin->db->get_records( $args );
282
283 return $items;
284 }
285
286 /**
287 * Get last query found rows
288 *
289 * @return integer
290 */
291 public function get_total_found_rows() {
292 return $this->plugin->db->get_found_records_count();
293 }
294
295 /**
296 * Returns the column content for the provided item and column.
297 *
298 * @param array $item Record data.
299 * @param string $column_name Column name.
300 * @return void
301 */
302 public function column_default( $item, $column_name ) {
303 $out = '';
304 $record = new Record( $item );
305
306 switch ( $column_name ) {
307 case 'date':
308 $created = gmdate( 'Y-m-d H:i:s', strtotime( $record->created ) );
309 $date_string = sprintf(
310 '<time datetime="%s" class="relative-time record-created">%s</time>',
311 wp_stream_get_iso_8601_extended_date( strtotime( $record->created ) ),
312 get_date_from_gmt( $created, 'Y/m/d' )
313 );
314 $out = $this->column_link( $date_string, 'date', get_date_from_gmt( $created, 'Y/m/d' ) );
315 $out .= '<br />';
316 $out .= get_date_from_gmt( $created, 'h:i:s A T' );
317 break;
318
319 case 'summary':
320 $out = $record->summary;
321 $object_title = $record->get_object_title();
322 /* translators: %s: the title of any object, like a Post (e.g. "Hello World") */
323 $view_all_text = $object_title ? sprintf( esc_html__( 'View all activity for "%s"', 'stream' ), esc_attr( $object_title ) ) : esc_html__( 'View all activity for this object', 'stream' );
324
325 if ( $record->object_id ) {
326 $out .= $this->column_link(
327 '<span class="dashicons dashicons-search stream-filter-object-id"></span>',
328 array(
329 'object_id' => $record->object_id,
330 'context' => $record->context,
331 ),
332 null,
333 esc_attr( $view_all_text )
334 );
335 }
336 $out .= $this->get_action_links( $record );
337 break;
338
339 case 'user_id':
340 $user = new Author( (int) $record->user_id, (array) $record->user_meta );
341
342 $filtered_records_url = add_query_arg(
343 array(
344 'page' => $this->plugin->admin->records_page_slug,
345 'user_id' => absint( $user->id ),
346 ),
347 self_admin_url( $this->plugin->admin->admin_parent_page )
348 );
349
350 $out = sprintf(
351 '<a href="%s">%s <span>%s</span></a>%s%s%s',
352 $filtered_records_url,
353 $user->get_avatar_img( 80 ),
354 $user->get_display_name(),
355 $user->is_deleted() ? sprintf( '<br /><small class="deleted">%s</small>', esc_html__( 'Deleted User', 'stream' ) ) : '',
356 sprintf( '<br /><small>%s</small>', $user->get_role() ),
357 sprintf( '<br /><small>%s</small>', $user->get_agent_label( $user->get_agent() ) )
358 );
359 break;
360
361 case 'context':
362 $connector_title = $this->get_term_title( $record->{'connector'}, 'connector' );
363 $context_title = $this->get_term_title( $record->{'context'}, 'context' );
364
365 $out = $this->column_link( $connector_title, 'connector', $item->{'connector'} );
366 $out .= '<br />&#8627;&nbsp;';
367 $out .= $this->column_link(
368 $context_title,
369 array(
370 'connector' => $record->{'connector'},
371 'context' => $record->{'context'},
372 )
373 );
374 break;
375
376 case 'action':
377 $out = $this->column_link( $this->get_term_title( $record->{$column_name}, $column_name ), $column_name, $record->{$column_name} );
378 break;
379
380 case 'blog_id':
381 $blog = ( $record->blog_id && is_multisite() ) ? get_blog_details( $record->blog_id ) : $this->plugin->admin->network->get_network_blog();
382 $out = $this->column_link( $blog->blogname, 'blog_id', $blog->blog_id );
383 break;
384
385 case 'ip':
386 $out = $this->column_link( $record->{$column_name}, 'ip', $record->{$column_name} );
387 break;
388
389 default:
390 /**
391 * Registers new Columns to be inserted into the table. The cell contents of this column is set
392 * below with 'wp_stream_insert_column_default_'
393 *
394 * @since 3.5.1
395 * @deprecated 4.0.1 Use the {@see 'wp_stream_list_table_columns'} filter instead.
396 *
397 * @param array $new_columns Columns injected in the table.
398 *
399 * @return array
400 */
401 apply_filters_deprecated(
402 'wp_stream_register_column_defaults',
403 array( array() ),
404 /* translators: %s is the Stream version number. It is part of a filter deprecation notice and is preceded by: "{hook_name} is deprecated since version %s of Stream". */
405 sprintf( __( '%s of Stream', 'stream' ), '4.0.1' ),
406 'wp_stream_list_table_columns',
407 __( 'This filter is being deprecated as it is redundant. You can define custom column names and titles using the `wp_stream_list_table_columns` filter then provide the value for the custom columns using the `wp_stream_insert_column_default_{$column_name}` filter.', 'stream' )
408 );
409
410 /**
411 * Allows for the addition of content under a specified column.
412 *
413 * @param string $out Column content.
414 * @param object $record Record with row content.
415 * @param string $column_name Column name.
416 *
417 * @return string
418 */
419 $out = (string) apply_filters( "wp_stream_insert_column_default_{$column_name}", $out, $record, $column_name );
420 break;
421 }
422
423 $allowed_tags = wp_kses_allowed_html( 'post' );
424 $allowed_tags['time'] = array(
425 'datetime' => true,
426 'class' => true,
427 );
428 $allowed_tags['img']['srcset'] = true;
429
430 echo wp_kses( $out, $allowed_tags );
431 }
432
433 /**
434 * Returns the actions links for the provided record. (Eg. Edit, View)
435 *
436 * @param Record $record Record.
437 * @return string
438 */
439 public function get_action_links( $record ) {
440 $out = '';
441
442 /**
443 * Filter allows modification of action links for a specific connector
444 *
445 * @param array
446 * @param Record
447 *
448 * @return array Action links for this connector
449 */
450 $action_links = apply_filters( 'wp_stream_action_links_' . $record->connector, array(), $record );
451
452 /**
453 * Filter allows addition of custom links for a specific connector
454 *
455 * @param array
456 * @param Record
457 *
458 * @return array Custom links for this connector
459 */
460 $custom_links = apply_filters( 'wp_stream_custom_action_links_' . $record->connector, array(), $record );
461
462 if ( $action_links || $custom_links ) {
463 $out .= '<div class="row-actions">';
464 }
465
466 $links = array();
467 if ( $action_links && is_array( $action_links ) ) {
468 foreach ( $action_links as $al_title => $al_href ) {
469 $links[] = sprintf(
470 '<span><a href="%s" class="action-link">%s</a></span>',
471 $al_href,
472 $al_title
473 );
474 }
475 }
476
477 if ( $custom_links && is_array( $custom_links ) ) {
478 foreach ( $custom_links as $key => $link ) {
479 $links[] = $link;
480 }
481 }
482
483 $out .= implode( ' | ', $links );
484
485 if ( $action_links || $custom_links ) {
486 $out .= '</div>';
487 }
488
489 return $out;
490 }
491
492 /**
493 * Returns a link to be display in the table.
494 *
495 * @param string $display Text to be displayed in link.
496 * @param string|array $key Query string variable(s).
497 * @param string $value Single query string value.
498 * @param string $title Tooltip value.
499 * @return string
500 */
501 public function column_link( $display, $key, $value = null, $title = null ) {
502 $url = add_query_arg(
503 array(
504 'page' => $this->plugin->admin->records_page_slug,
505 ),
506 self_admin_url( $this->plugin->admin->admin_parent_page )
507 );
508
509 $args = ! is_array( $key ) ? array(
510 $key => $value,
511 ) : $key;
512
513 foreach ( $args as $k => $v ) {
514 $url = add_query_arg( $k, $v, $url );
515 }
516
517 return sprintf(
518 '<a href="%s" title="%s">%s</a>',
519 esc_url( $url ),
520 esc_attr( $title ),
521 $display
522 );
523 }
524
525 /**
526 * Returns the label for a connector term.
527 *
528 * @param string $term Connector label type.
529 * @param string $type Connector term.
530 * @return string
531 */
532 public function get_term_title( $term, $type ) {
533 if ( ! isset( $this->plugin->connectors->term_labels[ 'stream_' . $type ][ $term ] ) ) {
534 return $term;
535 }
536
537 return $this->plugin->connectors->term_labels[ 'stream_' . $type ][ $term ];
538 }
539
540 /**
541 * Assembles records for display in search filters
542 *
543 * Gathers list of all users/connectors, then compares it to
544 * results of existing records. All items that do not exist in records
545 * get assigned a disabled value of "true".
546 *
547 * @param string $column List table column name.
548 *
549 * @return array Options to be displayed in search filters
550 */
551 public function assemble_records( $column ) {
552 // @todo eliminate special condition for authors, especially using a WP_User object as the value; should use string or stringifiable object
553 if ( 'user_id' === $column ) {
554 $all_records = array();
555
556 // If the number of users exceeds the max users constant value then return an empty array and use AJAX instead.
557 $user_count = count_users();
558 $total_users = $user_count['total_users'];
559
560 if ( $total_users > $this->plugin->admin->preload_users_max ) {
561 $selected_user = wp_stream_filter_input( INPUT_GET, 'user_id' );
562 if ( $selected_user ) {
563 $user = new Author( $selected_user );
564
565 return array(
566 $selected_user => $user->get_display_name(),
567 );
568 } else {
569 return array();
570 }
571 }
572
573 $users = array_map(
574 function ( $user_id ) {
575 return new Author( $user_id );
576 },
577 get_users(
578 array(
579 'fields' => 'ID',
580 )
581 )
582 );
583
584 if ( is_multisite() && is_super_admin() ) {
585 $super_admins = array_map(
586 function ( $login ) {
587 $user = get_user_by( 'login', $login );
588
589 return new Author( $user->ID );
590 },
591 get_super_admins()
592 );
593 $users = array_unique( array_merge( $users, $super_admins ) );
594 }
595
596 $users[] = new Author(
597 0,
598 array(
599 'is_wp_cli' => true,
600 )
601 );
602
603 foreach ( $users as $user ) {
604 $all_records[ $user->id ] = $user->get_display_name();
605 }
606 } else {
607 $prefixed_column = sprintf( 'stream_%s', $column );
608 $all_records = $this->plugin->connectors->term_labels[ $prefixed_column ];
609 }
610
611 $existing_records = $this->plugin->db->existing_records( $column );
612 $active_records = array();
613 $disabled_records = array();
614
615 foreach ( $all_records as $record => $label ) {
616 if ( array_key_exists( $record, $existing_records ) ) {
617 $active_records[ $record ] = array(
618 'label' => $label,
619 'disabled' => false,
620 );
621 } else {
622 $disabled_records[ $record ] = array(
623 'label' => $label,
624 'disabled' => true,
625 );
626 }
627 }
628
629 // Remove WP-CLI pseudo user if no records with user=0 exist.
630 if ( isset( $disabled_records[0] ) ) {
631 unset( $disabled_records[0] );
632 }
633
634 $sort = function ( $a, $b ) use ( $column ) {
635 $label_a = (string) $a['label'];
636 $label_b = (string) $b['label'];
637
638 if ( $label_a === $label_b ) {
639 return 0;
640 }
641
642 return ( strtolower( $label_a ) < strtolower( $label_b ) ) ? - 1 : 1;
643 };
644
645 uasort( $active_records, $sort );
646 uasort( $disabled_records, $sort );
647
648 // Not using array_merge() in order to preserve the array index for the users dropdown which uses the user_id as the key.
649 $all_records = $active_records + $disabled_records;
650
651 return $all_records;
652 }
653
654 /**
655 * Returns list filters.
656 *
657 * @return array
658 */
659 public function get_filters() {
660 $filters = array();
661
662 $date_interval = new Date_Interval();
663
664 $filters['date'] = array(
665 'title' => __( 'dates', 'stream' ),
666 'items' => $date_interval->intervals,
667 );
668
669 $users = $this->get_users_dropdown_items(
670 $this->assemble_records( 'user_id' )
671 );
672
673 $filters['user_id'] = array(
674 'title' => __( 'users', 'stream' ),
675 'items' => $users,
676 'ajax' => count( $users ) <= 0,
677 );
678
679 $filters['context'] = array(
680 'title' => __( 'contexts', 'stream' ),
681 'items' => $this->assemble_records( 'context' ),
682 );
683
684 $filters['action'] = array(
685 'title' => __( 'actions', 'stream' ),
686 'items' => $this->assemble_records( 'action' ),
687 );
688
689 /**
690 * Filter allows additional filters in the list table dropdowns
691 * Note the format of the filters above, with they key and array
692 * containing a title and array of items.
693 *
694 * @return array
695 */
696 return apply_filters( 'wp_stream_list_table_filters', $filters );
697 }
698
699 /**
700 * Renders table filters form.
701 *
702 * @return void
703 */
704 public function render_filters_form() {
705 $filters = $this->get_filters();
706
707 printf( '<input type="hidden" name="page" value="%s" />', 'wp_stream' );
708 printf( '<span class="filter_info hidden">%s</span>', esc_html__( 'Show filter controls via the screen options tab above.', 'stream' ) );
709
710 foreach ( $filters as $name => $data ) {
711
712 $data = wp_parse_args(
713 $data,
714 array(
715 'title' => '',
716 'items' => array(),
717 'ajax' => false,
718 )
719 );
720
721 if ( 'date' === $name ) {
722 $this->filter_date( $data['items'] );
723 } else {
724 if ( 'context' === $name ) {
725 // Add Connectors as parents, and apply the Contexts as children.
726 $connectors = $this->assemble_records( 'connector' );
727 $context_items = array();
728
729 foreach ( $connectors as $connector => $item ) {
730 $context_items[ $connector ]['label'] = $item['label'];
731
732 foreach ( $data['items'] as $context_value => $context_item ) {
733 if ( isset( $this->plugin->connectors->contexts[ $connector ] ) && array_key_exists( $context_value, $this->plugin->connectors->contexts[ $connector ] ) ) {
734 $context_items[ $connector ]['children'][ $context_value ] = $context_item;
735 }
736 }
737
738 if ( isset( $context_items[ $connector ]['children'] ) ) {
739 $labels = wp_list_pluck( $context_items[ $connector ]['children'], 'label' );
740
741 // Sort child items by label.
742 array_multisort( $labels, SORT_ASC, $context_items[ $connector ]['children'] );
743 }
744 }
745
746 foreach ( $context_items as $context_value => $context_item ) {
747 if ( ! isset( $context_item['children'] ) || empty( $context_item['children'] ) ) {
748 unset( $context_items[ $context_value ] );
749 }
750 }
751
752 $data['items'] = $context_items;
753
754 $labels = wp_list_pluck( $data['items'], 'label' );
755
756 // Sort top-level items by label.
757 array_multisort( $labels, SORT_ASC, $data['items'] );
758
759 // Output a hidden input to handle the connector value.
760 printf(
761 '<input type="hidden" name="connector" class="record-filter-connector" value="%s" />',
762 esc_attr( wp_stream_filter_input( INPUT_GET, 'connector' ) )
763 );
764 }
765
766 $this->filter_select( $name, $data['title'], $data['items'], $data['ajax'] );
767 }
768 }
769
770 printf(
771 '<input type="submit" id="record-query-submit" class="button" value="%s" />',
772 esc_attr__( 'Filter', 'stream' )
773 );
774
775 // Parse all query vars into an array.
776 $query_vars = array();
777
778 if ( isset( $_SERVER['QUERY_STRING'] ) ) {
779 parse_str( wp_unslash( $_SERVER['QUERY_STRING'] ), $query_vars );
780 $query_vars = map_deep( $query_vars, 'sanitize_text_field' );
781 }
782
783 // Ignore certain query vars and query vars that are empty.
784 foreach ( $query_vars as $query_var => $value ) {
785 if ( '' === $value || 'page' === $query_var || 'paged' === $query_var ) {
786 unset( $query_vars[ $query_var ] );
787 }
788 }
789
790 $url = add_query_arg(
791 array(
792 'page' => $this->plugin->admin->records_page_slug,
793 ),
794 self_admin_url( $this->plugin->admin->admin_parent_page )
795 );
796
797 // Display reset action if records are being filtered.
798 if ( ! empty( $query_vars ) ) {
799 printf(
800 '<a href="%1$s" id="record-query-reset"><span class="dashicons dashicons-dismiss"></span> <span class="record-query-reset-text">%2$s</span></a>',
801 esc_url( $url ),
802 esc_html__( 'Reset filters', 'stream' )
803 );
804 }
805 }
806
807 /**
808 * Renders a filterable select control with filtered items.
809 *
810 * @param string $name Search input.
811 * @param string $title Name of the control.
812 * @param array $items Items to be filtered.
813 * @param boolean $ajax Whether is an ajax request or not.
814 *
815 * @return void
816 */
817 public function filter_select( $name, $title, $items, $ajax = false ) {
818 $selected = wp_stream_filter_input( INPUT_GET, $name );
819
820 printf(
821 '<select name="%1$s" class="chosen-select" data-placeholder="%2$s">',
822 esc_attr( $name ),
823 esc_attr(
824 sprintf(
825 /* translators: %s: the title of the dropdown menu (e.g. "users") */
826 __( 'Show all %s', 'stream' ),
827 $title
828 )
829 )
830 );
831
832 // First option should be empty.
833 echo '<option value=""></option>';
834
835 foreach ( $items as $key => $item ) {
836 $value = isset( $item['children'] ) ? 'group-' . $key : $key;
837 $option_args = array(
838 'value' => $value,
839 'selected' => (string) $value === (string) $selected,
840 'disabled' => ! empty( $item['disabled'] ),
841 'icon' => isset( $item['icon'] ) ? $item['icon'] : null,
842 'group' => isset( $item['children'] ) ? $key : null,
843 'tooltip' => isset( $item['tooltip'] ) ? $item['tooltip'] : null,
844 'class' => isset( $item['children'] ) ? 'level-1' : null,
845 'label' => isset( $item['label'] ) ? $item['label'] : null,
846 );
847 $this->filter_option( $option_args );
848
849 if ( isset( $item['children'] ) ) {
850 foreach ( $item['children'] as $child_value => $child_item ) {
851 $option_args = array(
852 'value' => $child_value,
853 'selected' => (string) $child_value === (string) $selected,
854 'disabled' => ! empty( $child_item['disabled'] ),
855 'icon' => isset( $child_item['icon'] ) ? $child_item['icon'] : null,
856 'group' => $key,
857 'tooltip' => isset( $child_item['tooltip'] ) ? $child_item['tooltip'] : null,
858 'class' => 'level-2',
859 'label' => isset( $child_item['label'] ) ? '- ' . $child_item['label'] : null,
860 );
861 $this->filter_option( $option_args );
862 }
863 }
864 }
865
866 echo '</select>';
867 }
868
869 /**
870 * Render a filterable select option.
871 *
872 * @param array $args Option attributes.
873 *
874 * @return void
875 */
876 public function filter_option( $args ) {
877 $defaults = array(
878 'value' => null,
879 'selected' => false,
880 'disabled' => false,
881 'icon' => null,
882 'group' => null,
883 'tooltip' => null,
884 'class' => null,
885 'label' => null,
886 );
887 wp_parse_args( $args, $defaults );
888
889 printf(
890 '<option value="%s" %s %s %s %s %s class="%s">%s</option>',
891 esc_attr( $args['value'] ),
892 selected( $args['selected'], true, false ),
893 disabled( $args['disabled'], true, false ),
894 $args['icon'] ? sprintf( 'data-icon="%s"', esc_attr( $args['icon'] ) ) : null,
895 $args['group'] ? sprintf( 'data-group="%s"', esc_attr( $args['group'] ) ) : null,
896 $args['tooltip'] ? sprintf( 'title="%s"', esc_attr( $args['tooltip'] ) ) : null,
897 $args['class'] ? esc_attr( $args['class'] ) : null,
898 esc_html( $args['label'] )
899 );
900 }
901
902 /**
903 * Render filter search box.
904 *
905 * @return void
906 */
907 public function filter_search() {
908 printf(
909 '<p class="search-box">
910 <label class="screen-reader-text" for="record-search-input">%1$s:</label>
911 <input type="search" id="record-search-input" name="search" value="%2$s" />
912 <input type="submit" name="" id="search-submit" class="button" value="%3$s" />
913 </p>',
914 esc_html__( 'Search Records', 'stream' ),
915 esc_attr( ! empty( $_GET['search'] ) ? sanitize_text_field( wp_unslash( $_GET['search'] ) ) : '' ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended
916 esc_attr__( 'Search Records', 'stream' )
917 );
918 }
919
920 /**
921 * Renders a filter select box based upon date.
922 *
923 * @param array $items Records.
924 *
925 * @return void
926 */
927 public function filter_date( $items ) {
928 wp_enqueue_style( 'jquery-ui' );
929 wp_enqueue_style( 'wp-stream-datepicker' );
930 wp_enqueue_script( 'jquery-ui-datepicker' );
931
932 $date_predefined = wp_stream_filter_input( INPUT_GET, 'date_predefined' );
933 $date_from = wp_stream_filter_input( INPUT_GET, 'date_from' );
934 $date_to = wp_stream_filter_input( INPUT_GET, 'date_to' );
935
936 ?>
937 <div class="date-interval">
938
939 <select class="field-predefined hide-if-no-js chosen-select" name="date_predefined" data-placeholder="<?php esc_attr_e( 'All Time', 'stream' ); ?>">
940 <option></option>
941 <option value="custom" <?php selected( 'custom' === $date_predefined ); ?>><?php esc_attr_e( 'Custom', 'stream' ); ?></option>
942 <?php
943 foreach ( $items as $key => $interval ) {
944 $end = isset( $interval['end'] ) ? $interval['end']->format( 'Y/m/d' ) : null;
945
946 printf(
947 '<option value="%s" data-from="%s" data-to="%s" %s>%s</option>',
948 esc_attr( $key ),
949 esc_attr( $interval['start']->format( 'Y/m/d' ) ),
950 esc_attr( $end ),
951 selected( $key === $date_predefined ),
952 esc_html( $interval['label'] )
953 );
954 }
955 ?>
956 </select>
957
958 <div class="date-inputs">
959 <div class="box">
960 <i class="date-remove dashicons"></i>
961 <input type="text" name="date_from" class="date-picker field-from" placeholder="<?php esc_attr_e( 'Start Date', 'stream' ); ?>" value="<?php echo esc_attr( $date_from ); ?>"/>
962 </div>
963 <span class="connector dashicons"></span>
964
965 <div class="box">
966 <i class="date-remove dashicons"></i>
967 <input type="text" name="date_to" class="date-picker field-to" placeholder="<?php esc_attr_e( 'End Date', 'stream' ); ?>" value="<?php echo esc_attr( $date_to ); ?>"/>
968 </div>
969 </div>
970
971 </div>
972 <?php
973 }
974
975 /**
976 * Render a Select dropdown of actions relating to the Stream records
977 *
978 * @return void
979 */
980 public function record_actions_form() {
981 /**
982 * Filter the records screen actions dropdown menu
983 *
984 * @return array Should be in the format of action_slug => 'Action Name'
985 */
986 $actions = apply_filters( 'wp_stream_record_actions_menu', array() );
987
988 if ( empty( $actions ) ) {
989 return;
990 }
991
992 printf( '<div class="alignleft actions recordactions"><select name="%s">', esc_attr( 'record-actions' ) );
993 printf( '<option value="">%s</option>', esc_attr__( 'Record Actions', 'stream' ) );
994 foreach ( $actions as $value => $name ) {
995 printf(
996 '<option value="%s">%s</option>',
997 esc_attr( $value ),
998 esc_attr( $name )
999 );
1000 }
1001 echo '</select></div>';
1002 wp_nonce_field( 'stream_record_actions_nonce', 'stream_record_actions_nonce' );
1003 wp_nonce_field( 'stream_filters_user_search_nonce', 'stream_filters_user_search_nonce' );
1004
1005 printf( '<input type="hidden" name="page" value="%s">', esc_attr( wp_stream_filter_input( INPUT_GET, 'page' ) ) );
1006 printf( '<input type="hidden" name="date_predefined" value="%s">', esc_attr( wp_stream_filter_input( INPUT_GET, 'date_predefined' ) ) );
1007 printf( '<input type="hidden" name="date_from" value="%s">', esc_attr( wp_stream_filter_input( INPUT_GET, 'date_from' ) ) );
1008 printf( '<input type="hidden" name="date_to" value="%s">', esc_attr( wp_stream_filter_input( INPUT_GET, 'date_to' ) ) );
1009 printf( '<input type="hidden" name="user_id" value="%s">', esc_attr( wp_stream_filter_input( INPUT_GET, 'user_id' ) ) );
1010 printf( '<input type="hidden" name="connector" value="%s">', esc_attr( wp_stream_filter_input( INPUT_GET, 'connector' ) ) );
1011 printf( '<input type="hidden" name="context" value="%s">', esc_attr( wp_stream_filter_input( INPUT_GET, 'context' ) ) );
1012 printf( '<input type="hidden" name="action" value="%s">', esc_attr( wp_stream_filter_input( INPUT_GET, 'action' ) ) );
1013
1014 printf( '<input type="submit" name="" id="record-actions-submit" class="button" value="%s">', esc_attr__( 'Apply', 'stream' ) );
1015 echo '<div class="clear"></div>';
1016 }
1017
1018 /**
1019 * Renders record filter forms.
1020 */
1021 public function display() {
1022 $url = self_admin_url( $this->plugin->admin->admin_parent_page );
1023
1024 echo '<form method="get" action="' . esc_url( $url ) . '" id="record-filter-form">';
1025 $this->filter_search();
1026 parent::display();
1027 echo '</form>';
1028
1029 echo '<form method="get" action="' . esc_url( $url ) . '" id="record-actions-form">';
1030 $this->record_actions_form();
1031 echo '</form>';
1032 }
1033
1034 /**
1035 * Renders a single record
1036 *
1037 * @param array $item Record data.
1038 */
1039 public function single_row( $item ) {
1040 $classes = apply_filters( 'wp_stream_record_classes', array(), $item );
1041
1042 if ( empty( $classes ) ) {
1043 echo '<tr>';
1044 } else {
1045 printf( '<tr class="%s">', esc_attr( join( ' ', $classes ) ) );
1046 }
1047
1048 $this->single_row_columns( $item );
1049 echo '</tr>';
1050 }
1051
1052 /**
1053 * Renders table navigation controls
1054 *
1055 * @param string $which Location controls.
1056 */
1057 public function display_tablenav( $which ) {
1058 if ( 'top' === $which ) :
1059 ?>
1060 <div class="tablenav <?php echo esc_attr( $which ); ?>">
1061 <?php
1062 $this->pagination( $which );
1063 $this->extra_tablenav( $which );
1064 ?>
1065
1066 <br class="clear"/>
1067 </div>
1068 <?php else : ?>
1069 <div class="tablenav <?php echo esc_attr( $which ); ?>">
1070 <?php
1071 /**
1072 * Fires after the list table is displayed.
1073 */
1074 do_action( 'wp_stream_after_list_table' );
1075 $this->pagination( $which );
1076 $this->extra_tablenav( $which );
1077 ?>
1078
1079 <br class="clear"/>
1080 </div>
1081 <?php
1082 endif;
1083 }
1084
1085 /**
1086 * Sets the screen options.
1087 *
1088 * @param string $dummy Unused.
1089 * @param string $option Screen option name.
1090 * @param string $value Screen option value.
1091 * @return string
1092 */
1093 public function set_screen_option( $dummy, $option, $value ) {
1094 if ( 'edit_stream_per_page' === $option ) {
1095 return $value;
1096 } else {
1097 return $dummy;
1098 }
1099 }
1100
1101 /**
1102 * Sets the live update options.
1103 *
1104 * @param string $dummy Unused.
1105 * @param string $option Screen option name.
1106 * @param string $value Screen option value.
1107 * @return string
1108 */
1109 public function set_live_update_option( $dummy, $option, $value ) {
1110 unset( $value );
1111
1112 // @codingStandardsIgnoreStart
1113 if (
1114 $this->plugin->admin->live_update->user_meta_key === $option
1115 &&
1116 isset( $_POST[ $this->plugin->admin->live_update->user_meta_key ] )
1117 ) {
1118 $value = esc_attr( $_POST[ $this->plugin->admin->live_update->user_meta_key ] ); //input var okay
1119
1120 return $value;
1121 }
1122
1123 // @codingStandardsIgnoreEnd
1124
1125 return $dummy;
1126 }
1127
1128 /**
1129 * Return HTML string of the "Live updates" screen option.
1130 *
1131 * @param string $status Unused.
1132 * @param array $args Unused.
1133 * @return string
1134 */
1135 public function screen_controls( $status, $args ) {
1136 unset( $status );
1137 unset( $args );
1138
1139 $user_id = get_current_user_id();
1140 $option = $this->plugin->admin->get_user_meta( $user_id, $this->plugin->admin->live_update->user_meta_key, true );
1141 $heartbeat = wp_script_is( 'heartbeat', 'done' ) ? 'true' : 'false';
1142
1143 if ( 'on' === $option && 'false' === $heartbeat ) {
1144 $option = 'off';
1145
1146 update_user_meta( $user_id, $this->plugin->admin->live_update->user_meta_key, 'off' );
1147 }
1148
1149 $nonce = wp_create_nonce( $this->plugin->admin->live_update->user_meta_key . '_nonce' );
1150
1151 ob_start();
1152 ?>
1153 <fieldset>
1154 <h5><?php esc_html_e( 'Live updates', 'stream' ); ?></h5>
1155
1156 <div>
1157 <input type="hidden" name="stream_live_update_nonce" id="stream_live_update_nonce" value="<?php echo esc_attr( $nonce ); ?>"/>
1158 </div>
1159 <div class="metabox-prefs stream-live-update-checkbox">
1160 <label for="enable_live_update">
1161 <input type="checkbox" value="on" name="enable_live_update" id="enable_live_update" data-heartbeat="<?php echo esc_attr( $heartbeat ); ?>" <?php checked( $option, 'on' ); ?> />
1162 <?php esc_html_e( 'Enabled', 'stream' ); ?>
1163 <span class="spinner"></span>
1164 </label>
1165 </div>
1166 </fieldset>
1167 <?php
1168 return ob_get_clean();
1169 }
1170
1171 /**
1172 * This function is use to map List table column name with excluded setting keys
1173 *
1174 * @param string $column List table column name.
1175 *
1176 * @return string setting name for that column
1177 */
1178 public function get_column_excluded_setting_key( $column ) {
1179 switch ( $column ) {
1180 case 'connector':
1181 $output = 'connectors';
1182 break;
1183 case 'context':
1184 $output = 'contexts';
1185 break;
1186 case 'action':
1187 $output = 'action';
1188 break;
1189 case 'ip':
1190 $output = 'ip_addresses';
1191 break;
1192 case 'user_id':
1193 $output = 'users';
1194 break;
1195 default:
1196 $output = false;
1197 }
1198
1199 return $output;
1200 }
1201
1202 /**
1203 * Get users as dropdown items
1204 *
1205 * @param array $users Users.
1206 *
1207 * @return array
1208 */
1209 public function get_users_dropdown_items( $users ) {
1210 $record_meta = array();
1211
1212 foreach ( $users as $user_id => $args ) {
1213 $user = new Author( $user_id );
1214
1215 $record_meta[ $user_id ] = array(
1216 'text' => $user->get_display_name(),
1217 'id' => $user_id,
1218 'label' => $user->get_display_name(),
1219 'icon' => $user->get_avatar_src( 32 ),
1220 'title' => '',
1221 'disabled' => ! empty( $args['disabled'] ),
1222 );
1223 }
1224
1225 return $record_meta;
1226 }
1227 }
1228