wp/wp-admin/includes/class-wp-list-table.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
     9 
     9 
    10 /**
    10 /**
    11  * Base class for displaying a list of items in an ajaxified HTML table.
    11  * Base class for displaying a list of items in an ajaxified HTML table.
    12  *
    12  *
    13  * @since 3.1.0
    13  * @since 3.1.0
    14  * @access private
       
    15  */
    14  */
       
    15 #[AllowDynamicProperties]
    16 class WP_List_Table {
    16 class WP_List_Table {
    17 
    17 
    18 	/**
    18 	/**
    19 	 * The current list of items.
    19 	 * The current list of items.
    20 	 *
    20 	 *
   171 			);
   171 			);
   172 		}
   172 		}
   173 	}
   173 	}
   174 
   174 
   175 	/**
   175 	/**
   176 	 * Make private properties readable for backward compatibility.
   176 	 * Makes private properties readable for backward compatibility.
   177 	 *
   177 	 *
   178 	 * @since 4.0.0
   178 	 * @since 4.0.0
       
   179 	 * @since 6.4.0 Getting a dynamic property is deprecated.
   179 	 *
   180 	 *
   180 	 * @param string $name Property to get.
   181 	 * @param string $name Property to get.
   181 	 * @return mixed Property.
   182 	 * @return mixed Property.
   182 	 */
   183 	 */
   183 	public function __get( $name ) {
   184 	public function __get( $name ) {
   184 		if ( in_array( $name, $this->compat_fields, true ) ) {
   185 		if ( in_array( $name, $this->compat_fields, true ) ) {
   185 			return $this->$name;
   186 			return $this->$name;
   186 		}
   187 		}
   187 	}
   188 
   188 
   189 		wp_trigger_error(
   189 	/**
   190 			__METHOD__,
   190 	 * Make private properties settable for backward compatibility.
   191 			"The property `{$name}` is not declared. Getting a dynamic property is " .
       
   192 			'deprecated since version 6.4.0! Instead, declare the property on the class.',
       
   193 			E_USER_DEPRECATED
       
   194 		);
       
   195 		return null;
       
   196 	}
       
   197 
       
   198 	/**
       
   199 	 * Makes private properties settable for backward compatibility.
   191 	 *
   200 	 *
   192 	 * @since 4.0.0
   201 	 * @since 4.0.0
       
   202 	 * @since 6.4.0 Setting a dynamic property is deprecated.
   193 	 *
   203 	 *
   194 	 * @param string $name  Property to check if set.
   204 	 * @param string $name  Property to check if set.
   195 	 * @param mixed  $value Property value.
   205 	 * @param mixed  $value Property value.
   196 	 * @return mixed Newly-set property.
       
   197 	 */
   206 	 */
   198 	public function __set( $name, $value ) {
   207 	public function __set( $name, $value ) {
   199 		if ( in_array( $name, $this->compat_fields, true ) ) {
   208 		if ( in_array( $name, $this->compat_fields, true ) ) {
   200 			return $this->$name = $value;
   209 			$this->$name = $value;
   201 		}
   210 			return;
   202 	}
   211 		}
   203 
   212 
   204 	/**
   213 		wp_trigger_error(
   205 	 * Make private properties checkable for backward compatibility.
   214 			__METHOD__,
       
   215 			"The property `{$name}` is not declared. Setting a dynamic property is " .
       
   216 			'deprecated since version 6.4.0! Instead, declare the property on the class.',
       
   217 			E_USER_DEPRECATED
       
   218 		);
       
   219 	}
       
   220 
       
   221 	/**
       
   222 	 * Makes private properties checkable for backward compatibility.
   206 	 *
   223 	 *
   207 	 * @since 4.0.0
   224 	 * @since 4.0.0
       
   225 	 * @since 6.4.0 Checking a dynamic property is deprecated.
   208 	 *
   226 	 *
   209 	 * @param string $name Property to check if set.
   227 	 * @param string $name Property to check if set.
   210 	 * @return bool Whether the property is a back-compat property and it is set.
   228 	 * @return bool Whether the property is a back-compat property and it is set.
   211 	 */
   229 	 */
   212 	public function __isset( $name ) {
   230 	public function __isset( $name ) {
   213 		if ( in_array( $name, $this->compat_fields, true ) ) {
   231 		if ( in_array( $name, $this->compat_fields, true ) ) {
   214 			return isset( $this->$name );
   232 			return isset( $this->$name );
   215 		}
   233 		}
   216 
   234 
       
   235 		wp_trigger_error(
       
   236 			__METHOD__,
       
   237 			"The property `{$name}` is not declared. Checking `isset()` on a dynamic property " .
       
   238 			'is deprecated since version 6.4.0! Instead, declare the property on the class.',
       
   239 			E_USER_DEPRECATED
       
   240 		);
   217 		return false;
   241 		return false;
   218 	}
   242 	}
   219 
   243 
   220 	/**
   244 	/**
   221 	 * Make private properties un-settable for backward compatibility.
   245 	 * Makes private properties un-settable for backward compatibility.
   222 	 *
   246 	 *
   223 	 * @since 4.0.0
   247 	 * @since 4.0.0
       
   248 	 * @since 6.4.0 Unsetting a dynamic property is deprecated.
   224 	 *
   249 	 *
   225 	 * @param string $name Property to unset.
   250 	 * @param string $name Property to unset.
   226 	 */
   251 	 */
   227 	public function __unset( $name ) {
   252 	public function __unset( $name ) {
   228 		if ( in_array( $name, $this->compat_fields, true ) ) {
   253 		if ( in_array( $name, $this->compat_fields, true ) ) {
   229 			unset( $this->$name );
   254 			unset( $this->$name );
   230 		}
   255 			return;
   231 	}
   256 		}
   232 
   257 
   233 	/**
   258 		wp_trigger_error(
   234 	 * Make private/protected methods readable for backward compatibility.
   259 			__METHOD__,
       
   260 			"A property `{$name}` is not declared. Unsetting a dynamic property is " .
       
   261 			'deprecated since version 6.4.0! Instead, declare the property on the class.',
       
   262 			E_USER_DEPRECATED
       
   263 		);
       
   264 	}
       
   265 
       
   266 	/**
       
   267 	 * Makes private/protected methods readable for backward compatibility.
   235 	 *
   268 	 *
   236 	 * @since 4.0.0
   269 	 * @since 4.0.0
   237 	 *
   270 	 *
   238 	 * @param string $name      Method to call.
   271 	 * @param string $name      Method to call.
   239 	 * @param array  $arguments Arguments to pass when calling.
   272 	 * @param array  $arguments Arguments to pass when calling.
   267 	public function prepare_items() {
   300 	public function prepare_items() {
   268 		die( 'function WP_List_Table::prepare_items() must be overridden in a subclass.' );
   301 		die( 'function WP_List_Table::prepare_items() must be overridden in a subclass.' );
   269 	}
   302 	}
   270 
   303 
   271 	/**
   304 	/**
   272 	 * An internal method that sets all the necessary pagination arguments
   305 	 * Sets all the necessary pagination arguments.
   273 	 *
   306 	 *
   274 	 * @since 3.1.0
   307 	 * @since 3.1.0
   275 	 *
   308 	 *
   276 	 * @param array|string $args Array or string of arguments with information about the pagination.
   309 	 * @param array|string $args Array or string of arguments with information about the pagination.
   277 	 */
   310 	 */
   284 				'per_page'    => 0,
   317 				'per_page'    => 0,
   285 			)
   318 			)
   286 		);
   319 		);
   287 
   320 
   288 		if ( ! $args['total_pages'] && $args['per_page'] > 0 ) {
   321 		if ( ! $args['total_pages'] && $args['per_page'] > 0 ) {
   289 			$args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
   322 			$args['total_pages'] = (int) ceil( $args['total_items'] / $args['per_page'] );
   290 		}
   323 		}
   291 
   324 
   292 		// Redirect if page number is invalid and headers are not already sent.
   325 		// Redirect if page number is invalid and headers are not already sent.
   293 		if ( ! headers_sent() && ! wp_doing_ajax() && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
   326 		if ( ! headers_sent() && ! wp_doing_ajax() && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
   294 			wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
   327 			wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
   318 
   351 
   319 		return 0;
   352 		return 0;
   320 	}
   353 	}
   321 
   354 
   322 	/**
   355 	/**
   323 	 * Whether the table has items to display or not
   356 	 * Determines whether the table has items to display or not
   324 	 *
   357 	 *
   325 	 * @since 3.1.0
   358 	 * @since 3.1.0
   326 	 *
   359 	 *
   327 	 * @return bool
   360 	 * @return bool
   328 	 */
   361 	 */
   353 		}
   386 		}
   354 
   387 
   355 		$input_id = $input_id . '-search-input';
   388 		$input_id = $input_id . '-search-input';
   356 
   389 
   357 		if ( ! empty( $_REQUEST['orderby'] ) ) {
   390 		if ( ! empty( $_REQUEST['orderby'] ) ) {
   358 			echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
   391 			if ( is_array( $_REQUEST['orderby'] ) ) {
       
   392 				foreach ( $_REQUEST['orderby'] as $key => $value ) {
       
   393 					echo '<input type="hidden" name="orderby[' . esc_attr( $key ) . ']" value="' . esc_attr( $value ) . '" />';
       
   394 				}
       
   395 			} else {
       
   396 				echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
       
   397 			}
   359 		}
   398 		}
   360 		if ( ! empty( $_REQUEST['order'] ) ) {
   399 		if ( ! empty( $_REQUEST['order'] ) ) {
   361 			echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
   400 			echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
   362 		}
   401 		}
   363 		if ( ! empty( $_REQUEST['post_mime_type'] ) ) {
   402 		if ( ! empty( $_REQUEST['post_mime_type'] ) ) {
   371 	<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label>
   410 	<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label>
   372 	<input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" value="<?php _admin_search_query(); ?>" />
   411 	<input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" value="<?php _admin_search_query(); ?>" />
   373 		<?php submit_button( $text, '', '', false, array( 'id' => 'search-submit' ) ); ?>
   412 		<?php submit_button( $text, '', '', false, array( 'id' => 'search-submit' ) ); ?>
   374 </p>
   413 </p>
   375 		<?php
   414 		<?php
       
   415 	}
       
   416 
       
   417 	/**
       
   418 	 * Generates views links.
       
   419 	 *
       
   420 	 * @since 6.1.0
       
   421 	 *
       
   422 	 * @param array $link_data {
       
   423 	 *     An array of link data.
       
   424 	 *
       
   425 	 *     @type string $url     The link URL.
       
   426 	 *     @type string $label   The link label.
       
   427 	 *     @type bool   $current Optional. Whether this is the currently selected view.
       
   428 	 * }
       
   429 	 * @return string[] An array of link markup. Keys match the `$link_data` input array.
       
   430 	 */
       
   431 	protected function get_views_links( $link_data = array() ) {
       
   432 		if ( ! is_array( $link_data ) ) {
       
   433 			_doing_it_wrong(
       
   434 				__METHOD__,
       
   435 				sprintf(
       
   436 					/* translators: %s: The $link_data argument. */
       
   437 					__( 'The %s argument must be an array.' ),
       
   438 					'<code>$link_data</code>'
       
   439 				),
       
   440 				'6.1.0'
       
   441 			);
       
   442 
       
   443 			return array( '' );
       
   444 		}
       
   445 
       
   446 		$views_links = array();
       
   447 
       
   448 		foreach ( $link_data as $view => $link ) {
       
   449 			if ( empty( $link['url'] ) || ! is_string( $link['url'] ) || '' === trim( $link['url'] ) ) {
       
   450 				_doing_it_wrong(
       
   451 					__METHOD__,
       
   452 					sprintf(
       
   453 						/* translators: %1$s: The argument name. %2$s: The view name. */
       
   454 						__( 'The %1$s argument must be a non-empty string for %2$s.' ),
       
   455 						'<code>url</code>',
       
   456 						'<code>' . esc_html( $view ) . '</code>'
       
   457 					),
       
   458 					'6.1.0'
       
   459 				);
       
   460 
       
   461 				continue;
       
   462 			}
       
   463 
       
   464 			if ( empty( $link['label'] ) || ! is_string( $link['label'] ) || '' === trim( $link['label'] ) ) {
       
   465 				_doing_it_wrong(
       
   466 					__METHOD__,
       
   467 					sprintf(
       
   468 						/* translators: %1$s: The argument name. %2$s: The view name. */
       
   469 						__( 'The %1$s argument must be a non-empty string for %2$s.' ),
       
   470 						'<code>label</code>',
       
   471 						'<code>' . esc_html( $view ) . '</code>'
       
   472 					),
       
   473 					'6.1.0'
       
   474 				);
       
   475 
       
   476 				continue;
       
   477 			}
       
   478 
       
   479 			$views_links[ $view ] = sprintf(
       
   480 				'<a href="%s"%s>%s</a>',
       
   481 				esc_url( $link['url'] ),
       
   482 				isset( $link['current'] ) && true === $link['current'] ? ' class="current" aria-current="page"' : '',
       
   483 				$link['label']
       
   484 			);
       
   485 		}
       
   486 
       
   487 		return $views_links;
   376 	}
   488 	}
   377 
   489 
   378 	/**
   490 	/**
   379 	 * Gets the list of views available on this table.
   491 	 * Gets the list of views available on this table.
   380 	 *
   492 	 *
   456 	/**
   568 	/**
   457 	 * Displays the bulk actions dropdown.
   569 	 * Displays the bulk actions dropdown.
   458 	 *
   570 	 *
   459 	 * @since 3.1.0
   571 	 * @since 3.1.0
   460 	 *
   572 	 *
   461 	 * @param string $which The location of the bulk actions: 'top' or 'bottom'.
   573 	 * @param string $which The location of the bulk actions: Either 'top' or 'bottom'.
   462 	 *                      This is designated as optional for backward compatibility.
   574 	 *                      This is designated as optional for backward compatibility.
   463 	 */
   575 	 */
   464 	protected function bulk_actions( $which = '' ) {
   576 	protected function bulk_actions( $which = '' ) {
   465 		if ( is_null( $this->_actions ) ) {
   577 		if ( is_null( $this->_actions ) ) {
   466 			$this->_actions = $this->get_bulk_actions();
   578 			$this->_actions = $this->get_bulk_actions();
   485 
   597 
   486 		if ( empty( $this->_actions ) ) {
   598 		if ( empty( $this->_actions ) ) {
   487 			return;
   599 			return;
   488 		}
   600 		}
   489 
   601 
   490 		echo '<label for="bulk-action-selector-' . esc_attr( $which ) . '" class="screen-reader-text">' . __( 'Select bulk action' ) . '</label>';
   602 		echo '<label for="bulk-action-selector-' . esc_attr( $which ) . '" class="screen-reader-text">' .
       
   603 			/* translators: Hidden accessibility text. */
       
   604 			__( 'Select bulk action' ) .
       
   605 		'</label>';
   491 		echo '<select name="action' . $two . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n";
   606 		echo '<select name="action' . $two . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n";
   492 		echo '<option value="-1">' . __( 'Bulk actions' ) . "</option>\n";
   607 		echo '<option value="-1">' . __( 'Bulk actions' ) . "</option>\n";
   493 
   608 
   494 		foreach ( $this->_actions as $key => $value ) {
   609 		foreach ( $this->_actions as $key => $value ) {
   495 			if ( is_array( $value ) ) {
   610 			if ( is_array( $value ) ) {
   524 	public function current_action() {
   639 	public function current_action() {
   525 		if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) {
   640 		if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) {
   526 			return false;
   641 			return false;
   527 		}
   642 		}
   528 
   643 
   529 		if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] ) {
   644 		if ( isset( $_REQUEST['action'] ) && '-1' !== $_REQUEST['action'] ) {
   530 			return $_REQUEST['action'];
   645 			return $_REQUEST['action'];
   531 		}
   646 		}
   532 
   647 
   533 		return false;
   648 		return false;
   534 	}
   649 	}
   553 
   668 
   554 		if ( 'excerpt' === $mode ) {
   669 		if ( 'excerpt' === $mode ) {
   555 			$always_visible = true;
   670 			$always_visible = true;
   556 		}
   671 		}
   557 
   672 
   558 		$out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
   673 		$output = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
   559 
   674 
   560 		$i = 0;
   675 		$i = 0;
   561 
   676 
   562 		foreach ( $actions as $action => $link ) {
   677 		foreach ( $actions as $action => $link ) {
   563 			++$i;
   678 			++$i;
   564 
   679 
   565 			$sep = ( $i < $action_count ) ? ' | ' : '';
   680 			$separator = ( $i < $action_count ) ? ' | ' : '';
   566 
   681 
   567 			$out .= "<span class='$action'>$link$sep</span>";
   682 			$output .= "<span class='$action'>{$link}{$separator}</span>";
   568 		}
   683 		}
   569 
   684 
   570 		$out .= '</div>';
   685 		$output .= '</div>';
   571 
   686 
   572 		$out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>';
   687 		$output .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' .
   573 
   688 			/* translators: Hidden accessibility text. */
   574 		return $out;
   689 			__( 'Show more details' ) .
       
   690 		'</span></button>';
       
   691 
       
   692 		return $output;
   575 	}
   693 	}
   576 
   694 
   577 	/**
   695 	/**
   578 	 * Displays a dropdown for filtering items in the list table by month.
   696 	 * Displays a dropdown for filtering items in the list table by month.
   579 	 *
   697 	 *
   598 		if ( apply_filters( 'disable_months_dropdown', false, $post_type ) ) {
   716 		if ( apply_filters( 'disable_months_dropdown', false, $post_type ) ) {
   599 			return;
   717 			return;
   600 		}
   718 		}
   601 
   719 
   602 		/**
   720 		/**
   603 		 * Filters to short-circuit performing the months dropdown query.
   721 		 * Filters whether to short-circuit performing the months dropdown query.
   604 		 *
   722 		 *
   605 		 * @since 5.7.0
   723 		 * @since 5.7.0
   606 		 *
   724 		 *
   607 		 * @param object[]|false $months   'Months' drop-down results. Default false.
   725 		 * @param object[]|false $months   'Months' drop-down results. Default false.
   608 		 * @param string         $post_type The post type.
   726 		 * @param string         $post_type The post type.
   617 				$extra_checks = $wpdb->prepare( ' AND post_status = %s', $_GET['post_status'] );
   735 				$extra_checks = $wpdb->prepare( ' AND post_status = %s', $_GET['post_status'] );
   618 			}
   736 			}
   619 
   737 
   620 			$months = $wpdb->get_results(
   738 			$months = $wpdb->get_results(
   621 				$wpdb->prepare(
   739 				$wpdb->prepare(
   622 					"
   740 					"SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
   623 				SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
   741 					FROM $wpdb->posts
   624 				FROM $wpdb->posts
   742 					WHERE post_type = %s
   625 				WHERE post_type = %s
   743 					$extra_checks
   626 				$extra_checks
   744 					ORDER BY post_date DESC",
   627 				ORDER BY post_date DESC
       
   628 			",
       
   629 					$post_type
   745 					$post_type
   630 				)
   746 				)
   631 			);
   747 			);
   632 		}
   748 		}
   633 
   749 
   641 		 */
   757 		 */
   642 		$months = apply_filters( 'months_dropdown_results', $months, $post_type );
   758 		$months = apply_filters( 'months_dropdown_results', $months, $post_type );
   643 
   759 
   644 		$month_count = count( $months );
   760 		$month_count = count( $months );
   645 
   761 
   646 		if ( ! $month_count || ( 1 == $month_count && 0 == $months[0]->month ) ) {
   762 		if ( ! $month_count || ( 1 === $month_count && 0 === (int) $months[0]->month ) ) {
   647 			return;
   763 			return;
   648 		}
   764 		}
   649 
   765 
   650 		$m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
   766 		$m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
   651 		?>
   767 		?>
   652 		<label for="filter-by-date" class="screen-reader-text"><?php echo get_post_type_object( $post_type )->labels->filter_by_date; ?></label>
   768 		<label for="filter-by-date" class="screen-reader-text"><?php echo get_post_type_object( $post_type )->labels->filter_by_date; ?></label>
   653 		<select name="m" id="filter-by-date">
   769 		<select name="m" id="filter-by-date">
   654 			<option<?php selected( $m, 0 ); ?> value="0"><?php _e( 'All dates' ); ?></option>
   770 			<option<?php selected( $m, 0 ); ?> value="0"><?php _e( 'All dates' ); ?></option>
   655 		<?php
   771 		<?php
   656 		foreach ( $months as $arc_row ) {
   772 		foreach ( $months as $arc_row ) {
   657 			if ( 0 == $arc_row->year ) {
   773 			if ( 0 === (int) $arc_row->year ) {
   658 				continue;
   774 				continue;
   659 			}
   775 			}
   660 
   776 
   661 			$month = zeroise( $arc_row->month, 2 );
   777 			$month = zeroise( $arc_row->month, 2 );
   662 			$year  = $arc_row->year;
   778 			$year  = $arc_row->year;
   694 				$classes[]    = 'current';
   810 				$classes[]    = 'current';
   695 				$aria_current = ' aria-current="page"';
   811 				$aria_current = ' aria-current="page"';
   696 			}
   812 			}
   697 
   813 
   698 			printf(
   814 			printf(
   699 				"<a href='%s' class='%s' id='view-switch-$mode'$aria_current><span class='screen-reader-text'>%s</span></a>\n",
   815 				"<a href='%s' class='%s' id='view-switch-$mode'$aria_current>" .
       
   816 					"<span class='screen-reader-text'>%s</span>" .
       
   817 				"</a>\n",
   700 				esc_url( remove_query_arg( 'attachment-filter', add_query_arg( 'mode', $mode ) ) ),
   818 				esc_url( remove_query_arg( 'attachment-filter', add_query_arg( 'mode', $mode ) ) ),
   701 				implode( ' ', $classes ),
   819 				implode( ' ', $classes ),
   702 				$title
   820 				$title
   703 			);
   821 			);
   704 		}
   822 		}
   714 	 *
   832 	 *
   715 	 * @param int $post_id          The post ID.
   833 	 * @param int $post_id          The post ID.
   716 	 * @param int $pending_comments Number of pending comments.
   834 	 * @param int $pending_comments Number of pending comments.
   717 	 */
   835 	 */
   718 	protected function comments_bubble( $post_id, $pending_comments ) {
   836 	protected function comments_bubble( $post_id, $pending_comments ) {
       
   837 		$post_object   = get_post( $post_id );
       
   838 		$edit_post_cap = $post_object ? 'edit_post' : 'edit_posts';
       
   839 
       
   840 		if ( ! current_user_can( $edit_post_cap, $post_id )
       
   841 			&& ( post_password_required( $post_id )
       
   842 				|| ! current_user_can( 'read_post', $post_id ) )
       
   843 		) {
       
   844 			// The user has no access to the post and thus cannot see the comments.
       
   845 			return false;
       
   846 		}
       
   847 
   719 		$approved_comments = get_comments_number();
   848 		$approved_comments = get_comments_number();
   720 
   849 
   721 		$approved_comments_number = number_format_i18n( $approved_comments );
   850 		$approved_comments_number = number_format_i18n( $approved_comments );
   722 		$pending_comments_number  = number_format_i18n( $pending_comments );
   851 		$pending_comments_number  = number_format_i18n( $pending_comments );
   723 
   852 
   740 		);
   869 		);
   741 
   870 
   742 		if ( ! $approved_comments && ! $pending_comments ) {
   871 		if ( ! $approved_comments && ! $pending_comments ) {
   743 			// No comments at all.
   872 			// No comments at all.
   744 			printf(
   873 			printf(
   745 				'<span aria-hidden="true">&#8212;</span><span class="screen-reader-text">%s</span>',
   874 				'<span aria-hidden="true">&#8212;</span>' .
       
   875 				'<span class="screen-reader-text">%s</span>',
   746 				__( 'No comments' )
   876 				__( 'No comments' )
   747 			);
   877 			);
   748 		} elseif ( $approved_comments && 'trash' === get_post_status( $post_id ) ) {
   878 		} elseif ( $approved_comments && 'trash' === get_post_status( $post_id ) ) {
   749 			// Don't link the comment bubble for a trashed post.
   879 			// Don't link the comment bubble for a trashed post.
   750 			printf(
   880 			printf(
   751 				'<span class="post-com-count post-com-count-approved"><span class="comment-count-approved" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></span>',
   881 				'<span class="post-com-count post-com-count-approved">' .
       
   882 					'<span class="comment-count-approved" aria-hidden="true">%s</span>' .
       
   883 					'<span class="screen-reader-text">%s</span>' .
       
   884 				'</span>',
   752 				$approved_comments_number,
   885 				$approved_comments_number,
   753 				$pending_comments ? $approved_phrase : $approved_only_phrase
   886 				$pending_comments ? $approved_phrase : $approved_only_phrase
   754 			);
   887 			);
   755 		} elseif ( $approved_comments ) {
   888 		} elseif ( $approved_comments ) {
   756 			// Link the comment bubble to approved comments.
   889 			// Link the comment bubble to approved comments.
   757 			printf(
   890 			printf(
   758 				'<a href="%s" class="post-com-count post-com-count-approved"><span class="comment-count-approved" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></a>',
   891 				'<a href="%s" class="post-com-count post-com-count-approved">' .
       
   892 					'<span class="comment-count-approved" aria-hidden="true">%s</span>' .
       
   893 					'<span class="screen-reader-text">%s</span>' .
       
   894 				'</a>',
   759 				esc_url(
   895 				esc_url(
   760 					add_query_arg(
   896 					add_query_arg(
   761 						array(
   897 						array(
   762 							'p'              => $post_id,
   898 							'p'              => $post_id,
   763 							'comment_status' => 'approved',
   899 							'comment_status' => 'approved',
   769 				$pending_comments ? $approved_phrase : $approved_only_phrase
   905 				$pending_comments ? $approved_phrase : $approved_only_phrase
   770 			);
   906 			);
   771 		} else {
   907 		} else {
   772 			// Don't link the comment bubble when there are no approved comments.
   908 			// Don't link the comment bubble when there are no approved comments.
   773 			printf(
   909 			printf(
   774 				'<span class="post-com-count post-com-count-no-comments"><span class="comment-count comment-count-no-comments" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></span>',
   910 				'<span class="post-com-count post-com-count-no-comments">' .
       
   911 					'<span class="comment-count comment-count-no-comments" aria-hidden="true">%s</span>' .
       
   912 					'<span class="screen-reader-text">%s</span>' .
       
   913 				'</span>',
   775 				$approved_comments_number,
   914 				$approved_comments_number,
   776 				$pending_comments ? __( 'No approved comments' ) : __( 'No comments' )
   915 				$pending_comments ?
       
   916 				/* translators: Hidden accessibility text. */
       
   917 				__( 'No approved comments' ) :
       
   918 				/* translators: Hidden accessibility text. */
       
   919 				__( 'No comments' )
   777 			);
   920 			);
   778 		}
   921 		}
   779 
   922 
   780 		if ( $pending_comments ) {
   923 		if ( $pending_comments ) {
   781 			printf(
   924 			printf(
   782 				'<a href="%s" class="post-com-count post-com-count-pending"><span class="comment-count-pending" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></a>',
   925 				'<a href="%s" class="post-com-count post-com-count-pending">' .
       
   926 					'<span class="comment-count-pending" aria-hidden="true">%s</span>' .
       
   927 					'<span class="screen-reader-text">%s</span>' .
       
   928 				'</a>',
   783 				esc_url(
   929 				esc_url(
   784 					add_query_arg(
   930 					add_query_arg(
   785 						array(
   931 						array(
   786 							'p'              => $post_id,
   932 							'p'              => $post_id,
   787 							'comment_status' => 'moderated',
   933 							'comment_status' => 'moderated',
   792 				$pending_comments_number,
   938 				$pending_comments_number,
   793 				$pending_phrase
   939 				$pending_phrase
   794 			);
   940 			);
   795 		} else {
   941 		} else {
   796 			printf(
   942 			printf(
   797 				'<span class="post-com-count post-com-count-pending post-com-count-no-pending"><span class="comment-count comment-count-no-pending" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></span>',
   943 				'<span class="post-com-count post-com-count-pending post-com-count-no-pending">' .
       
   944 					'<span class="comment-count comment-count-no-pending" aria-hidden="true">%s</span>' .
       
   945 					'<span class="screen-reader-text">%s</span>' .
       
   946 				'</span>',
   798 				$pending_comments_number,
   947 				$pending_comments_number,
   799 				$approved_comments ? __( 'No pending comments' ) : __( 'No comments' )
   948 				$approved_comments ?
       
   949 				/* translators: Hidden accessibility text. */
       
   950 				__( 'No pending comments' ) :
       
   951 				/* translators: Hidden accessibility text. */
       
   952 				__( 'No comments' )
   800 			);
   953 			);
   801 		}
   954 		}
   802 	}
   955 	}
   803 
   956 
   804 	/**
   957 	/**
   840 		 * on the type of list table in use. Possible filter names include:
   993 		 * on the type of list table in use. Possible filter names include:
   841 		 *
   994 		 *
   842 		 *  - `edit_comments_per_page`
   995 		 *  - `edit_comments_per_page`
   843 		 *  - `sites_network_per_page`
   996 		 *  - `sites_network_per_page`
   844 		 *  - `site_themes_network_per_page`
   997 		 *  - `site_themes_network_per_page`
   845 		 *  - `themes_network_per_page'`
   998 		 *  - `themes_network_per_page`
   846 		 *  - `users_network_per_page`
   999 		 *  - `users_network_per_page`
   847 		 *  - `edit_post_per_page`
  1000 		 *  - `edit_post_per_page`
   848 		 *  - `edit_page_per_page'`
  1001 		 *  - `edit_page_per_page`
   849 		 *  - `edit_{$post_type}_per_page`
  1002 		 *  - `edit_{$post_type}_per_page`
   850 		 *  - `edit_post_tag_per_page`
  1003 		 *  - `edit_post_tag_per_page`
   851 		 *  - `edit_category_per_page`
  1004 		 *  - `edit_category_per_page`
   852 		 *  - `edit_{$taxonomy}_per_page`
  1005 		 *  - `edit_{$taxonomy}_per_page`
   853 		 *  - `site_users_network_per_page`
  1006 		 *  - `site_users_network_per_page`
   863 	/**
  1016 	/**
   864 	 * Displays the pagination.
  1017 	 * Displays the pagination.
   865 	 *
  1018 	 *
   866 	 * @since 3.1.0
  1019 	 * @since 3.1.0
   867 	 *
  1020 	 *
   868 	 * @param string $which
  1021 	 * @param string $which The location of the pagination: Either 'top' or 'bottom'.
   869 	 */
  1022 	 */
   870 	protected function pagination( $which ) {
  1023 	protected function pagination( $which ) {
   871 		if ( empty( $this->_pagination_args ) ) {
  1024 		if ( empty( $this->_pagination_args ) ) {
   872 			return;
  1025 			return;
   873 		}
  1026 		}
   904 		$disable_first = false;
  1057 		$disable_first = false;
   905 		$disable_last  = false;
  1058 		$disable_last  = false;
   906 		$disable_prev  = false;
  1059 		$disable_prev  = false;
   907 		$disable_next  = false;
  1060 		$disable_next  = false;
   908 
  1061 
   909 		if ( 1 == $current ) {
  1062 		if ( 1 === $current ) {
   910 			$disable_first = true;
  1063 			$disable_first = true;
   911 			$disable_prev  = true;
  1064 			$disable_prev  = true;
   912 		}
  1065 		}
   913 		if ( $total_pages == $current ) {
  1066 		if ( $total_pages === $current ) {
   914 			$disable_last = true;
  1067 			$disable_last = true;
   915 			$disable_next = true;
  1068 			$disable_next = true;
   916 		}
  1069 		}
   917 
  1070 
   918 		if ( $disable_first ) {
  1071 		if ( $disable_first ) {
   919 			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&laquo;</span>';
  1072 			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&laquo;</span>';
   920 		} else {
  1073 		} else {
   921 			$page_links[] = sprintf(
  1074 			$page_links[] = sprintf(
   922 				"<a class='first-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
  1075 				"<a class='first-page button' href='%s'>" .
       
  1076 					"<span class='screen-reader-text'>%s</span>" .
       
  1077 					"<span aria-hidden='true'>%s</span>" .
       
  1078 				'</a>',
   923 				esc_url( remove_query_arg( 'paged', $current_url ) ),
  1079 				esc_url( remove_query_arg( 'paged', $current_url ) ),
       
  1080 				/* translators: Hidden accessibility text. */
   924 				__( 'First page' ),
  1081 				__( 'First page' ),
   925 				'&laquo;'
  1082 				'&laquo;'
   926 			);
  1083 			);
   927 		}
  1084 		}
   928 
  1085 
   929 		if ( $disable_prev ) {
  1086 		if ( $disable_prev ) {
   930 			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&lsaquo;</span>';
  1087 			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&lsaquo;</span>';
   931 		} else {
  1088 		} else {
   932 			$page_links[] = sprintf(
  1089 			$page_links[] = sprintf(
   933 				"<a class='prev-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
  1090 				"<a class='prev-page button' href='%s'>" .
       
  1091 					"<span class='screen-reader-text'>%s</span>" .
       
  1092 					"<span aria-hidden='true'>%s</span>" .
       
  1093 				'</a>',
   934 				esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ),
  1094 				esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ),
       
  1095 				/* translators: Hidden accessibility text. */
   935 				__( 'Previous page' ),
  1096 				__( 'Previous page' ),
   936 				'&lsaquo;'
  1097 				'&lsaquo;'
   937 			);
  1098 			);
   938 		}
  1099 		}
   939 
  1100 
   940 		if ( 'bottom' === $which ) {
  1101 		if ( 'bottom' === $which ) {
   941 			$html_current_page  = $current;
  1102 			$html_current_page  = $current;
   942 			$total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page' ) . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
  1103 			$total_pages_before = sprintf(
       
  1104 				'<span class="screen-reader-text">%s</span>' .
       
  1105 				'<span id="table-paging" class="paging-input">' .
       
  1106 				'<span class="tablenav-paging-text">',
       
  1107 				/* translators: Hidden accessibility text. */
       
  1108 				__( 'Current Page' )
       
  1109 			);
   943 		} else {
  1110 		} else {
   944 			$html_current_page = sprintf(
  1111 			$html_current_page = sprintf(
   945 				"%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' /><span class='tablenav-paging-text'>",
  1112 				'<label for="current-page-selector" class="screen-reader-text">%s</label>' .
   946 				'<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page' ) . '</label>',
  1113 				"<input class='current-page' id='current-page-selector' type='text'
       
  1114 					name='paged' value='%s' size='%d' aria-describedby='table-paging' />" .
       
  1115 				"<span class='tablenav-paging-text'>",
       
  1116 				/* translators: Hidden accessibility text. */
       
  1117 				__( 'Current Page' ),
   947 				$current,
  1118 				$current,
   948 				strlen( $total_pages )
  1119 				strlen( $total_pages )
   949 			);
  1120 			);
   950 		}
  1121 		}
       
  1122 
   951 		$html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
  1123 		$html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
   952 		$page_links[]     = $total_pages_before . sprintf(
  1124 
       
  1125 		$page_links[] = $total_pages_before . sprintf(
   953 			/* translators: 1: Current page, 2: Total pages. */
  1126 			/* translators: 1: Current page, 2: Total pages. */
   954 			_x( '%1$s of %2$s', 'paging' ),
  1127 			_x( '%1$s of %2$s', 'paging' ),
   955 			$html_current_page,
  1128 			$html_current_page,
   956 			$html_total_pages
  1129 			$html_total_pages
   957 		) . $total_pages_after;
  1130 		) . $total_pages_after;
   958 
  1131 
   959 		if ( $disable_next ) {
  1132 		if ( $disable_next ) {
   960 			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&rsaquo;</span>';
  1133 			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&rsaquo;</span>';
   961 		} else {
  1134 		} else {
   962 			$page_links[] = sprintf(
  1135 			$page_links[] = sprintf(
   963 				"<a class='next-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
  1136 				"<a class='next-page button' href='%s'>" .
       
  1137 					"<span class='screen-reader-text'>%s</span>" .
       
  1138 					"<span aria-hidden='true'>%s</span>" .
       
  1139 				'</a>',
   964 				esc_url( add_query_arg( 'paged', min( $total_pages, $current + 1 ), $current_url ) ),
  1140 				esc_url( add_query_arg( 'paged', min( $total_pages, $current + 1 ), $current_url ) ),
       
  1141 				/* translators: Hidden accessibility text. */
   965 				__( 'Next page' ),
  1142 				__( 'Next page' ),
   966 				'&rsaquo;'
  1143 				'&rsaquo;'
   967 			);
  1144 			);
   968 		}
  1145 		}
   969 
  1146 
   970 		if ( $disable_last ) {
  1147 		if ( $disable_last ) {
   971 			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&raquo;</span>';
  1148 			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&raquo;</span>';
   972 		} else {
  1149 		} else {
   973 			$page_links[] = sprintf(
  1150 			$page_links[] = sprintf(
   974 				"<a class='last-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
  1151 				"<a class='last-page button' href='%s'>" .
       
  1152 					"<span class='screen-reader-text'>%s</span>" .
       
  1153 					"<span aria-hidden='true'>%s</span>" .
       
  1154 				'</a>',
   975 				esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
  1155 				esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
       
  1156 				/* translators: Hidden accessibility text. */
   976 				__( 'Last page' ),
  1157 				__( 'Last page' ),
   977 				'&raquo;'
  1158 				'&raquo;'
   978 			);
  1159 			);
   979 		}
  1160 		}
   980 
  1161 
  1012 	/**
  1193 	/**
  1013 	 * Gets a list of sortable columns.
  1194 	 * Gets a list of sortable columns.
  1014 	 *
  1195 	 *
  1015 	 * The format is:
  1196 	 * The format is:
  1016 	 * - `'internal-name' => 'orderby'`
  1197 	 * - `'internal-name' => 'orderby'`
       
  1198 	 * - `'internal-name' => array( 'orderby', bool, 'abbr', 'orderby-text', 'initially-sorted-column-order' )` -
  1017 	 * - `'internal-name' => array( 'orderby', 'asc' )` - The second element sets the initial sorting order.
  1199 	 * - `'internal-name' => array( 'orderby', 'asc' )` - The second element sets the initial sorting order.
  1018 	 * - `'internal-name' => array( 'orderby', true )`  - The second element makes the initial order descending.
  1200 	 * - `'internal-name' => array( 'orderby', true )`  - The second element makes the initial order descending.
  1019 	 *
  1201 	 *
  1020 	 * @since 3.1.0
  1202 	 * In the second format, passing true as second parameter will make the initial
       
  1203 	 * sorting order be descending. Following parameters add a short column name to
       
  1204 	 * be used as 'abbr' attribute, a translatable string for the current sorting,
       
  1205 	 * and the initial order for the initial sorted column, 'asc' or 'desc' (default: false).
       
  1206 	 *
       
  1207 	 * @since 3.1.0
       
  1208 	 * @since 6.3.0 Added 'abbr', 'orderby-text' and 'initially-sorted-column-order'.
  1021 	 *
  1209 	 *
  1022 	 * @return array
  1210 	 * @return array
  1023 	 */
  1211 	 */
  1024 	protected function get_sortable_columns() {
  1212 	protected function get_sortable_columns() {
  1025 		return array();
  1213 		return array();
  1038 
  1226 
  1039 		if ( empty( $columns ) ) {
  1227 		if ( empty( $columns ) ) {
  1040 			return $column;
  1228 			return $column;
  1041 		}
  1229 		}
  1042 
  1230 
  1043 		// We need a primary defined so responsive views show something,
  1231 		/*
  1044 		// so let's fall back to the first non-checkbox column.
  1232 		 * We need a primary defined so responsive views show something,
       
  1233 		 * so let's fall back to the first non-checkbox column.
       
  1234 		 */
  1045 		foreach ( $columns as $col => $column_name ) {
  1235 		foreach ( $columns as $col => $column_name ) {
  1046 			if ( 'cb' === $col ) {
  1236 			if ( 'cb' === $col ) {
  1047 				continue;
  1237 				continue;
  1048 			}
  1238 			}
  1049 
  1239 
  1053 
  1243 
  1054 		return $column;
  1244 		return $column;
  1055 	}
  1245 	}
  1056 
  1246 
  1057 	/**
  1247 	/**
       
  1248 	 * Gets the name of the primary column.
       
  1249 	 *
  1058 	 * Public wrapper for WP_List_Table::get_default_primary_column_name().
  1250 	 * Public wrapper for WP_List_Table::get_default_primary_column_name().
  1059 	 *
  1251 	 *
  1060 	 * @since 4.4.0
  1252 	 * @since 4.4.0
  1061 	 *
  1253 	 *
  1062 	 * @return string Name of the default primary column.
  1254 	 * @return string Name of the default primary column.
  1074 	 */
  1266 	 */
  1075 	protected function get_primary_column_name() {
  1267 	protected function get_primary_column_name() {
  1076 		$columns = get_column_headers( $this->screen );
  1268 		$columns = get_column_headers( $this->screen );
  1077 		$default = $this->get_default_primary_column_name();
  1269 		$default = $this->get_default_primary_column_name();
  1078 
  1270 
  1079 		// If the primary column doesn't exist,
  1271 		/*
  1080 		// fall back to the first non-checkbox column.
  1272 		 * If the primary column doesn't exist,
       
  1273 		 * fall back to the first non-checkbox column.
       
  1274 		 */
  1081 		if ( ! isset( $columns[ $default ] ) ) {
  1275 		if ( ! isset( $columns[ $default ] ) ) {
  1082 			$default = self::get_default_primary_column_name();
  1276 			$default = self::get_default_primary_column_name();
  1083 		}
  1277 		}
  1084 
  1278 
  1085 		/**
  1279 		/**
  1106 	 *
  1300 	 *
  1107 	 * @return array
  1301 	 * @return array
  1108 	 */
  1302 	 */
  1109 	protected function get_column_info() {
  1303 	protected function get_column_info() {
  1110 		// $_column_headers is already set / cached.
  1304 		// $_column_headers is already set / cached.
  1111 		if ( isset( $this->_column_headers ) && is_array( $this->_column_headers ) ) {
  1305 		if (
       
  1306 			isset( $this->_column_headers ) &&
       
  1307 			is_array( $this->_column_headers )
       
  1308 		) {
  1112 			/*
  1309 			/*
  1113 			 * Backward compatibility for `$_column_headers` format prior to WordPress 4.3.
  1310 			 * Backward compatibility for `$_column_headers` format prior to WordPress 4.3.
  1114 			 *
  1311 			 *
  1115 			 * In WordPress 4.3 the primary column name was added as a fourth item in the
  1312 			 * In WordPress 4.3 the primary column name was added as a fourth item in the
  1116 			 * column headers property. This ensures the primary column name is included
  1313 			 * column headers property. This ensures the primary column name is included
  1117 			 * in plugins setting the property directly in the three item format.
  1314 			 * in plugins setting the property directly in the three item format.
  1118 			 */
  1315 			 */
       
  1316 			if ( 4 === count( $this->_column_headers ) ) {
       
  1317 				return $this->_column_headers;
       
  1318 			}
       
  1319 
  1119 			$column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
  1320 			$column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
  1120 			foreach ( $this->_column_headers as $key => $value ) {
  1321 			foreach ( $this->_column_headers as $key => $value ) {
  1121 				$column_headers[ $key ] = $value;
  1322 				$column_headers[ $key ] = $value;
  1122 			}
  1323 			}
  1123 
  1324 
  1124 			return $column_headers;
  1325 			$this->_column_headers = $column_headers;
       
  1326 
       
  1327 			return $this->_column_headers;
  1125 		}
  1328 		}
  1126 
  1329 
  1127 		$columns = get_column_headers( $this->screen );
  1330 		$columns = get_column_headers( $this->screen );
  1128 		$hidden  = get_hidden_columns( $this->screen );
  1331 		$hidden  = get_hidden_columns( $this->screen );
  1129 
  1332 
  1145 			if ( empty( $data ) ) {
  1348 			if ( empty( $data ) ) {
  1146 				continue;
  1349 				continue;
  1147 			}
  1350 			}
  1148 
  1351 
  1149 			$data = (array) $data;
  1352 			$data = (array) $data;
       
  1353 			// Descending initial sorting.
  1150 			if ( ! isset( $data[1] ) ) {
  1354 			if ( ! isset( $data[1] ) ) {
  1151 				$data[1] = false;
  1355 				$data[1] = false;
       
  1356 			}
       
  1357 			// Current sorting translatable string.
       
  1358 			if ( ! isset( $data[2] ) ) {
       
  1359 				$data[2] = '';
       
  1360 			}
       
  1361 			// Initial view sorted column and asc/desc order, default: false.
       
  1362 			if ( ! isset( $data[3] ) ) {
       
  1363 				$data[3] = false;
       
  1364 			}
       
  1365 			// Initial order for the initial sorted column, default: false.
       
  1366 			if ( ! isset( $data[4] ) ) {
       
  1367 				$data[4] = false;
  1152 			}
  1368 			}
  1153 
  1369 
  1154 			$sortable[ $id ] = $data;
  1370 			$sortable[ $id ] = $data;
  1155 		}
  1371 		}
  1156 
  1372 
  1184 		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
  1400 		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
  1185 
  1401 
  1186 		$current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
  1402 		$current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
  1187 		$current_url = remove_query_arg( 'paged', $current_url );
  1403 		$current_url = remove_query_arg( 'paged', $current_url );
  1188 
  1404 
       
  1405 		// When users click on a column header to sort by other columns.
  1189 		if ( isset( $_GET['orderby'] ) ) {
  1406 		if ( isset( $_GET['orderby'] ) ) {
  1190 			$current_orderby = $_GET['orderby'];
  1407 			$current_orderby = $_GET['orderby'];
       
  1408 			// In the initial view there's no orderby parameter.
  1191 		} else {
  1409 		} else {
  1192 			$current_orderby = '';
  1410 			$current_orderby = '';
  1193 		}
  1411 		}
  1194 
  1412 
       
  1413 		// Not in the initial view and descending order.
  1195 		if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) {
  1414 		if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) {
  1196 			$current_order = 'desc';
  1415 			$current_order = 'desc';
  1197 		} else {
  1416 		} else {
       
  1417 			// The initial view is not always 'asc', we'll take care of this below.
  1198 			$current_order = 'asc';
  1418 			$current_order = 'asc';
  1199 		}
  1419 		}
  1200 
  1420 
  1201 		if ( ! empty( $columns['cb'] ) ) {
  1421 		if ( ! empty( $columns['cb'] ) ) {
  1202 			static $cb_counter = 1;
  1422 			static $cb_counter = 1;
  1203 			$columns['cb']     = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>'
  1423 			$columns['cb']     = '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />
  1204 				. '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
  1424 			<label for="cb-select-all-' . $cb_counter . '">' .
  1205 			$cb_counter++;
  1425 				'<span class="screen-reader-text">' .
       
  1426 					/* translators: Hidden accessibility text. */
       
  1427 					__( 'Select All' ) .
       
  1428 				'</span>' .
       
  1429 				'</label>';
       
  1430 			++$cb_counter;
  1206 		}
  1431 		}
  1207 
  1432 
  1208 		foreach ( $columns as $column_key => $column_display_name ) {
  1433 		foreach ( $columns as $column_key => $column_display_name ) {
  1209 			$class = array( 'manage-column', "column-$column_key" );
  1434 			$class          = array( 'manage-column', "column-$column_key" );
       
  1435 			$aria_sort_attr = '';
       
  1436 			$abbr_attr      = '';
       
  1437 			$order_text     = '';
  1210 
  1438 
  1211 			if ( in_array( $column_key, $hidden, true ) ) {
  1439 			if ( in_array( $column_key, $hidden, true ) ) {
  1212 				$class[] = 'hidden';
  1440 				$class[] = 'hidden';
  1213 			}
  1441 			}
  1214 
  1442 
  1221 			if ( $column_key === $primary ) {
  1449 			if ( $column_key === $primary ) {
  1222 				$class[] = 'column-primary';
  1450 				$class[] = 'column-primary';
  1223 			}
  1451 			}
  1224 
  1452 
  1225 			if ( isset( $sortable[ $column_key ] ) ) {
  1453 			if ( isset( $sortable[ $column_key ] ) ) {
  1226 				list( $orderby, $desc_first ) = $sortable[ $column_key ];
  1454 				$orderby       = isset( $sortable[ $column_key ][0] ) ? $sortable[ $column_key ][0] : '';
  1227 
  1455 				$desc_first    = isset( $sortable[ $column_key ][1] ) ? $sortable[ $column_key ][1] : false;
       
  1456 				$abbr          = isset( $sortable[ $column_key ][2] ) ? $sortable[ $column_key ][2] : '';
       
  1457 				$orderby_text  = isset( $sortable[ $column_key ][3] ) ? $sortable[ $column_key ][3] : '';
       
  1458 				$initial_order = isset( $sortable[ $column_key ][4] ) ? $sortable[ $column_key ][4] : '';
       
  1459 
       
  1460 				/*
       
  1461 				 * We're in the initial view and there's no $_GET['orderby'] then check if the
       
  1462 				 * initial sorting information is set in the sortable columns and use that.
       
  1463 				 */
       
  1464 				if ( '' === $current_orderby && $initial_order ) {
       
  1465 					// Use the initially sorted column $orderby as current orderby.
       
  1466 					$current_orderby = $orderby;
       
  1467 					// Use the initially sorted column asc/desc order as initial order.
       
  1468 					$current_order = $initial_order;
       
  1469 				}
       
  1470 
       
  1471 				/*
       
  1472 				 * True in the initial view when an initial orderby is set via get_sortable_columns()
       
  1473 				 * and true in the sorted views when the actual $_GET['orderby'] is equal to $orderby.
       
  1474 				 */
  1228 				if ( $current_orderby === $orderby ) {
  1475 				if ( $current_orderby === $orderby ) {
  1229 					$order = 'asc' === $current_order ? 'desc' : 'asc';
  1476 					// The sorted column. The `aria-sort` attribute must be set only on the sorted column.
       
  1477 					if ( 'asc' === $current_order ) {
       
  1478 						$order          = 'desc';
       
  1479 						$aria_sort_attr = ' aria-sort="ascending"';
       
  1480 					} else {
       
  1481 						$order          = 'asc';
       
  1482 						$aria_sort_attr = ' aria-sort="descending"';
       
  1483 					}
  1230 
  1484 
  1231 					$class[] = 'sorted';
  1485 					$class[] = 'sorted';
  1232 					$class[] = $current_order;
  1486 					$class[] = $current_order;
  1233 				} else {
  1487 				} else {
       
  1488 					// The other sortable columns.
  1234 					$order = strtolower( $desc_first );
  1489 					$order = strtolower( $desc_first );
  1235 
  1490 
  1236 					if ( ! in_array( $order, array( 'desc', 'asc' ), true ) ) {
  1491 					if ( ! in_array( $order, array( 'desc', 'asc' ), true ) ) {
  1237 						$order = $desc_first ? 'desc' : 'asc';
  1492 						$order = $desc_first ? 'desc' : 'asc';
  1238 					}
  1493 					}
  1239 
  1494 
  1240 					$class[] = 'sortable';
  1495 					$class[] = 'sortable';
  1241 					$class[] = 'desc' === $order ? 'asc' : 'desc';
  1496 					$class[] = 'desc' === $order ? 'asc' : 'desc';
       
  1497 
       
  1498 					/* translators: Hidden accessibility text. */
       
  1499 					$asc_text = __( 'Sort ascending.' );
       
  1500 					/* translators: Hidden accessibility text. */
       
  1501 					$desc_text  = __( 'Sort descending.' );
       
  1502 					$order_text = 'asc' === $order ? $asc_text : $desc_text;
  1242 				}
  1503 				}
  1243 
  1504 
       
  1505 				if ( '' !== $order_text ) {
       
  1506 					$order_text = ' <span class="screen-reader-text">' . $order_text . '</span>';
       
  1507 				}
       
  1508 
       
  1509 				// Print an 'abbr' attribute if a value is provided via get_sortable_columns().
       
  1510 				$abbr_attr = $abbr ? ' abbr="' . esc_attr( $abbr ) . '"' : '';
       
  1511 
  1244 				$column_display_name = sprintf(
  1512 				$column_display_name = sprintf(
  1245 					'<a href="%s"><span>%s</span><span class="sorting-indicator"></span></a>',
  1513 					'<a href="%1$s">' .
       
  1514 						'<span>%2$s</span>' .
       
  1515 						'<span class="sorting-indicators">' .
       
  1516 							'<span class="sorting-indicator asc" aria-hidden="true"></span>' .
       
  1517 							'<span class="sorting-indicator desc" aria-hidden="true"></span>' .
       
  1518 						'</span>' .
       
  1519 						'%3$s' .
       
  1520 					'</a>',
  1246 					esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ),
  1521 					esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ),
  1247 					$column_display_name
  1522 					$column_display_name,
       
  1523 					$order_text
  1248 				);
  1524 				);
  1249 			}
  1525 			}
  1250 
  1526 
  1251 			$tag   = ( 'cb' === $column_key ) ? 'td' : 'th';
  1527 			$tag   = ( 'cb' === $column_key ) ? 'td' : 'th';
  1252 			$scope = ( 'th' === $tag ) ? 'scope="col"' : '';
  1528 			$scope = ( 'th' === $tag ) ? 'scope="col"' : '';
  1254 
  1530 
  1255 			if ( ! empty( $class ) ) {
  1531 			if ( ! empty( $class ) ) {
  1256 				$class = "class='" . implode( ' ', $class ) . "'";
  1532 				$class = "class='" . implode( ' ', $class ) . "'";
  1257 			}
  1533 			}
  1258 
  1534 
  1259 			echo "<$tag $scope $id $class>$column_display_name</$tag>";
  1535 			echo "<$tag $scope $id $class $aria_sort_attr $abbr_attr>$column_display_name</$tag>";
       
  1536 		}
       
  1537 	}
       
  1538 
       
  1539 	/**
       
  1540 	 * Print a table description with information about current sorting and order.
       
  1541 	 *
       
  1542 	 * For the table initial view, information about initial orderby and order
       
  1543 	 * should be provided via get_sortable_columns().
       
  1544 	 *
       
  1545 	 * @since 6.3.0
       
  1546 	 * @access public
       
  1547 	 */
       
  1548 	public function print_table_description() {
       
  1549 		list( $columns, $hidden, $sortable ) = $this->get_column_info();
       
  1550 
       
  1551 		if ( empty( $sortable ) ) {
       
  1552 			return;
       
  1553 		}
       
  1554 
       
  1555 		// When users click on a column header to sort by other columns.
       
  1556 		if ( isset( $_GET['orderby'] ) ) {
       
  1557 			$current_orderby = $_GET['orderby'];
       
  1558 			// In the initial view there's no orderby parameter.
       
  1559 		} else {
       
  1560 			$current_orderby = '';
       
  1561 		}
       
  1562 
       
  1563 		// Not in the initial view and descending order.
       
  1564 		if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) {
       
  1565 			$current_order = 'desc';
       
  1566 		} else {
       
  1567 			// The initial view is not always 'asc', we'll take care of this below.
       
  1568 			$current_order = 'asc';
       
  1569 		}
       
  1570 
       
  1571 		foreach ( array_keys( $columns ) as $column_key ) {
       
  1572 
       
  1573 			if ( isset( $sortable[ $column_key ] ) ) {
       
  1574 				$orderby       = isset( $sortable[ $column_key ][0] ) ? $sortable[ $column_key ][0] : '';
       
  1575 				$desc_first    = isset( $sortable[ $column_key ][1] ) ? $sortable[ $column_key ][1] : false;
       
  1576 				$abbr          = isset( $sortable[ $column_key ][2] ) ? $sortable[ $column_key ][2] : '';
       
  1577 				$orderby_text  = isset( $sortable[ $column_key ][3] ) ? $sortable[ $column_key ][3] : '';
       
  1578 				$initial_order = isset( $sortable[ $column_key ][4] ) ? $sortable[ $column_key ][4] : '';
       
  1579 
       
  1580 				if ( ! is_string( $orderby_text ) || '' === $orderby_text ) {
       
  1581 					return;
       
  1582 				}
       
  1583 				/*
       
  1584 				 * We're in the initial view and there's no $_GET['orderby'] then check if the
       
  1585 				 * initial sorting information is set in the sortable columns and use that.
       
  1586 				 */
       
  1587 				if ( '' === $current_orderby && $initial_order ) {
       
  1588 					// Use the initially sorted column $orderby as current orderby.
       
  1589 					$current_orderby = $orderby;
       
  1590 					// Use the initially sorted column asc/desc order as initial order.
       
  1591 					$current_order = $initial_order;
       
  1592 				}
       
  1593 
       
  1594 				/*
       
  1595 				 * True in the initial view when an initial orderby is set via get_sortable_columns()
       
  1596 				 * and true in the sorted views when the actual $_GET['orderby'] is equal to $orderby.
       
  1597 				 */
       
  1598 				if ( $current_orderby === $orderby ) {
       
  1599 					/* translators: Hidden accessibility text. */
       
  1600 					$asc_text = __( 'Ascending.' );
       
  1601 					/* translators: Hidden accessibility text. */
       
  1602 					$desc_text  = __( 'Descending.' );
       
  1603 					$order_text = 'asc' === $current_order ? $asc_text : $desc_text;
       
  1604 					echo '<caption class="screen-reader-text">' . $orderby_text . ' ' . $order_text . '</caption>';
       
  1605 
       
  1606 					return;
       
  1607 				}
       
  1608 			}
  1260 		}
  1609 		}
  1261 	}
  1610 	}
  1262 
  1611 
  1263 	/**
  1612 	/**
  1264 	 * Displays the table.
  1613 	 * Displays the table.
  1271 		$this->display_tablenav( 'top' );
  1620 		$this->display_tablenav( 'top' );
  1272 
  1621 
  1273 		$this->screen->render_screen_reader_content( 'heading_list' );
  1622 		$this->screen->render_screen_reader_content( 'heading_list' );
  1274 		?>
  1623 		?>
  1275 <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
  1624 <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
       
  1625 		<?php $this->print_table_description(); ?>
  1276 	<thead>
  1626 	<thead>
  1277 	<tr>
  1627 	<tr>
  1278 		<?php $this->print_column_headers(); ?>
  1628 		<?php $this->print_column_headers(); ?>
  1279 	</tr>
  1629 	</tr>
  1280 	</thead>
  1630 	</thead>
  1317 
  1667 
  1318 	/**
  1668 	/**
  1319 	 * Generates the table navigation above or below the table
  1669 	 * Generates the table navigation above or below the table
  1320 	 *
  1670 	 *
  1321 	 * @since 3.1.0
  1671 	 * @since 3.1.0
  1322 	 * @param string $which
  1672 	 * @param string $which The location of the navigation: Either 'top' or 'bottom'.
  1323 	 */
  1673 	 */
  1324 	protected function display_tablenav( $which ) {
  1674 	protected function display_tablenav( $which ) {
  1325 		if ( 'top' === $which ) {
  1675 		if ( 'top' === $which ) {
  1326 			wp_nonce_field( 'bulk-' . $this->_args['plural'] );
  1676 			wp_nonce_field( 'bulk-' . $this->_args['plural'] );
  1327 		}
  1677 		}
  1342 	</div>
  1692 	</div>
  1343 		<?php
  1693 		<?php
  1344 	}
  1694 	}
  1345 
  1695 
  1346 	/**
  1696 	/**
  1347 	 * Extra controls to be displayed between bulk actions and pagination.
  1697 	 * Displays extra controls between bulk actions and pagination.
  1348 	 *
  1698 	 *
  1349 	 * @since 3.1.0
  1699 	 * @since 3.1.0
  1350 	 *
  1700 	 *
  1351 	 * @param string $which
  1701 	 * @param string $which
  1352 	 */
  1702 	 */
  1420 
  1770 
  1421 			if ( in_array( $column_name, $hidden, true ) ) {
  1771 			if ( in_array( $column_name, $hidden, true ) ) {
  1422 				$classes .= ' hidden';
  1772 				$classes .= ' hidden';
  1423 			}
  1773 			}
  1424 
  1774 
  1425 			// Comments column uses HTML in the display name with screen reader text.
  1775 			/*
  1426 			// Strip tags to get closer to a user-friendly string.
  1776 			 * Comments column uses HTML in the display name with screen reader text.
       
  1777 			 * Strip tags to get closer to a user-friendly string.
       
  1778 			 */
  1427 			$data = 'data-colname="' . esc_attr( wp_strip_all_tags( $column_display_name ) ) . '"';
  1779 			$data = 'data-colname="' . esc_attr( wp_strip_all_tags( $column_display_name ) ) . '"';
  1428 
  1780 
  1429 			$attributes = "class='$classes' $data";
  1781 			$attributes = "class='$classes' $data";
  1430 
  1782 
  1431 			if ( 'cb' === $column_name ) {
  1783 			if ( 'cb' === $column_name ) {
  1464 	 * @param string       $primary     Primary column name.
  1816 	 * @param string       $primary     Primary column name.
  1465 	 * @return string The row actions HTML, or an empty string
  1817 	 * @return string The row actions HTML, or an empty string
  1466 	 *                if the current column is not the primary column.
  1818 	 *                if the current column is not the primary column.
  1467 	 */
  1819 	 */
  1468 	protected function handle_row_actions( $item, $column_name, $primary ) {
  1820 	protected function handle_row_actions( $item, $column_name, $primary ) {
  1469 		return $column_name === $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>' : '';
  1821 		return $column_name === $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' .
       
  1822 			/* translators: Hidden accessibility text. */
       
  1823 			__( 'Show more details' ) .
       
  1824 		'</span></button>' : '';
  1470 	}
  1825 	}
  1471 
  1826 
  1472 	/**
  1827 	/**
  1473 	 * Handles an incoming ajax request (called from admin-ajax.php)
  1828 	 * Handles an incoming ajax request (called from admin-ajax.php)
  1474 	 *
  1829 	 *