PluginProbe
WebberZone Top 10 — Popular Posts / 4.3.2
WebberZone Top 10 — Popular Posts v4.3.2
4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 All 117 releases
top-10 / includes / admin / class-statistics-table.php

class-statistics-table.php in WebberZone Top 10 — Popular Posts 4.3.2, at includes/admin/class-statistics-table.php

654 lines 19.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Statistics Table class.
4 *
5 * @package WebberZone\Top_Ten\Admin
6 */
7
8 namespace WebberZone\Top_Ten\Admin;
9
10 use WebberZone\Top_Ten\Database;
11 use WebberZone\Top_Ten\Util\Helpers;
12
13 if ( ! defined( 'WPINC' ) ) {
14 die;
15 }
16
17 if ( ! class_exists( '\WP_List_Table' ) ) {
18 require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
19 }
20
21 /**
22 * Statistics Table class to display the popular counts.
23 *
24 * @since 3.3.0
25 */
26 class Statistics_Table extends \WP_List_Table {
27
28 /**
29 * Holds the post type array elements for translation.
30 *
31 * @var array
32 */
33 public $all_post_type;
34
35 /**
36 * Network wide popular posts flag.
37 *
38 * @var bool
39 */
40 public $network_wide;
41
42 /**
43 * Class constructor.
44 *
45 * @param bool $network_wide Network wide popular posts.
46 */
47 public function __construct( $network_wide = false ) {
48 parent::__construct(
49 array(
50 'singular' => __( 'popular_post', 'top-10' ), // Singular name of the listed records.
51 'plural' => __( 'popular_posts', 'top-10' ), // plural name of the listed records.
52 )
53 );
54 $this->all_post_type = array(
55 'all' => __( 'All post types', 'top-10' ),
56 );
57 $this->network_wide = $network_wide;
58 }
59
60 /**
61 * Retrieve the Top 10 posts
62 *
63 * @param int $per_page Posts per page.
64 * @param int $page_number Page number.
65 * @param array $args Array of arguments.
66 *
67 * @return array Array of popular posts
68 */
69 public function get_popular_posts( $per_page = 20, $page_number = 1, $args = null ) {
70
71 global $wpdb;
72
73 // Initialise some variables.
74 $fields = array();
75 $where = '';
76 $join = '';
77 $groupby = '';
78 $orderby = '';
79 $limits = '';
80 $sql = '';
81
82 $blog_id = get_current_blog_id();
83
84 $from_date = isset( $args['post-date-filter-from'] ) ? $args['post-date-filter-from'] : current_time( 'd M Y' );
85 $from_date = gmdate( 'Y-m-d', strtotime( $from_date ) );
86 $to_date = isset( $args['post-date-filter-to'] ) ? $args['post-date-filter-to'] : current_time( 'd M Y' );
87 $to_date = gmdate( 'Y-m-d', strtotime( $to_date ) );
88
89 /* Start creating the SQL */
90 $table_name_daily = $wpdb->base_prefix . 'top_ten_daily AS ttd';
91 $table_name = $wpdb->base_prefix . 'top_ten AS ttt';
92
93 // Fields to return.
94 if ( ! $this->network_wide ) {
95 $fields[] = "{$wpdb->posts}.post_title as title";
96 $fields[] = "{$wpdb->posts}.post_type";
97 $fields[] = "{$wpdb->posts}.post_date";
98 $fields[] = "{$wpdb->posts}.post_author";
99 }
100
101 $fields[] = 'ttt.postnumber as ID';
102 $fields[] = 'ttt.cntaccess as total_count';
103 $fields[] = 'SUM(ttd.cntaccess) as daily_count';
104 $fields[] = 'ttt.blog_id as blog_id';
105
106 $fields = implode( ', ', $fields );
107
108 // Create the JOIN clause.
109 if ( ! $this->network_wide ) {
110 $join .= " LEFT JOIN {$wpdb->posts} ON ttt.postnumber={$wpdb->posts}.ID ";
111 }
112 $join .= $wpdb->prepare(
113 " LEFT JOIN (
114 SELECT * FROM {$table_name_daily} " . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
115 'WHERE DATE(ttd.dp_date) >= DATE(%s) AND DATE(ttd.dp_date) <= DATE(%s)
116 ) AS ttd
117 ON ttt.postnumber=ttd.postnumber AND ttt.blog_id=ttd.blog_id
118 ',
119 $from_date,
120 $to_date
121 );
122
123 // Create the base WHERE clause.
124 if ( ! $this->network_wide ) {
125 $where .= $wpdb->prepare( ' AND ttt.blog_id = %d ', $blog_id ); // Posts need to be from the current blog only.
126 $where .= " AND ($wpdb->posts.post_status = 'publish' OR $wpdb->posts.post_status = 'inherit') "; // Show published posts and attachments.
127
128 // If search argument is set, do a search for it.
129 if ( ! empty( $args['search'] ) ) {
130 $where .= $wpdb->prepare( " AND $wpdb->posts.post_title LIKE %s ", '%' . $wpdb->esc_like( $args['search'] ) . '%' );
131 }
132
133 // If post filter argument is set, do a search for it.
134 if ( isset( $args['post-type-filter'] ) && $this->all_post_type['all'] !== $args['post-type-filter'] ) {
135 $where .= $wpdb->prepare( " AND $wpdb->posts.post_type = %s ", $args['post-type-filter'] );
136 } else {
137 $post_types = get_post_types(
138 array(
139 'public' => true,
140 )
141 );
142 $where .= " AND $wpdb->posts.post_type IN ('" . join( "', '", $post_types ) . "') ";
143 }
144 }
145
146 // Create the base GROUP BY clause.
147 $groupby = ' ttt.postnumber, ttt.blog_id ';
148
149 // Create the ORDER BY clause.
150 $orderby = '';
151 if ( ! empty( $_REQUEST['orderby'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
152 $orderby = sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
153 } elseif ( ! empty( $args['orderby'] ) ) {
154 $orderby = $args['orderby'];
155 }
156
157 if ( $orderby ) {
158 if ( ! in_array( $orderby, array( 'title', 'daily_count', 'total_count' ) ) ) { //phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
159 $orderby = ' total_count ';
160 }
161
162 $order = '';
163 if ( ! empty( $_REQUEST['order'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
164 $order = sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
165 } elseif ( ! empty( $args['order'] ) ) {
166 $order = $args['order'];
167 }
168
169 if ( $order && in_array( $order, array( 'asc', 'ASC', 'desc', 'DESC' ), true ) ) {
170 $orderby .= " {$order}";
171 } else {
172 $orderby .= ' DESC';
173 }
174 } else {
175 $orderby = ' total_count DESC ';
176 }
177
178 // Create the base LIMITS clause.
179 $limits = $wpdb->prepare( ' LIMIT %d, %d ', ( $page_number - 1 ) * $per_page, $per_page );
180
181 $groupby = " GROUP BY {$groupby} ";
182 $orderby = " ORDER BY {$orderby} ";
183
184 $sql = "SELECT $fields FROM {$table_name} $join WHERE 1=1 $where $groupby $orderby $limits";
185
186 $result = $wpdb->get_results( $sql, 'ARRAY_A' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
187
188 return $result;
189 }
190
191 /**
192 * Returns the count of records in the database.
193 *
194 * @param array $args Array of arguments.
195 * @return null|string null|string
196 */
197 public function record_count( $args = null ) {
198
199 global $wpdb;
200
201 $where = '';
202 $join = '';
203
204 $sql = "SELECT COUNT(*) FROM {$wpdb->base_prefix}top_ten as ttt";
205
206 if ( ! $this->network_wide ) {
207 $join = "INNER JOIN {$wpdb->posts} ON ttt.postnumber=ID";
208 $where .= $wpdb->prepare( ' AND blog_id = %d ', get_current_blog_id() );
209
210 if ( ! empty( $args['search'] ) ) {
211 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title LIKE %s ", '%' . $wpdb->esc_like( $args['search'] ) . '%' );
212 }
213
214 if ( isset( $args['post-type-filter'] ) && $this->all_post_type['all'] !== $args['post-type-filter'] ) {
215 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s ", $args['post-type-filter'] );
216 } else {
217 $post_types = get_post_types(
218 array(
219 'public' => true,
220 )
221 );
222 $where .= " AND $wpdb->posts.post_type IN ('" . join( "', '", $post_types ) . "') ";
223 }
224 }
225 return $wpdb->get_var( "$sql $join WHERE 1=1 $where" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
226 }
227
228 /**
229 * Delete the post count for this post.
230 *
231 * @param int $id Post ID.
232 * @param int $blog_id Blog ID.
233 */
234 public static function delete_post_count( $id, $blog_id ) {
235 \WebberZone\Top_Ten\Counter::delete_count( $id, $blog_id, false );
236 \WebberZone\Top_Ten\Counter::delete_count( $id, $blog_id, true );
237 }
238
239 /**
240 * Text displayed when no post data is available
241 */
242 public function no_items() {
243 esc_html_e( 'No popular posts available.', 'top-10' );
244 }
245
246
247 /**
248 * Render a column when no column specific method exist.
249 *
250 * @param array $item Current item.
251 * @param string $column_name Column name.
252 *
253 * @return mixed
254 */
255 public function column_default( $item, $column_name ) {
256 switch ( $column_name ) {
257 case 'daily_count':
258 return \WebberZone\Top_Ten\Util\Helpers::number_format_i18n( absint( $item[ $column_name ] ) );
259 default:
260 // Show the whole array for troubleshooting purposes.
261 return '';
262 }
263 }
264
265 /**
266 * Render the checkbox column.
267 *
268 * @param array $item Current item.
269 * @return string
270 */
271 public function column_cb( $item ) {
272 return sprintf(
273 '<input type="checkbox" name="%1$s[]" value="%2$s-%3$s" />',
274 'bulk-delete',
275 $item['ID'],
276 $item['blog_id']
277 );
278 }
279
280 /**
281 * Render the title column.
282 *
283 * @param array $item Current item.
284 * @return string
285 */
286 public function column_title( $item ) {
287
288 $delete_nonce = wp_create_nonce( 'tptn_delete_entry' );
289 $page = isset( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
290 $post = $this->network_wide ? get_blog_post( $item['blog_id'], $item['ID'] ) : get_post( $item['ID'] );
291
292 if ( null === $post ) {
293 return sprintf(
294 '%s <span style="color:grey">(id:%2$s)</span>',
295 __( 'Invalid post ID. This post might have been deleted.', 'top-10' ),
296 $item['ID']
297 );
298 }
299
300 $actions = array(
301 'view' => sprintf( '<a href="%s" target="_blank">' . __( 'View', 'top-10' ) . '</a>', get_permalink( $item['ID'] ) ),
302 'edit' => sprintf( '<a href="%s">' . __( 'Edit', 'top-10' ) . '</a>', get_edit_post_link( $item['ID'] ) ),
303 'delete' => sprintf(
304 '<a href="?page=%1$s&action=%2$s&post=%3$s&blog_id=%4$s&_wpnonce=%5$s">' . __( 'Delete', 'top-10' ) . '</a>',
305 esc_attr( $page ),
306 'delete',
307 absint( $item['ID'] ),
308 absint( $item['blog_id'] ),
309 $delete_nonce
310 ),
311 );
312
313 // Return the title contents.
314 return sprintf(
315 '<a href="%4$s" target="_blank">%1$s</a> <span style="color:grey">(id:%2$s)</span>%3$s',
316 $post->post_title,
317 $item['ID'],
318 $this->network_wide ? '' : $this->row_actions( $actions ),
319 is_multisite() ? get_blog_permalink( $item['blog_id'], $item['ID'] ) : get_permalink( $item['ID'] )
320 );
321 }
322
323
324 /**
325 * Handles the post date column output.
326 *
327 * @param array $item Current item.
328 * @return string Post date.
329 */
330 public function column_date( $item ) {
331
332 $post = is_multisite() ? get_blog_post( $item['blog_id'], $item['ID'] ) : get_post( $item['ID'] );
333
334 if ( $post ) {
335 $m_time = strtotime( $post->post_date );
336 $h_time = wp_date( get_option( 'date_format' ), $m_time );
337
338 return sprintf(
339 '<abbr title="%1$s">%1$s</abbr>',
340 esc_attr( $h_time )
341 );
342 }
343 return '';
344 }
345
346 /**
347 * Handles the post_type column output.
348 *
349 * @param array $item Current item.
350 * @return string Post Type.
351 */
352 public function column_post_type( $item ) {
353
354 $post = is_multisite() ? get_blog_post( $item['blog_id'], $item['ID'] ) : get_post( $item['ID'] );
355
356 if ( $post ) {
357 $pt = get_post_type_object( $post->post_type );
358 if ( empty( $pt ) ) {
359 return $post->post_type;
360 }
361 $name = isset( $pt->labels->singular_name ) ? $pt->labels->singular_name : $pt->labels->name;
362 return $name;
363 }
364 return '';
365 }
366
367 /**
368 * Handles the post author column output.
369 *
370 * @param array $item Current item.
371 * @return string Post Author.
372 */
373 public function column_author( $item ) {
374
375 $post = is_multisite() ? get_blog_post( $item['blog_id'], $item['ID'] ) : get_post( $item['ID'] );
376 if ( ! $post ) {
377 return '';
378 }
379
380 $author_info = get_userdata( (int) $post->post_author );
381 $author_name = ( false === $author_info ) ? '' : ucwords( trim( stripslashes( $author_info->display_name ) ) );
382
383 return sprintf(
384 '<a href="%s">%s</a>',
385 esc_url(
386 add_query_arg(
387 array(
388 'post_type' => $post->post_type,
389 'author' => ( false === $author_info ) ? 0 : $author_info->ID,
390 ),
391 get_admin_url( $item['blog_id'], 'edit.php' )
392 )
393 ),
394 esc_html( $author_name )
395 );
396 }
397
398 /**
399 * Render the Total Count column.
400 *
401 * @param array $item Current item.
402 * @return string
403 */
404 public function column_total_count( $item ) {
405 return sprintf(
406 '<div contentEditable="true" class="live_edit" id="total_count_%1$s" data-wp-post-id="%1$s" data-wp-count="%2$s"%3$s>%4$s</div>',
407 $item['ID'],
408 $item['total_count'],
409 $this->network_wide ? ' data-wp-blog-id="' . esc_attr( $item['blog_id'] ) . '"' : '',
410 \WebberZone\Top_Ten\Util\Helpers::number_format_i18n( absint( $item['total_count'] ) )
411 );
412 }
413
414 /**
415 * Handles the blog id column output.
416 *
417 * @param array $item Current item.
418 * @return void
419 */
420 public function column_blog_id( $item ) {
421
422 $blog_details = get_blog_details( $item['blog_id'] );
423
424 printf(
425 '<a href="%s" target="_blank">%s</a>',
426 esc_url(
427 add_query_arg(
428 array(
429 'page' => 'tptn_popular_posts',
430 ),
431 get_admin_url( $item['blog_id'] ) . 'admin.php'
432 )
433 ),
434 esc_html( $blog_details->blogname )
435 );
436 }
437
438 /**
439 * Associative array of columns
440 *
441 * @return array
442 */
443 public function get_columns() {
444 $columns = array(
445 'cb' => '<input type="checkbox" />',
446 'title' => __( 'Title', 'top-10' ),
447 'total_count' => __( 'Total visits', 'top-10' ),
448 /* translators: %s: Custom period label (e.g. Daily, Custom (7 days)). */
449 'daily_count' => sprintf( __( '%s visits', 'top-10' ), Helpers::get_daily_range_label() ),
450 'post_type' => __( 'Post type', 'top-10' ),
451 'author' => __( 'Author', 'top-10' ),
452 'date' => __( 'Date', 'top-10' ),
453 );
454
455 if ( $this->network_wide ) {
456 $columns['blog_id'] = __( 'Blog', 'top-10' );
457 }
458
459 /**
460 * Filter the columns displayed in the Posts list table.
461 *
462 * @since 1.5.0
463 *
464 * @param array $columns An array of column names.
465 */
466 return apply_filters( 'manage_pop_posts_columns', $columns );
467 }
468
469 /**
470 * Columns to make sortable.
471 *
472 * @return array
473 */
474 public function get_sortable_columns() {
475 $sortable_columns = array(
476 'total_count' => array( 'total_count', true ),
477 'daily_count' => array( 'daily_count', true ),
478 );
479 if ( ! $this->network_wide ) {
480 $sortable_columns['title'] = array( 'title', false );
481 }
482 return $sortable_columns;
483 }
484
485 /**
486 * Returns an associative array containing the bulk action
487 *
488 * @return array
489 */
490 public function get_bulk_actions() {
491 $actions = array(
492 'bulk-delete' => __( 'Delete Count', 'top-10' ),
493 );
494 return $actions;
495 }
496
497 /**
498 * Handles data query and filter, sorting, and pagination.
499 */
500 public function prepare_items() {
501
502 $this->_column_headers = $this->get_column_info();
503
504 /** Process bulk action */
505 $this->process_bulk_action();
506
507 $per_page = $this->get_items_per_page( 'pop_posts_per_page', 20 );
508
509 $current_page = $this->get_pagenum();
510
511 $args = array();
512
513 // If this is a search?
514 if ( isset( $_REQUEST['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
515 $args['search'] = sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
516 }
517 // If this is a post type filter?
518 if ( isset( $_REQUEST['post-type-filter'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
519 $args['post-type-filter'] = sanitize_text_field( wp_unslash( $_REQUEST['post-type-filter'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
520 }
521
522 // If this is a post date filter?
523 if ( isset( $_REQUEST['post-date-filter-to'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
524 $args['post-date-filter-to'] = sanitize_text_field( wp_unslash( $_REQUEST['post-date-filter-to'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
525 }
526 if ( isset( $_REQUEST['post-date-filter-from'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
527 $args['post-date-filter-from'] = sanitize_text_field( wp_unslash( $_REQUEST['post-date-filter-from'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
528 }
529
530 $this->items = self::get_popular_posts( $per_page, $current_page, $args );
531 $total_items = (int) self::record_count( $args );
532
533 $this->set_pagination_args(
534 array(
535 'total_items' => $total_items, // WE have to calculate the total number of items.
536 'per_page' => $per_page, // WE have to determine how many items to show on a page.
537 'total_pages' => (int) ceil( $total_items / $per_page ), // WE have to calculate the total number of pages.
538 )
539 );
540 }
541
542 /**
543 * Handles any bulk actions
544 */
545 public function process_bulk_action() {
546
547 // Detect when a bulk action is being triggered...
548 if ( 'delete' === $this->current_action() ) {
549 // In our file that handles the request, verify the nonce.
550 $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0;
551 $blog_id = isset( $_GET['blog_id'] ) ? absint( $_GET['blog_id'] ) : get_current_blog_id();
552
553 if ( isset( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'tptn_delete_entry' ) ) {
554 self::delete_post_count( $post_id, $blog_id );
555 } else {
556 die( esc_html__( 'Are you sure you want to do this', 'top-10' ) );
557 }
558 }
559
560 // If the delete bulk action is triggered.
561 if ( ( isset( $_REQUEST['action'] ) && 'bulk-delete' === $_REQUEST['action'] )
562 || ( isset( $_REQUEST['action2'] ) && 'bulk-delete' === $_REQUEST['action2'] )
563 ) {
564 $delete_ids = isset( $_REQUEST['bulk-delete'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_REQUEST['bulk-delete'] ) ) : array();
565
566 // Loop over the array of record IDs and delete them.
567 $post_ids = array();
568 $blog_ids = array();
569 foreach ( $delete_ids as $id ) {
570 $pieces = explode( '-', $id );
571 $post_ids[] = absint( $pieces[0] );
572 $blog_ids[] = absint( $pieces[1] );
573 }
574 \WebberZone\Top_Ten\Counter::delete_counts(
575 array(
576 'post_id' => $post_ids,
577 'blog_id' => $blog_ids,
578 'daily' => true,
579 )
580 );
581 \WebberZone\Top_Ten\Counter::delete_counts(
582 array(
583 'post_id' => $post_ids,
584 'blog_id' => $blog_ids,
585 'daily' => false,
586 )
587 );
588 }
589 }
590
591 /**
592 * Adds extra navigation elements to the table.
593 *
594 * @param string $which Which part of the table are we.
595 */
596 public function extra_tablenav( $which ) {
597 ?>
598 <div class="alignleft actions">
599 <?php
600 if ( 'top' === $which ) {
601 ob_start();
602
603 // Add date selector.
604 $current_date = current_time( 'd M Y' );
605
606 $post_date_from = isset( $_REQUEST['post-date-filter-from'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['post-date-filter-from'] ) ) : $current_date; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
607 echo '<input type="text" id="datepicker-from" name="post-date-filter-from" value="' . esc_attr( $post_date_from ) . '" size="11" />';
608
609 $post_date_to = isset( $_REQUEST['post-date-filter-to'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['post-date-filter-to'] ) ) : $current_date; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
610 echo '<input type="text" id="datepicker-to" name="post-date-filter-to" value="' . esc_attr( $post_date_to ) . '" size="11" />';
611
612 if ( ! $this->network_wide ) {
613 $post_types = get_post_types(
614 array(
615 'public' => true,
616 )
617 );
618 $post_types = $this->all_post_type + $post_types;
619
620 if ( $post_types ) {
621
622 echo '<select name="post-type-filter">';
623
624 foreach ( $post_types as $post_type ) {
625 $pt = get_post_type_object( $post_type );
626 $label = isset( $pt->labels->singular_name ) ? $pt->labels->singular_name : $post_type;
627
628 $selected = '';
629 if ( isset( $_REQUEST['post-type-filter'] ) && $_REQUEST['post-type-filter'] === $post_type ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
630 $selected = ' selected = "selected"';
631 }
632 ?>
633 <option value="<?php echo esc_attr( $post_type ); ?>" <?php echo esc_attr( $selected ); ?>><?php echo esc_attr( $label ); ?></option>
634 <?php
635 }
636
637 echo '</select>';
638
639 }
640 }
641
642 $output = ob_get_clean();
643
644 if ( ! empty( $output ) ) {
645 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
646 submit_button( __( 'Filter' ), '', 'filter_action', false, array( 'id' => 'top-10-query-submit' ) );
647 }
648 }
649 ?>
650 </div>
651 <?php
652 }
653 }
654