run:R W Run
7.85 KB
2026-03-11 16:18:51
R W Run
3.54 KB
2026-03-11 16:18:51
R W Run
148.33 KB
2026-03-11 16:18:51
R W Run
11.45 KB
2026-03-11 16:18:51
R W Run
3.58 KB
2026-03-11 16:18:51
R W Run
2.53 KB
2026-03-11 16:18:51
R W Run
2.6 KB
2026-03-11 16:18:51
R W Run
6.59 KB
2026-03-11 16:18:51
R W Run
14.83 KB
2026-03-11 16:18:51
R W Run
21.18 KB
2026-03-11 16:18:51
R W Run
48.13 KB
2026-03-11 16:18:51
R W Run
4.07 KB
2026-03-11 16:18:51
R W Run
5.3 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
26.73 KB
2026-03-11 16:18:51
R W Run
2.8 KB
2026-03-11 16:18:51
R W Run
15.2 KB
2026-03-11 16:18:51
R W Run
192.08 KB
2026-03-11 16:18:51
R W Run
11.77 KB
2026-03-11 16:18:51
R W Run
3.2 KB
2026-03-11 16:18:51
R W Run
22.89 KB
2026-03-11 16:18:51
R W Run
12.77 KB
2026-03-11 16:18:51
R W Run
4.08 KB
2026-03-11 16:18:51
R W Run
26.27 KB
2026-03-11 16:18:51
R W Run
4.97 KB
2026-03-11 16:18:51
R W Run
5.57 KB
2026-03-11 16:18:51
R W Run
13.93 KB
2026-03-11 16:18:51
R W Run
4.09 KB
2026-03-11 16:18:51
R W Run
6.79 KB
2026-03-11 16:18:51
R W Run
60.45 KB
2026-03-11 16:18:51
R W Run
32.4 KB
2026-03-11 16:18:51
R W Run
18.24 KB
2026-03-11 16:18:51
R W Run
66.01 KB
2026-03-11 16:18:51
R W Run
23.84 KB
2026-03-11 16:18:51
R W Run
17.72 KB
2026-03-11 16:18:51
R W Run
22.71 KB
2026-03-11 16:18:51
R W Run
18.05 KB
2026-03-11 16:18:51
R W Run
22.76 KB
2026-03-11 16:18:51
R W Run
7.34 KB
2026-03-11 16:18:51
R W Run
4.51 KB
2026-03-11 16:18:51
R W Run
9.02 KB
2026-03-11 16:18:51
R W Run
1.46 KB
2026-03-11 16:18:51
R W Run
51.76 KB
2026-03-11 16:18:51
R W Run
25.29 KB
2026-03-11 16:18:51
R W Run
21.61 KB
2026-03-11 16:18:51
R W Run
27.77 KB
2026-03-11 16:18:51
R W Run
15.35 KB
2026-03-11 16:18:51
R W Run
24.54 KB
2026-03-11 16:18:51
R W Run
56.44 KB
2026-03-11 16:18:51
R W Run
1.42 KB
2026-03-11 16:18:51
R W Run
63.66 KB
2026-03-11 16:18:51
R W Run
31.9 KB
2026-03-11 16:18:51
R W Run
14.44 KB
2026-03-11 16:18:51
R W Run
36.47 KB
2026-03-11 16:18:51
R W Run
14 KB
2026-03-11 16:18:51
R W Run
121.89 KB
2026-03-11 16:18:51
R W Run
6.26 KB
2026-03-11 16:18:51
R W Run
20.73 KB
2026-03-11 16:18:51
R W Run
15.23 KB
2026-03-11 16:18:51
R W Run
10.14 KB
2026-03-11 16:18:51
R W Run
6.94 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
46.85 KB
2026-03-11 16:18:51
R W Run
18.61 KB
2026-03-11 16:18:51
R W Run
6.08 KB
2026-03-11 16:18:51
R W Run
20.06 KB
2026-03-11 16:18:51
R W Run
5.73 KB
2026-03-11 16:18:51
R W Run
68.18 KB
2026-03-11 16:18:51
R W Run
40.8 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
25.26 KB
2026-03-11 16:18:51
R W Run
95.94 KB
2026-03-11 16:18:51
R W Run
43.12 KB
2026-03-11 16:18:51
R W Run
41.73 KB
2026-03-11 16:18:51
R W Run
6.46 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
116.31 KB
2026-03-11 16:18:51
R W Run
9.39 KB
2026-03-11 16:18:51
R W Run
64.34 KB
2026-03-11 16:18:51
R W Run
44.73 KB
2026-03-11 16:18:51
R W Run
1.27 KB
2026-03-11 16:18:51
R W Run
3.68 KB
2026-03-11 16:18:51
R W Run
33.53 KB
2026-03-11 16:18:51
R W Run
48.84 KB
2026-03-11 16:18:51
R W Run
26.35 KB
2026-03-11 16:18:51
R W Run
1.12 KB
2026-03-11 16:18:51
R W Run
4.19 KB
2026-03-11 16:18:51
R W Run
38.19 KB
2026-03-11 16:18:51
R W Run
91.33 KB
2026-03-11 16:18:51
R W Run
80.39 KB
2026-03-11 16:18:51
R W Run
32.67 KB
2026-03-11 16:18:51
R W Run
16.18 KB
2026-03-11 16:18:51
R W Run
44.46 KB
2026-03-11 16:18:51
R W Run
6.23 KB
2026-03-11 16:18:51
R W Run
8.23 KB
2026-03-11 16:18:51
R W Run
96.96 KB
2026-03-11 16:18:51
R W Run
6.83 KB
2026-03-11 16:18:51
R W Run
46.62 KB
2026-03-11 16:18:51
R W Run
10.82 KB
2026-03-11 16:18:51
R W Run
68.86 KB
2026-03-11 16:18:51
R W Run
33.63 KB
2026-03-11 16:18:51
R W Run
113.3 KB
2026-03-11 16:18:51
R W Run
22.98 KB
2026-03-11 16:18:51
R W Run
10.66 KB
2026-03-11 16:18:51
R W Run
error_log
📄class-wp-media-list-table.php
1<?php
2/**
3 * List Table API: WP_Media_List_Table class
4 *
5 * @package WordPress
6 * @subpackage Administration
7 * @since 3.1.0
8 */
9
10/**
11 * Core class used to implement displaying media items in a list table.
12 *
13 * @since 3.1.0
14 *
15 * @see WP_List_Table
16 */
17class WP_Media_List_Table extends WP_List_Table {
18 /**
19 * Holds the number of pending comments for each post.
20 *
21 * @since 4.4.0
22 * @var array
23 */
24 protected $comment_pending_count = array();
25
26 private $detached;
27
28 private $is_trash;
29
30 /**
31 * Constructor.
32 *
33 * @since 3.1.0
34 *
35 * @see WP_List_Table::__construct() for more information on default arguments.
36 *
37 * @param array $args An associative array of arguments.
38 */
39 public function __construct( $args = array() ) {
40 $this->detached = ( isset( $_REQUEST['attachment-filter'] ) && 'detached' === $_REQUEST['attachment-filter'] );
41
42 $this->modes = array(
43 'list' => __( 'List view' ),
44 'grid' => __( 'Grid view' ),
45 );
46
47 parent::__construct(
48 array(
49 'plural' => 'media',
50 'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
51 )
52 );
53 }
54
55 /**
56 * @return bool
57 */
58 public function ajax_user_can() {
59 return current_user_can( 'upload_files' );
60 }
61
62 /**
63 * @global string $mode List table view mode.
64 * @global WP_Query $wp_query WordPress Query object.
65 * @global array $post_mime_types
66 * @global array $avail_post_mime_types
67 */
68 public function prepare_items() {
69 global $mode, $wp_query, $post_mime_types, $avail_post_mime_types;
70
71 $mode = empty( $_REQUEST['mode'] ) ? 'list' : $_REQUEST['mode'];
72
73 /*
74 * Exclude attachments scheduled for deletion in the next two hours
75 * if they are for zip packages for interrupted or failed updates.
76 * See File_Upload_Upgrader class.
77 */
78 $not_in = array();
79
80 $crons = _get_cron_array();
81
82 if ( is_array( $crons ) ) {
83 foreach ( $crons as $cron ) {
84 if ( isset( $cron['upgrader_scheduled_cleanup'] ) ) {
85 $details = reset( $cron['upgrader_scheduled_cleanup'] );
86
87 if ( ! empty( $details['args'][0] ) ) {
88 $not_in[] = (int) $details['args'][0];
89 }
90 }
91 }
92 }
93
94 if ( ! empty( $_REQUEST['post__not_in'] ) && is_array( $_REQUEST['post__not_in'] ) ) {
95 $not_in = array_merge( array_values( $_REQUEST['post__not_in'] ), $not_in );
96 }
97
98 if ( ! empty( $not_in ) ) {
99 $_REQUEST['post__not_in'] = $not_in;
100 }
101
102 list( $post_mime_types, $avail_post_mime_types ) = wp_edit_attachments_query( $_REQUEST );
103
104 $this->is_trash = isset( $_REQUEST['attachment-filter'] ) && 'trash' === $_REQUEST['attachment-filter'];
105
106 $this->set_pagination_args(
107 array(
108 'total_items' => $wp_query->found_posts,
109 'total_pages' => $wp_query->max_num_pages,
110 'per_page' => $wp_query->query_vars['posts_per_page'],
111 )
112 );
113 if ( $wp_query->posts ) {
114 update_post_thumbnail_cache( $wp_query );
115 update_post_parent_caches( $wp_query->posts );
116 }
117 }
118
119 /**
120 * @global array $post_mime_types
121 * @global array $avail_post_mime_types
122 * @return array
123 */
124 protected function get_views() {
125 global $post_mime_types, $avail_post_mime_types;
126
127 $type_links = array();
128
129 $filter = empty( $_GET['attachment-filter'] ) ? '' : $_GET['attachment-filter'];
130
131 $type_links['all'] = sprintf(
132 '<option value=""%s>%s</option>',
133 selected( $filter, true, false ),
134 __( 'All media items' )
135 );
136
137 foreach ( $post_mime_types as $mime_type => $label ) {
138 if ( ! wp_match_mime_types( $mime_type, $avail_post_mime_types ) ) {
139 continue;
140 }
141
142 $selected = selected(
143 $filter && str_starts_with( $filter, 'post_mime_type:' ) &&
144 wp_match_mime_types( $mime_type, str_replace( 'post_mime_type:', '', $filter ) ),
145 true,
146 false
147 );
148
149 $type_links[ $mime_type ] = sprintf(
150 '<option value="post_mime_type:%s"%s>%s</option>',
151 esc_attr( $mime_type ),
152 $selected,
153 $label[0]
154 );
155 }
156
157 $type_links['detached'] = '<option value="detached"' . ( $this->detached ? ' selected="selected"' : '' ) . '>' . _x( 'Unattached', 'media items' ) . '</option>';
158
159 $type_links['mine'] = sprintf(
160 '<option value="mine"%s>%s</option>',
161 selected( 'mine' === $filter, true, false ),
162 _x( 'Mine', 'media items' )
163 );
164
165 if ( $this->is_trash || ( defined( 'MEDIA_TRASH' ) && MEDIA_TRASH ) ) {
166 $type_links['trash'] = sprintf(
167 '<option value="trash"%s>%s</option>',
168 selected( 'trash' === $filter, true, false ),
169 _x( 'Trash', 'attachment filter' )
170 );
171 }
172
173 return $type_links;
174 }
175
176 /**
177 * @return array
178 */
179 protected function get_bulk_actions() {
180 $actions = array();
181
182 if ( MEDIA_TRASH ) {
183 if ( $this->is_trash ) {
184 $actions['untrash'] = __( 'Restore' );
185 $actions['delete'] = __( 'Delete permanently' );
186 } else {
187 $actions['trash'] = __( 'Move to Trash' );
188 }
189 } else {
190 $actions['delete'] = __( 'Delete permanently' );
191 }
192
193 if ( $this->detached ) {
194 $actions['attach'] = __( 'Attach' );
195 }
196
197 return $actions;
198 }
199
200 /**
201 * @param string $which
202 */
203 protected function extra_tablenav( $which ) {
204 if ( 'bar' !== $which ) {
205 return;
206 }
207 ?>
208 <div class="actions">
209 <?php
210 if ( ! $this->is_trash ) {
211 $this->months_dropdown( 'attachment' );
212 }
213
214 /** This action is documented in wp-admin/includes/class-wp-posts-list-table.php */
215 do_action( 'restrict_manage_posts', $this->screen->post_type, $which );
216
217 submit_button( __( 'Filter' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
218
219 if ( $this->is_trash && $this->has_items()
220 && current_user_can( 'edit_others_posts' )
221 ) {
222 submit_button( __( 'Empty Trash' ), 'apply', 'delete_all', false );
223 }
224 ?>
225 </div>
226 <?php
227 }
228
229 /**
230 * @return string
231 */
232 public function current_action() {
233 if ( isset( $_REQUEST['found_post_id'] ) && isset( $_REQUEST['media'] ) ) {
234 return 'attach';
235 }
236
237 if ( isset( $_REQUEST['parent_post_id'] ) && isset( $_REQUEST['media'] ) ) {
238 return 'detach';
239 }
240
241 if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) ) {
242 return 'delete_all';
243 }
244
245 return parent::current_action();
246 }
247
248 /**
249 * @return bool
250 */
251 public function has_items() {
252 return have_posts();
253 }
254
255 /**
256 */
257 public function no_items() {
258 if ( $this->is_trash ) {
259 _e( 'No media files found in Trash.' );
260 } else {
261 _e( 'No media files found.' );
262 }
263 }
264
265 /**
266 * Overrides parent views to use the filter bar display.
267 *
268 * @global string $mode List table view mode.
269 */
270 public function views() {
271 global $mode;
272
273 $views = $this->get_views();
274
275 $this->screen->render_screen_reader_content( 'heading_views' );
276 ?>
277 <div class="wp-filter">
278 <div class="filter-items">
279 <?php $this->view_switcher( $mode ); ?>
280
281 <label for="attachment-filter" class="screen-reader-text">
282 <?php
283 /* translators: Hidden accessibility text. */
284 _e( 'Filter by type' );
285 ?>
286 </label>
287 <select class="attachment-filters" name="attachment-filter" id="attachment-filter">
288 <?php
289 if ( ! empty( $views ) ) {
290 foreach ( $views as $class => $view ) {
291 echo "\t$view\n";
292 }
293 }
294 ?>
295 </select>
296
297 <?php
298 $this->extra_tablenav( 'bar' );
299
300 /** This filter is documented in wp-admin/includes/class-wp-list-table.php */
301 $views = apply_filters( "views_{$this->screen->id}", array() );
302
303 // Back compat for pre-4.0 view links.
304 if ( ! empty( $views ) ) {
305 echo '<ul class="filter-links">';
306 foreach ( $views as $class => $view ) {
307 echo "<li class='$class'>$view</li>";
308 }
309 echo '</ul>';
310 }
311 ?>
312 </div>
313
314 <div class="search-form">
315 <p class="search-box">
316 <label class="screen-reader-text" for="media-search-input">
317 <?php
318 /* translators: Hidden accessibility text. */
319 esc_html_e( 'Search Media' );
320 ?>
321 </label>
322 <input type="search" id="media-search-input" class="search" name="s" value="<?php _admin_search_query(); ?>">
323 <input id="search-submit" type="submit" class="button" value="<?php esc_attr_e( 'Search Media' ); ?>">
324 </p>
325 </div>
326 </div>
327 <?php
328 }
329
330 /**
331 * @return string[] Array of column titles keyed by their column name.
332 */
333 public function get_columns() {
334 $posts_columns = array();
335 $posts_columns['cb'] = '<input type="checkbox" />';
336 /* translators: Column name. */
337 $posts_columns['title'] = _x( 'File', 'column name' );
338 $posts_columns['author'] = __( 'Author' );
339
340 $taxonomies = get_taxonomies_for_attachments( 'objects' );
341 $taxonomies = wp_filter_object_list( $taxonomies, array( 'show_admin_column' => true ), 'and', 'name' );
342
343 /**
344 * Filters the taxonomy columns for attachments in the Media list table.
345 *
346 * @since 3.5.0
347 *
348 * @param string[] $taxonomies An array of registered taxonomy names to show for attachments.
349 * @param string $post_type The post type. Default 'attachment'.
350 */
351 $taxonomies = apply_filters( 'manage_taxonomies_for_attachment_columns', $taxonomies, 'attachment' );
352 $taxonomies = array_filter( $taxonomies, 'taxonomy_exists' );
353
354 foreach ( $taxonomies as $taxonomy ) {
355 if ( 'category' === $taxonomy ) {
356 $column_key = 'categories';
357 } elseif ( 'post_tag' === $taxonomy ) {
358 $column_key = 'tags';
359 } else {
360 $column_key = 'taxonomy-' . $taxonomy;
361 }
362
363 $posts_columns[ $column_key ] = get_taxonomy( $taxonomy )->labels->name;
364 }
365
366 /* translators: Column name. */
367 if ( ! $this->detached ) {
368 $posts_columns['parent'] = _x( 'Uploaded to', 'column name' );
369
370 if ( post_type_supports( 'attachment', 'comments' ) ) {
371 $posts_columns['comments'] = sprintf(
372 '<span class="vers comment-grey-bubble" title="%1$s" aria-hidden="true"></span><span class="screen-reader-text">%2$s</span>',
373 esc_attr__( 'Comments' ),
374 /* translators: Hidden accessibility text. */
375 __( 'Comments' )
376 );
377 }
378 }
379
380 /* translators: Column name. */
381 $posts_columns['date'] = _x( 'Date', 'column name' );
382
383 /**
384 * Filters the Media list table columns.
385 *
386 * @since 2.5.0
387 *
388 * @param string[] $posts_columns An array of columns displayed in the Media list table.
389 * @param bool $detached Whether the list table contains media not attached
390 * to any posts. Default true.
391 */
392 return apply_filters( 'manage_media_columns', $posts_columns, $this->detached );
393 }
394
395 /**
396 * @return array
397 */
398 protected function get_sortable_columns() {
399 return array(
400 'title' => array( 'title', false, _x( 'File', 'column name' ), __( 'Table ordered by File Name.' ) ),
401 'author' => array( 'author', false, __( 'Author' ), __( 'Table ordered by Author.' ) ),
402 'parent' => array( 'parent', false, _x( 'Uploaded to', 'column name' ), __( 'Table ordered by Uploaded To.' ) ),
403 'comments' => array( 'comment_count', __( 'Comments' ), false, __( 'Table ordered by Comments.' ) ),
404 'date' => array( 'date', true, __( 'Date' ), __( 'Table ordered by Date.' ), 'desc' ),
405 );
406 }
407
408 /**
409 * Handles the checkbox column output.
410 *
411 * @since 4.3.0
412 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
413 *
414 * @param WP_Post $item The current WP_Post object.
415 */
416 public function column_cb( $item ) {
417 // Restores the more descriptive, specific name for use within this method.
418 $post = $item;
419
420 if ( current_user_can( 'edit_post', $post->ID ) ) {
421 ?>
422 <input type="checkbox" name="media[]" id="cb-select-<?php echo $post->ID; ?>" value="<?php echo $post->ID; ?>" />
423 <label for="cb-select-<?php echo $post->ID; ?>">
424 <span class="screen-reader-text">
425 <?php
426 /* translators: Hidden accessibility text. %s: Attachment title. */
427 printf( __( 'Select %s' ), _draft_or_post_title() );
428 ?>
429 </span>
430 </label>
431 <?php
432 }
433 }
434
435 /**
436 * Handles the title column output.
437 *
438 * @since 4.3.0
439 *
440 * @param WP_Post $post The current WP_Post object.
441 */
442 public function column_title( $post ) {
443 list( $mime ) = explode( '/', $post->post_mime_type );
444
445 $attachment_id = $post->ID;
446
447 if ( has_post_thumbnail( $post ) ) {
448 $thumbnail_id = get_post_thumbnail_id( $post );
449
450 if ( ! empty( $thumbnail_id ) ) {
451 $attachment_id = $thumbnail_id;
452 }
453 }
454
455 $title = _draft_or_post_title();
456 $thumb = wp_get_attachment_image( $attachment_id, array( 60, 60 ), true, array( 'alt' => '' ) );
457 $link_start = '';
458 $link_end = '';
459
460 if ( current_user_can( 'edit_post', $post->ID ) && ! $this->is_trash ) {
461 $link_start = sprintf(
462 '<a href="%s" aria-label="%s">',
463 get_edit_post_link( $post->ID ),
464 /* translators: %s: Attachment title. */
465 esc_attr( sprintf( __( '&#8220;%s&#8221; (Edit)' ), $title ) )
466 );
467 $link_end = '</a>';
468 }
469
470 $class = $thumb ? ' class="has-media-icon"' : '';
471 ?>
472 <strong<?php echo $class; ?>>
473 <?php
474 echo $link_start;
475
476 if ( $thumb ) :
477 ?>
478 <span class="media-icon <?php echo sanitize_html_class( $mime . '-icon' ); ?>"><?php echo $thumb; ?></span>
479 <?php
480 endif;
481
482 echo $title . $link_end;
483
484 _media_states( $post );
485 ?>
486 </strong>
487 <p class="filename">
488 <span class="screen-reader-text">
489 <?php
490 /* translators: Hidden accessibility text. */
491 _e( 'File name:' );
492 ?>
493 </span>
494 <?php
495 $file = get_attached_file( $post->ID );
496 echo esc_html( wp_basename( $file ) );
497 ?>
498 </p>
499 <?php
500 }
501
502 /**
503 * Handles the author column output.
504 *
505 * @since 4.3.0
506 * @since 6.8.0 Added fallback text when author's name is unknown.
507 *
508 * @param WP_Post $post The current WP_Post object.
509 */
510 public function column_author( $post ) {
511 $author = get_the_author();
512
513 if ( ! empty( $author ) ) {
514 printf(
515 '<a href="%s">%s</a>',
516 esc_url( add_query_arg( array( 'author' => get_the_author_meta( 'ID' ) ), 'upload.php' ) ),
517 esc_html( $author )
518 );
519 } else {
520 echo '<span aria-hidden="true">&#8212;</span><span class="screen-reader-text">' . __( '(no author)' ) . '</span>';
521 }
522 }
523
524 /**
525 * Handles the description column output.
526 *
527 * @since 4.3.0
528 * @deprecated 6.2.0
529 *
530 * @param WP_Post $post The current WP_Post object.
531 */
532 public function column_desc( $post ) {
533 _deprecated_function( __METHOD__, '6.2.0' );
534
535 echo has_excerpt() ? $post->post_excerpt : '';
536 }
537
538 /**
539 * Handles the date column output.
540 *
541 * @since 4.3.0
542 *
543 * @param WP_Post $post The current WP_Post object.
544 */
545 public function column_date( $post ) {
546 if ( '0000-00-00 00:00:00' === $post->post_date ) {
547 $h_time = __( 'Unpublished' );
548 } else {
549 $time = get_post_timestamp( $post );
550 $time_diff = time() - $time;
551
552 if ( $time && $time_diff > 0 && $time_diff < DAY_IN_SECONDS ) {
553 /* translators: %s: Human-readable time difference. */
554 $h_time = sprintf( __( '%s ago' ), human_time_diff( $time ) );
555 } else {
556 $h_time = get_the_time( __( 'Y/m/d' ), $post );
557 }
558 }
559
560 /**
561 * Filters the published time of an attachment displayed in the Media list table.
562 *
563 * @since 6.0.0
564 *
565 * @param string $h_time The published time.
566 * @param WP_Post $post Attachment object.
567 * @param string $column_name The column name.
568 */
569 echo apply_filters( 'media_date_column_time', $h_time, $post, 'date' );
570 }
571
572 /**
573 * Handles the parent column output.
574 *
575 * @since 4.3.0
576 *
577 * @param WP_Post $post The current WP_Post object.
578 */
579 public function column_parent( $post ) {
580 $user_can_edit = current_user_can( 'edit_post', $post->ID );
581
582 if ( $post->post_parent > 0 ) {
583 $parent = get_post( $post->post_parent );
584 } else {
585 $parent = false;
586 }
587
588 if ( $parent ) {
589 $title = _draft_or_post_title( $post->post_parent );
590 $parent_type = get_post_type_object( $parent->post_type );
591
592 if ( $parent_type && $parent_type->show_ui && current_user_can( 'edit_post', $post->post_parent ) ) {
593 printf( '<strong><a href="%s">%s</a></strong>', get_edit_post_link( $post->post_parent ), $title );
594 } elseif ( $parent_type && current_user_can( 'read_post', $post->post_parent ) ) {
595 printf( '<strong>%s</strong>', $title );
596 } else {
597 _e( '(Private post)' );
598 }
599
600 if ( $user_can_edit ) :
601 $detach_url = add_query_arg(
602 array(
603 'parent_post_id' => $post->post_parent,
604 'media[]' => $post->ID,
605 '_wpnonce' => wp_create_nonce( 'bulk-' . $this->_args['plural'] ),
606 ),
607 'upload.php'
608 );
609 printf(
610 '<br /><a href="%s" class="hide-if-no-js detach-from-parent" aria-label="%s">%s</a>',
611 $detach_url,
612 /* translators: %s: Title of the post the attachment is attached to. */
613 esc_attr( sprintf( __( 'Detach from &#8220;%s&#8221;' ), $title ) ),
614 __( 'Detach' )
615 );
616 endif;
617 } else {
618 _e( '(Unattached)' );
619 ?>
620 <?php
621 if ( $user_can_edit ) {
622 $title = _draft_or_post_title( $post->post_parent );
623 printf(
624 '<br /><a href="#the-list" onclick="findPosts.open( \'media[]\', \'%s\' ); return false;" class="hide-if-no-js aria-button-if-js" aria-label="%s">%s</a>',
625 $post->ID,
626 /* translators: %s: Attachment title. */
627 esc_attr( sprintf( __( 'Attach &#8220;%s&#8221; to existing content' ), $title ) ),
628 __( 'Attach' )
629 );
630 }
631 }
632 }
633
634 /**
635 * Handles the comments column output.
636 *
637 * @since 4.3.0
638 *
639 * @param WP_Post $post The current WP_Post object.
640 */
641 public function column_comments( $post ) {
642 echo '<div class="post-com-count-wrapper">';
643
644 if ( isset( $this->comment_pending_count[ $post->ID ] ) ) {
645 $pending_comments = $this->comment_pending_count[ $post->ID ];
646 } else {
647 $pending_comments = get_pending_comments_num( $post->ID );
648 }
649
650 $this->comments_bubble( $post->ID, $pending_comments );
651
652 echo '</div>';
653 }
654
655 /**
656 * Handles output for the default column.
657 *
658 * @since 4.3.0
659 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
660 *
661 * @param WP_Post $item The current WP_Post object.
662 * @param string $column_name Current column name.
663 */
664 public function column_default( $item, $column_name ) {
665 // Restores the more descriptive, specific name for use within this method.
666 $post = $item;
667
668 if ( 'categories' === $column_name ) {
669 $taxonomy = 'category';
670 } elseif ( 'tags' === $column_name ) {
671 $taxonomy = 'post_tag';
672 } elseif ( str_starts_with( $column_name, 'taxonomy-' ) ) {
673 $taxonomy = substr( $column_name, 9 );
674 } else {
675 $taxonomy = false;
676 }
677
678 if ( $taxonomy ) {
679 $terms = get_the_terms( $post->ID, $taxonomy );
680
681 if ( is_array( $terms ) ) {
682 $output = array();
683
684 foreach ( $terms as $t ) {
685 $posts_in_term_qv = array();
686 $posts_in_term_qv['taxonomy'] = $taxonomy;
687 $posts_in_term_qv['term'] = $t->slug;
688
689 $output[] = sprintf(
690 '<a href="%s">%s</a>',
691 esc_url( add_query_arg( $posts_in_term_qv, 'upload.php' ) ),
692 esc_html( sanitize_term_field( 'name', $t->name, $t->term_id, $taxonomy, 'display' ) )
693 );
694 }
695
696 echo implode( wp_get_list_item_separator(), $output );
697 } else {
698 echo '<span aria-hidden="true">&#8212;</span><span class="screen-reader-text">' . get_taxonomy( $taxonomy )->labels->no_terms . '</span>';
699 }
700
701 return;
702 }
703
704 /**
705 * Fires for each custom column in the Media list table.
706 *
707 * Custom columns are registered using the {@see 'manage_media_columns'} filter.
708 *
709 * @since 2.5.0
710 *
711 * @param string $column_name Name of the custom column.
712 * @param int $post_id Attachment ID.
713 */
714 do_action( 'manage_media_custom_column', $column_name, $post->ID );
715 }
716
717 /**
718 * Generates the list table rows.
719 *
720 * @since 3.1.0
721 *
722 * @global WP_Post $post Global post object.
723 * @global WP_Query $wp_query WordPress Query object.
724 */
725 public function display_rows() {
726 global $post, $wp_query;
727
728 $post_ids = wp_list_pluck( $wp_query->posts, 'ID' );
729 reset( $wp_query->posts );
730
731 $this->comment_pending_count = get_pending_comments_num( $post_ids );
732
733 add_filter( 'the_title', 'esc_html' );
734
735 while ( have_posts() ) :
736 the_post();
737
738 if ( $this->is_trash && 'trash' !== $post->post_status
739 || ! $this->is_trash && 'trash' === $post->post_status
740 ) {
741 continue;
742 }
743
744 $post_owner = ( get_current_user_id() === (int) $post->post_author ) ? 'self' : 'other';
745 ?>
746 <tr id="post-<?php echo $post->ID; ?>" class="<?php echo trim( ' author-' . $post_owner . ' status-' . $post->post_status ); ?>">
747 <?php $this->single_row_columns( $post ); ?>
748 </tr>
749 <?php
750 endwhile;
751 }
752
753 /**
754 * Gets the name of the default primary column.
755 *
756 * @since 4.3.0
757 *
758 * @return string Name of the default primary column, in this case, 'title'.
759 */
760 protected function get_default_primary_column_name() {
761 return 'title';
762 }
763
764 /**
765 * @param WP_Post $post
766 * @param string $att_title
767 * @return array
768 */
769 private function _get_row_actions( $post, $att_title ) {
770 $actions = array();
771
772 if ( ! $this->is_trash && current_user_can( 'edit_post', $post->ID ) ) {
773 $actions['edit'] = sprintf(
774 '<a href="%s" aria-label="%s">%s</a>',
775 esc_url( get_edit_post_link( $post->ID ) ),
776 /* translators: %s: Attachment title. */
777 esc_attr( sprintf( __( 'Edit &#8220;%s&#8221;' ), $att_title ) ),
778 __( 'Edit' )
779 );
780 }
781
782 if ( current_user_can( 'delete_post', $post->ID ) ) {
783 if ( $this->is_trash ) {
784 $actions['untrash'] = sprintf(
785 '<a href="%s" class="submitdelete aria-button-if-js" aria-label="%s">%s</a>',
786 esc_url( wp_nonce_url( "post.php?action=untrash&amp;post=$post->ID", 'untrash-post_' . $post->ID ) ),
787 /* translators: %s: Attachment title. */
788 esc_attr( sprintf( __( 'Restore &#8220;%s&#8221; from the Trash' ), $att_title ) ),
789 __( 'Restore' )
790 );
791 } elseif ( EMPTY_TRASH_DAYS && MEDIA_TRASH ) {
792 $actions['trash'] = sprintf(
793 '<a href="%s" class="submitdelete aria-button-if-js" aria-label="%s">%s</a>',
794 esc_url( wp_nonce_url( "post.php?action=trash&amp;post=$post->ID", 'trash-post_' . $post->ID ) ),
795 /* translators: %s: Attachment title. */
796 esc_attr( sprintf( __( 'Move &#8220;%s&#8221; to the Trash' ), $att_title ) ),
797 _x( 'Trash', 'verb' )
798 );
799 }
800
801 if ( $this->is_trash || ! EMPTY_TRASH_DAYS || ! MEDIA_TRASH ) {
802 $show_confirmation = ( ! $this->is_trash && ! MEDIA_TRASH ) ? " onclick='return showNotice.warn();'" : '';
803
804 $actions['delete'] = sprintf(
805 '<a href="%s" class="submitdelete aria-button-if-js"%s aria-label="%s">%s</a>',
806 esc_url( wp_nonce_url( "post.php?action=delete&amp;post=$post->ID", 'delete-post_' . $post->ID ) ),
807 $show_confirmation,
808 /* translators: %s: Attachment title. */
809 esc_attr( sprintf( __( 'Delete &#8220;%s&#8221; permanently' ), $att_title ) ),
810 __( 'Delete Permanently' )
811 );
812 }
813 }
814
815 $attachment_url = wp_get_attachment_url( $post->ID );
816
817 if ( ! $this->is_trash ) {
818 $permalink = get_permalink( $post->ID );
819
820 if ( $permalink ) {
821 $actions['view'] = sprintf(
822 '<a href="%s" aria-label="%s" rel="bookmark">%s</a>',
823 esc_url( $permalink ),
824 /* translators: %s: Attachment title. */
825 esc_attr( sprintf( __( 'View &#8220;%s&#8221;' ), $att_title ) ),
826 __( 'View' )
827 );
828 }
829
830 if ( $attachment_url ) {
831 $actions['copy'] = sprintf(
832 '<span class="copy-to-clipboard-container"><button type="button" class="button-link copy-attachment-url media-library" data-clipboard-text="%s" aria-label="%s">%s</button><span class="success hidden" aria-hidden="true">%s</span></span>',
833 esc_url( $attachment_url ),
834 /* translators: %s: Attachment title. */
835 esc_attr( sprintf( __( 'Copy &#8220;%s&#8221; URL to clipboard' ), $att_title ) ),
836 __( 'Copy URL' ),
837 __( 'Copied!' )
838 );
839 }
840 }
841
842 if ( $attachment_url ) {
843 $actions['download'] = sprintf(
844 '<a href="%s" aria-label="%s" download>%s</a>',
845 esc_url( $attachment_url ),
846 /* translators: %s: Attachment title. */
847 esc_attr( sprintf( __( 'Download &#8220;%s&#8221;' ), $att_title ) ),
848 __( 'Download file' )
849 );
850 }
851
852 if ( $this->detached && current_user_can( 'edit_post', $post->ID ) ) {
853 $actions['attach'] = sprintf(
854 '<a href="#the-list" onclick="findPosts.open( \'media[]\', \'%s\' ); return false;" class="hide-if-no-js aria-button-if-js" aria-label="%s">%s</a>',
855 $post->ID,
856 /* translators: %s: Attachment title. */
857 esc_attr( sprintf( __( 'Attach &#8220;%s&#8221; to existing content' ), $att_title ) ),
858 __( 'Attach' )
859 );
860 }
861
862 /**
863 * Filters the action links for each attachment in the Media list table.
864 *
865 * @since 2.8.0
866 *
867 * @param string[] $actions An array of action links for each attachment.
868 * Includes 'Edit', 'Delete Permanently', 'View',
869 * 'Copy URL' and 'Download file'.
870 * @param WP_Post $post WP_Post object for the current attachment.
871 * @param bool $detached Whether the list table contains media not attached
872 * to any posts. Default true.
873 */
874 return apply_filters( 'media_row_actions', $actions, $post, $this->detached );
875 }
876
877 /**
878 * Generates and displays row action links.
879 *
880 * @since 4.3.0
881 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
882 *
883 * @param WP_Post $item Attachment being acted upon.
884 * @param string $column_name Current column name.
885 * @param string $primary Primary column name.
886 * @return string Row actions output for media attachments, or an empty string
887 * if the current column is not the primary column.
888 */
889 protected function handle_row_actions( $item, $column_name, $primary ) {
890 if ( $primary !== $column_name ) {
891 return '';
892 }
893
894 // Restores the more descriptive, specific name for use within this method.
895 $post = $item;
896
897 $att_title = _draft_or_post_title();
898 $actions = $this->_get_row_actions( $post, $att_title );
899
900 return $this->row_actions( $actions );
901 }
902}
903