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

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

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