Нет описания

class-wp-comments-list-table.php 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080
  1. <?php
  2. /**
  3. * List Table API: WP_Comments_List_Table class
  4. *
  5. * @package WordPress
  6. * @subpackage Administration
  7. * @since 3.1.0
  8. */
  9. /**
  10. * Core class used to implement displaying comments in a list table.
  11. *
  12. * @since 3.1.0
  13. * @access private
  14. *
  15. * @see WP_List_Table
  16. */
  17. class WP_Comments_List_Table extends WP_List_Table {
  18. public $checkbox = true;
  19. public $pending_count = array();
  20. public $extra_items;
  21. private $user_can;
  22. /**
  23. * Constructor.
  24. *
  25. * @since 3.1.0
  26. *
  27. * @see WP_List_Table::__construct() for more information on default arguments.
  28. *
  29. * @global int $post_id
  30. *
  31. * @param array $args An associative array of arguments.
  32. */
  33. public function __construct( $args = array() ) {
  34. global $post_id;
  35. $post_id = isset( $_REQUEST['p'] ) ? absint( $_REQUEST['p'] ) : 0;
  36. if ( get_option( 'show_avatars' ) ) {
  37. add_filter( 'comment_author', array( $this, 'floated_admin_avatar' ), 10, 2 );
  38. }
  39. parent::__construct(
  40. array(
  41. 'plural' => 'comments',
  42. 'singular' => 'comment',
  43. 'ajax' => true,
  44. 'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
  45. )
  46. );
  47. }
  48. /**
  49. * Adds avatars to comment author names.
  50. *
  51. * @since 3.1.0
  52. *
  53. * @param string $name Comment author name.
  54. * @param int $comment_id Comment ID.
  55. * @return string Avatar with the user name.
  56. */
  57. public function floated_admin_avatar( $name, $comment_id ) {
  58. $comment = get_comment( $comment_id );
  59. $avatar = get_avatar( $comment, 32, 'mystery' );
  60. return "$avatar $name";
  61. }
  62. /**
  63. * @return bool
  64. */
  65. public function ajax_user_can() {
  66. return current_user_can( 'edit_posts' );
  67. }
  68. /**
  69. * @global string $mode List table view mode.
  70. * @global int $post_id
  71. * @global string $comment_status
  72. * @global string $comment_type
  73. * @global string $search
  74. */
  75. public function prepare_items() {
  76. global $mode, $post_id, $comment_status, $comment_type, $search;
  77. if ( ! empty( $_REQUEST['mode'] ) ) {
  78. $mode = 'excerpt' === $_REQUEST['mode'] ? 'excerpt' : 'list';
  79. set_user_setting( 'posts_list_mode', $mode );
  80. } else {
  81. $mode = get_user_setting( 'posts_list_mode', 'list' );
  82. }
  83. $comment_status = isset( $_REQUEST['comment_status'] ) ? $_REQUEST['comment_status'] : 'all';
  84. if ( ! in_array( $comment_status, array( 'all', 'mine', 'moderated', 'approved', 'spam', 'trash' ), true ) ) {
  85. $comment_status = 'all';
  86. }
  87. $comment_type = ! empty( $_REQUEST['comment_type'] ) ? $_REQUEST['comment_type'] : '';
  88. $search = ( isset( $_REQUEST['s'] ) ) ? $_REQUEST['s'] : '';
  89. $post_type = ( isset( $_REQUEST['post_type'] ) ) ? sanitize_key( $_REQUEST['post_type'] ) : '';
  90. $user_id = ( isset( $_REQUEST['user_id'] ) ) ? $_REQUEST['user_id'] : '';
  91. $orderby = ( isset( $_REQUEST['orderby'] ) ) ? $_REQUEST['orderby'] : '';
  92. $order = ( isset( $_REQUEST['order'] ) ) ? $_REQUEST['order'] : '';
  93. $comments_per_page = $this->get_per_page( $comment_status );
  94. $doing_ajax = wp_doing_ajax();
  95. if ( isset( $_REQUEST['number'] ) ) {
  96. $number = (int) $_REQUEST['number'];
  97. } else {
  98. $number = $comments_per_page + min( 8, $comments_per_page ); // Grab a few extra.
  99. }
  100. $page = $this->get_pagenum();
  101. if ( isset( $_REQUEST['start'] ) ) {
  102. $start = $_REQUEST['start'];
  103. } else {
  104. $start = ( $page - 1 ) * $comments_per_page;
  105. }
  106. if ( $doing_ajax && isset( $_REQUEST['offset'] ) ) {
  107. $start += $_REQUEST['offset'];
  108. }
  109. $status_map = array(
  110. 'mine' => '',
  111. 'moderated' => 'hold',
  112. 'approved' => 'approve',
  113. 'all' => '',
  114. );
  115. $args = array(
  116. 'status' => isset( $status_map[ $comment_status ] ) ? $status_map[ $comment_status ] : $comment_status,
  117. 'search' => $search,
  118. 'user_id' => $user_id,
  119. 'offset' => $start,
  120. 'number' => $number,
  121. 'post_id' => $post_id,
  122. 'type' => $comment_type,
  123. 'orderby' => $orderby,
  124. 'order' => $order,
  125. 'post_type' => $post_type,
  126. );
  127. /**
  128. * Filters the arguments for the comment query in the comments list table.
  129. *
  130. * @since 5.1.0
  131. *
  132. * @param array $args An array of get_comments() arguments.
  133. */
  134. $args = apply_filters( 'comments_list_table_query_args', $args );
  135. $_comments = get_comments( $args );
  136. if ( is_array( $_comments ) ) {
  137. update_comment_cache( $_comments );
  138. $this->items = array_slice( $_comments, 0, $comments_per_page );
  139. $this->extra_items = array_slice( $_comments, $comments_per_page );
  140. $_comment_post_ids = array_unique( wp_list_pluck( $_comments, 'comment_post_ID' ) );
  141. $this->pending_count = get_pending_comments_num( $_comment_post_ids );
  142. }
  143. $total_comments = get_comments(
  144. array_merge(
  145. $args,
  146. array(
  147. 'count' => true,
  148. 'offset' => 0,
  149. 'number' => 0,
  150. )
  151. )
  152. );
  153. $this->set_pagination_args(
  154. array(
  155. 'total_items' => $total_comments,
  156. 'per_page' => $comments_per_page,
  157. )
  158. );
  159. }
  160. /**
  161. * @param string $comment_status
  162. * @return int
  163. */
  164. public function get_per_page( $comment_status = 'all' ) {
  165. $comments_per_page = $this->get_items_per_page( 'edit_comments_per_page' );
  166. /**
  167. * Filters the number of comments listed per page in the comments list table.
  168. *
  169. * @since 2.6.0
  170. *
  171. * @param int $comments_per_page The number of comments to list per page.
  172. * @param string $comment_status The comment status name. Default 'All'.
  173. */
  174. return apply_filters( 'comments_per_page', $comments_per_page, $comment_status );
  175. }
  176. /**
  177. * @global string $comment_status
  178. */
  179. public function no_items() {
  180. global $comment_status;
  181. if ( 'moderated' === $comment_status ) {
  182. _e( 'No comments awaiting moderation.' );
  183. } elseif ( 'trash' === $comment_status ) {
  184. _e( 'No comments found in Trash.' );
  185. } else {
  186. _e( 'No comments found.' );
  187. }
  188. }
  189. /**
  190. * @global int $post_id
  191. * @global string $comment_status
  192. * @global string $comment_type
  193. */
  194. protected function get_views() {
  195. global $post_id, $comment_status, $comment_type;
  196. $status_links = array();
  197. $num_comments = ( $post_id ) ? wp_count_comments( $post_id ) : wp_count_comments();
  198. $stati = array(
  199. /* translators: %s: Number of comments. */
  200. 'all' => _nx_noop(
  201. 'All <span class="count">(%s)</span>',
  202. 'All <span class="count">(%s)</span>',
  203. 'comments'
  204. ), // Singular not used.
  205. /* translators: %s: Number of comments. */
  206. 'mine' => _nx_noop(
  207. 'Mine <span class="count">(%s)</span>',
  208. 'Mine <span class="count">(%s)</span>',
  209. 'comments'
  210. ),
  211. /* translators: %s: Number of comments. */
  212. 'moderated' => _nx_noop(
  213. 'Pending <span class="count">(%s)</span>',
  214. 'Pending <span class="count">(%s)</span>',
  215. 'comments'
  216. ),
  217. /* translators: %s: Number of comments. */
  218. 'approved' => _nx_noop(
  219. 'Approved <span class="count">(%s)</span>',
  220. 'Approved <span class="count">(%s)</span>',
  221. 'comments'
  222. ),
  223. /* translators: %s: Number of comments. */
  224. 'spam' => _nx_noop(
  225. 'Spam <span class="count">(%s)</span>',
  226. 'Spam <span class="count">(%s)</span>',
  227. 'comments'
  228. ),
  229. /* translators: %s: Number of comments. */
  230. 'trash' => _nx_noop(
  231. 'Trash <span class="count">(%s)</span>',
  232. 'Trash <span class="count">(%s)</span>',
  233. 'comments'
  234. ),
  235. );
  236. if ( ! EMPTY_TRASH_DAYS ) {
  237. unset( $stati['trash'] );
  238. }
  239. $link = admin_url( 'edit-comments.php' );
  240. if ( ! empty( $comment_type ) && 'all' !== $comment_type ) {
  241. $link = add_query_arg( 'comment_type', $comment_type, $link );
  242. }
  243. foreach ( $stati as $status => $label ) {
  244. $current_link_attributes = '';
  245. if ( $status === $comment_status ) {
  246. $current_link_attributes = ' class="current" aria-current="page"';
  247. }
  248. if ( 'mine' === $status ) {
  249. $current_user_id = get_current_user_id();
  250. $num_comments->mine = get_comments(
  251. array(
  252. 'post_id' => $post_id ? $post_id : 0,
  253. 'user_id' => $current_user_id,
  254. 'count' => true,
  255. )
  256. );
  257. $link = add_query_arg( 'user_id', $current_user_id, $link );
  258. } else {
  259. $link = remove_query_arg( 'user_id', $link );
  260. }
  261. if ( ! isset( $num_comments->$status ) ) {
  262. $num_comments->$status = 10;
  263. }
  264. $link = add_query_arg( 'comment_status', $status, $link );
  265. if ( $post_id ) {
  266. $link = add_query_arg( 'p', absint( $post_id ), $link );
  267. }
  268. /*
  269. // I toyed with this, but decided against it. Leaving it in here in case anyone thinks it is a good idea. ~ Mark
  270. if ( !empty( $_REQUEST['s'] ) )
  271. $link = add_query_arg( 's', esc_attr( wp_unslash( $_REQUEST['s'] ) ), $link );
  272. */
  273. $status_links[ $status ] = "<a href='$link'$current_link_attributes>" . sprintf(
  274. translate_nooped_plural( $label, $num_comments->$status ),
  275. sprintf(
  276. '<span class="%s-count">%s</span>',
  277. ( 'moderated' === $status ) ? 'pending' : $status,
  278. number_format_i18n( $num_comments->$status )
  279. )
  280. ) . '</a>';
  281. }
  282. /**
  283. * Filters the comment status links.
  284. *
  285. * @since 2.5.0
  286. * @since 5.1.0 The 'Mine' link was added.
  287. *
  288. * @param string[] $status_links An associative array of fully-formed comment status links. Includes 'All', 'Mine',
  289. * 'Pending', 'Approved', 'Spam', and 'Trash'.
  290. */
  291. return apply_filters( 'comment_status_links', $status_links );
  292. }
  293. /**
  294. * @global string $comment_status
  295. *
  296. * @return array
  297. */
  298. protected function get_bulk_actions() {
  299. global $comment_status;
  300. $actions = array();
  301. if ( in_array( $comment_status, array( 'all', 'approved' ), true ) ) {
  302. $actions['unapprove'] = __( 'Unapprove' );
  303. }
  304. if ( in_array( $comment_status, array( 'all', 'moderated' ), true ) ) {
  305. $actions['approve'] = __( 'Approve' );
  306. }
  307. if ( in_array( $comment_status, array( 'all', 'moderated', 'approved', 'trash' ), true ) ) {
  308. $actions['spam'] = _x( 'Mark as spam', 'comment' );
  309. }
  310. if ( 'trash' === $comment_status ) {
  311. $actions['untrash'] = __( 'Restore' );
  312. } elseif ( 'spam' === $comment_status ) {
  313. $actions['unspam'] = _x( 'Not spam', 'comment' );
  314. }
  315. if ( in_array( $comment_status, array( 'trash', 'spam' ), true ) || ! EMPTY_TRASH_DAYS ) {
  316. $actions['delete'] = __( 'Delete permanently' );
  317. } else {
  318. $actions['trash'] = __( 'Move to Trash' );
  319. }
  320. return $actions;
  321. }
  322. /**
  323. * @global string $comment_status
  324. * @global string $comment_type
  325. *
  326. * @param string $which
  327. */
  328. protected function extra_tablenav( $which ) {
  329. global $comment_status, $comment_type;
  330. static $has_items;
  331. if ( ! isset( $has_items ) ) {
  332. $has_items = $this->has_items();
  333. }
  334. echo '<div class="alignleft actions">';
  335. if ( 'top' === $which ) {
  336. ob_start();
  337. $this->comment_type_dropdown( $comment_type );
  338. /**
  339. * Fires just before the Filter submit button for comment types.
  340. *
  341. * @since 3.5.0
  342. */
  343. do_action( 'restrict_manage_comments' );
  344. $output = ob_get_clean();
  345. if ( ! empty( $output ) && $this->has_items() ) {
  346. echo $output;
  347. submit_button( __( 'Filter' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
  348. }
  349. }
  350. if ( ( 'spam' === $comment_status || 'trash' === $comment_status ) && $has_items
  351. && current_user_can( 'moderate_comments' )
  352. ) {
  353. wp_nonce_field( 'bulk-destroy', '_destroy_nonce' );
  354. $title = ( 'spam' === $comment_status ) ? esc_attr__( 'Empty Spam' ) : esc_attr__( 'Empty Trash' );
  355. submit_button( $title, 'apply', 'delete_all', false );
  356. }
  357. /**
  358. * Fires after the Filter submit button for comment types.
  359. *
  360. * @since 2.5.0
  361. * @since 5.6.0 The `$which` parameter was added.
  362. *
  363. * @param string $comment_status The comment status name. Default 'All'.
  364. * @param string $which The location of the extra table nav markup: 'top' or 'bottom'.
  365. */
  366. do_action( 'manage_comments_nav', $comment_status, $which );
  367. echo '</div>';
  368. }
  369. /**
  370. * @return string|false
  371. */
  372. public function current_action() {
  373. if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) ) {
  374. return 'delete_all';
  375. }
  376. return parent::current_action();
  377. }
  378. /**
  379. * @global int $post_id
  380. *
  381. * @return array
  382. */
  383. public function get_columns() {
  384. global $post_id;
  385. $columns = array();
  386. if ( $this->checkbox ) {
  387. $columns['cb'] = '<input type="checkbox" />';
  388. }
  389. $columns['author'] = __( 'Author' );
  390. $columns['comment'] = _x( 'Comment', 'column name' );
  391. if ( ! $post_id ) {
  392. /* translators: Column name or table row header. */
  393. $columns['response'] = __( 'In response to' );
  394. }
  395. $columns['date'] = _x( 'Submitted on', 'column name' );
  396. return $columns;
  397. }
  398. /**
  399. * Displays a comment type drop-down for filtering on the Comments list table.
  400. *
  401. * @since 5.5.0
  402. * @since 5.6.0 Renamed from `comment_status_dropdown()` to `comment_type_dropdown()`.
  403. *
  404. * @param string $comment_type The current comment type slug.
  405. */
  406. protected function comment_type_dropdown( $comment_type ) {
  407. /**
  408. * Filters the comment types shown in the drop-down menu on the Comments list table.
  409. *
  410. * @since 2.7.0
  411. *
  412. * @param string[] $comment_types Array of comment type labels keyed by their name.
  413. */
  414. $comment_types = apply_filters(
  415. 'admin_comment_types_dropdown',
  416. array(
  417. 'comment' => __( 'Comments' ),
  418. 'pings' => __( 'Pings' ),
  419. )
  420. );
  421. if ( $comment_types && is_array( $comment_types ) ) {
  422. printf( '<label class="screen-reader-text" for="filter-by-comment-type">%s</label>', __( 'Filter by comment type' ) );
  423. echo '<select id="filter-by-comment-type" name="comment_type">';
  424. printf( "\t<option value=''>%s</option>", __( 'All comment types' ) );
  425. foreach ( $comment_types as $type => $label ) {
  426. if ( get_comments(
  427. array(
  428. 'number' => 1,
  429. 'type' => $type,
  430. )
  431. ) ) {
  432. printf(
  433. "\t<option value='%s'%s>%s</option>\n",
  434. esc_attr( $type ),
  435. selected( $comment_type, $type, false ),
  436. esc_html( $label )
  437. );
  438. }
  439. }
  440. echo '</select>';
  441. }
  442. }
  443. /**
  444. * @return array
  445. */
  446. protected function get_sortable_columns() {
  447. return array(
  448. 'author' => 'comment_author',
  449. 'response' => 'comment_post_ID',
  450. 'date' => 'comment_date',
  451. );
  452. }
  453. /**
  454. * Get the name of the default primary column.
  455. *
  456. * @since 4.3.0
  457. *
  458. * @return string Name of the default primary column, in this case, 'comment'.
  459. */
  460. protected function get_default_primary_column_name() {
  461. return 'comment';
  462. }
  463. /**
  464. * Displays the comments table.
  465. *
  466. * Overrides the parent display() method to render extra comments.
  467. *
  468. * @since 3.1.0
  469. */
  470. public function display() {
  471. wp_nonce_field( 'fetch-list-' . get_class( $this ), '_ajax_fetch_list_nonce' );
  472. static $has_items;
  473. if ( ! isset( $has_items ) ) {
  474. $has_items = $this->has_items();
  475. if ( $has_items ) {
  476. $this->display_tablenav( 'top' );
  477. }
  478. }
  479. $this->screen->render_screen_reader_content( 'heading_list' );
  480. ?>
  481. <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
  482. <thead>
  483. <tr>
  484. <?php $this->print_column_headers(); ?>
  485. </tr>
  486. </thead>
  487. <tbody id="the-comment-list" data-wp-lists="list:comment">
  488. <?php $this->display_rows_or_placeholder(); ?>
  489. </tbody>
  490. <tbody id="the-extra-comment-list" data-wp-lists="list:comment" style="display: none;">
  491. <?php
  492. /*
  493. * Back up the items to restore after printing the extra items markup.
  494. * The extra items may be empty, which will prevent the table nav from displaying later.
  495. */
  496. $items = $this->items;
  497. $this->items = $this->extra_items;
  498. $this->display_rows_or_placeholder();
  499. $this->items = $items;
  500. ?>
  501. </tbody>
  502. <tfoot>
  503. <tr>
  504. <?php $this->print_column_headers( false ); ?>
  505. </tr>
  506. </tfoot>
  507. </table>
  508. <?php
  509. $this->display_tablenav( 'bottom' );
  510. }
  511. /**
  512. * @global WP_Post $post Global post object.
  513. * @global WP_Comment $comment Global comment object.
  514. *
  515. * @param WP_Comment $item
  516. */
  517. public function single_row( $item ) {
  518. global $post, $comment;
  519. $comment = $item;
  520. $the_comment_class = wp_get_comment_status( $comment );
  521. if ( ! $the_comment_class ) {
  522. $the_comment_class = '';
  523. }
  524. $the_comment_class = implode( ' ', get_comment_class( $the_comment_class, $comment, $comment->comment_post_ID ) );
  525. if ( $comment->comment_post_ID > 0 ) {
  526. $post = get_post( $comment->comment_post_ID );
  527. }
  528. $this->user_can = current_user_can( 'edit_comment', $comment->comment_ID );
  529. echo "<tr id='comment-$comment->comment_ID' class='$the_comment_class'>";
  530. $this->single_row_columns( $comment );
  531. echo "</tr>\n";
  532. unset( $GLOBALS['post'], $GLOBALS['comment'] );
  533. }
  534. /**
  535. * Generate and display row actions links.
  536. *
  537. * @since 4.3.0
  538. * @since 5.9.0 Renamed `$comment` to `$item` to match parent class for PHP 8 named parameter support.
  539. *
  540. * @global string $comment_status Status for the current listed comments.
  541. *
  542. * @param WP_Comment $item The comment object.
  543. * @param string $column_name Current column name.
  544. * @param string $primary Primary column name.
  545. * @return string Row actions output for comments. An empty string
  546. * if the current column is not the primary column,
  547. * or if the current user cannot edit the comment.
  548. */
  549. protected function handle_row_actions( $item, $column_name, $primary ) {
  550. global $comment_status;
  551. if ( $primary !== $column_name ) {
  552. return '';
  553. }
  554. if ( ! $this->user_can ) {
  555. return '';
  556. }
  557. // Restores the more descriptive, specific name for use within this method.
  558. $comment = $item;
  559. $the_comment_status = wp_get_comment_status( $comment );
  560. $out = '';
  561. $del_nonce = esc_html( '_wpnonce=' . wp_create_nonce( "delete-comment_$comment->comment_ID" ) );
  562. $approve_nonce = esc_html( '_wpnonce=' . wp_create_nonce( "approve-comment_$comment->comment_ID" ) );
  563. $url = "comment.php?c=$comment->comment_ID";
  564. $approve_url = esc_url( $url . "&action=approvecomment&$approve_nonce" );
  565. $unapprove_url = esc_url( $url . "&action=unapprovecomment&$approve_nonce" );
  566. $spam_url = esc_url( $url . "&action=spamcomment&$del_nonce" );
  567. $unspam_url = esc_url( $url . "&action=unspamcomment&$del_nonce" );
  568. $trash_url = esc_url( $url . "&action=trashcomment&$del_nonce" );
  569. $untrash_url = esc_url( $url . "&action=untrashcomment&$del_nonce" );
  570. $delete_url = esc_url( $url . "&action=deletecomment&$del_nonce" );
  571. // Preorder it: Approve | Reply | Quick Edit | Edit | Spam | Trash.
  572. $actions = array(
  573. 'approve' => '',
  574. 'unapprove' => '',
  575. 'reply' => '',
  576. 'quickedit' => '',
  577. 'edit' => '',
  578. 'spam' => '',
  579. 'unspam' => '',
  580. 'trash' => '',
  581. 'untrash' => '',
  582. 'delete' => '',
  583. );
  584. // Not looking at all comments.
  585. if ( $comment_status && 'all' !== $comment_status ) {
  586. if ( 'approved' === $the_comment_status ) {
  587. $actions['unapprove'] = sprintf(
  588. '<a href="%s" data-wp-lists="%s" class="vim-u vim-destructive aria-button-if-js" aria-label="%s">%s</a>',
  589. $unapprove_url,
  590. "delete:the-comment-list:comment-{$comment->comment_ID}:e7e7d3:action=dim-comment&amp;new=unapproved",
  591. esc_attr__( 'Unapprove this comment' ),
  592. __( 'Unapprove' )
  593. );
  594. } elseif ( 'unapproved' === $the_comment_status ) {
  595. $actions['approve'] = sprintf(
  596. '<a href="%s" data-wp-lists="%s" class="vim-a vim-destructive aria-button-if-js" aria-label="%s">%s</a>',
  597. $approve_url,
  598. "delete:the-comment-list:comment-{$comment->comment_ID}:e7e7d3:action=dim-comment&amp;new=approved",
  599. esc_attr__( 'Approve this comment' ),
  600. __( 'Approve' )
  601. );
  602. }
  603. } else {
  604. $actions['approve'] = sprintf(
  605. '<a href="%s" data-wp-lists="%s" class="vim-a aria-button-if-js" aria-label="%s">%s</a>',
  606. $approve_url,
  607. "dim:the-comment-list:comment-{$comment->comment_ID}:unapproved:e7e7d3:e7e7d3:new=approved",
  608. esc_attr__( 'Approve this comment' ),
  609. __( 'Approve' )
  610. );
  611. $actions['unapprove'] = sprintf(
  612. '<a href="%s" data-wp-lists="%s" class="vim-u aria-button-if-js" aria-label="%s">%s</a>',
  613. $unapprove_url,
  614. "dim:the-comment-list:comment-{$comment->comment_ID}:unapproved:e7e7d3:e7e7d3:new=unapproved",
  615. esc_attr__( 'Unapprove this comment' ),
  616. __( 'Unapprove' )
  617. );
  618. }
  619. if ( 'spam' !== $the_comment_status ) {
  620. $actions['spam'] = sprintf(
  621. '<a href="%s" data-wp-lists="%s" class="vim-s vim-destructive aria-button-if-js" aria-label="%s">%s</a>',
  622. $spam_url,
  623. "delete:the-comment-list:comment-{$comment->comment_ID}::spam=1",
  624. esc_attr__( 'Mark this comment as spam' ),
  625. /* translators: "Mark as spam" link. */
  626. _x( 'Spam', 'verb' )
  627. );
  628. } elseif ( 'spam' === $the_comment_status ) {
  629. $actions['unspam'] = sprintf(
  630. '<a href="%s" data-wp-lists="%s" class="vim-z vim-destructive aria-button-if-js" aria-label="%s">%s</a>',
  631. $unspam_url,
  632. "delete:the-comment-list:comment-{$comment->comment_ID}:66cc66:unspam=1",
  633. esc_attr__( 'Restore this comment from the spam' ),
  634. _x( 'Not Spam', 'comment' )
  635. );
  636. }
  637. if ( 'trash' === $the_comment_status ) {
  638. $actions['untrash'] = sprintf(
  639. '<a href="%s" data-wp-lists="%s" class="vim-z vim-destructive aria-button-if-js" aria-label="%s">%s</a>',
  640. $untrash_url,
  641. "delete:the-comment-list:comment-{$comment->comment_ID}:66cc66:untrash=1",
  642. esc_attr__( 'Restore this comment from the Trash' ),
  643. __( 'Restore' )
  644. );
  645. }
  646. if ( 'spam' === $the_comment_status || 'trash' === $the_comment_status || ! EMPTY_TRASH_DAYS ) {
  647. $actions['delete'] = sprintf(
  648. '<a href="%s" data-wp-lists="%s" class="delete vim-d vim-destructive aria-button-if-js" aria-label="%s">%s</a>',
  649. $delete_url,
  650. "delete:the-comment-list:comment-{$comment->comment_ID}::delete=1",
  651. esc_attr__( 'Delete this comment permanently' ),
  652. __( 'Delete Permanently' )
  653. );
  654. } else {
  655. $actions['trash'] = sprintf(
  656. '<a href="%s" data-wp-lists="%s" class="delete vim-d vim-destructive aria-button-if-js" aria-label="%s">%s</a>',
  657. $trash_url,
  658. "delete:the-comment-list:comment-{$comment->comment_ID}::trash=1",
  659. esc_attr__( 'Move this comment to the Trash' ),
  660. _x( 'Trash', 'verb' )
  661. );
  662. }
  663. if ( 'spam' !== $the_comment_status && 'trash' !== $the_comment_status ) {
  664. $actions['edit'] = sprintf(
  665. '<a href="%s" aria-label="%s">%s</a>',
  666. "comment.php?action=editcomment&amp;c={$comment->comment_ID}",
  667. esc_attr__( 'Edit this comment' ),
  668. __( 'Edit' )
  669. );
  670. $format = '<button type="button" data-comment-id="%d" data-post-id="%d" data-action="%s" class="%s button-link" aria-expanded="false" aria-label="%s">%s</button>';
  671. $actions['quickedit'] = sprintf(
  672. $format,
  673. $comment->comment_ID,
  674. $comment->comment_post_ID,
  675. 'edit',
  676. 'vim-q comment-inline',
  677. esc_attr__( 'Quick edit this comment inline' ),
  678. __( 'Quick&nbsp;Edit' )
  679. );
  680. $actions['reply'] = sprintf(
  681. $format,
  682. $comment->comment_ID,
  683. $comment->comment_post_ID,
  684. 'replyto',
  685. 'vim-r comment-inline',
  686. esc_attr__( 'Reply to this comment' ),
  687. __( 'Reply' )
  688. );
  689. }
  690. /** This filter is documented in wp-admin/includes/dashboard.php */
  691. $actions = apply_filters( 'comment_row_actions', array_filter( $actions ), $comment );
  692. $always_visible = false;
  693. $mode = get_user_setting( 'posts_list_mode', 'list' );
  694. if ( 'excerpt' === $mode ) {
  695. $always_visible = true;
  696. }
  697. $out .= '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
  698. $i = 0;
  699. foreach ( $actions as $action => $link ) {
  700. ++$i;
  701. if ( ( ( 'approve' === $action || 'unapprove' === $action ) && 2 === $i )
  702. || 1 === $i
  703. ) {
  704. $sep = '';
  705. } else {
  706. $sep = ' | ';
  707. }
  708. // Reply and quickedit need a hide-if-no-js span when not added with Ajax.
  709. if ( ( 'reply' === $action || 'quickedit' === $action ) && ! wp_doing_ajax() ) {
  710. $action .= ' hide-if-no-js';
  711. } elseif ( ( 'untrash' === $action && 'trash' === $the_comment_status )
  712. || ( 'unspam' === $action && 'spam' === $the_comment_status )
  713. ) {
  714. if ( '1' === get_comment_meta( $comment->comment_ID, '_wp_trash_meta_status', true ) ) {
  715. $action .= ' approve';
  716. } else {
  717. $action .= ' unapprove';
  718. }
  719. }
  720. $out .= "<span class='$action'>$sep$link</span>";
  721. }
  722. $out .= '</div>';
  723. $out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>';
  724. return $out;
  725. }
  726. /**
  727. * @since 5.9.0 Renamed `$comment` to `$item` to match parent class for PHP 8 named parameter support.
  728. *
  729. * @param WP_Comment $item The comment object.
  730. */
  731. public function column_cb( $item ) {
  732. // Restores the more descriptive, specific name for use within this method.
  733. $comment = $item;
  734. if ( $this->user_can ) {
  735. ?>
  736. <label class="screen-reader-text" for="cb-select-<?php echo $comment->comment_ID; ?>"><?php _e( 'Select comment' ); ?></label>
  737. <input id="cb-select-<?php echo $comment->comment_ID; ?>" type="checkbox" name="delete_comments[]" value="<?php echo $comment->comment_ID; ?>" />
  738. <?php
  739. }
  740. }
  741. /**
  742. * @param WP_Comment $comment The comment object.
  743. */
  744. public function column_comment( $comment ) {
  745. echo '<div class="comment-author">';
  746. $this->column_author( $comment );
  747. echo '</div>';
  748. if ( $comment->comment_parent ) {
  749. $parent = get_comment( $comment->comment_parent );
  750. if ( $parent ) {
  751. $parent_link = esc_url( get_comment_link( $parent ) );
  752. $name = get_comment_author( $parent );
  753. printf(
  754. /* translators: %s: Comment link. */
  755. __( 'In reply to %s.' ),
  756. '<a href="' . $parent_link . '">' . $name . '</a>'
  757. );
  758. }
  759. }
  760. comment_text( $comment );
  761. if ( $this->user_can ) {
  762. /** This filter is documented in wp-admin/includes/comment.php */
  763. $comment_content = apply_filters( 'comment_edit_pre', $comment->comment_content );
  764. ?>
  765. <div id="inline-<?php echo $comment->comment_ID; ?>" class="hidden">
  766. <textarea class="comment" rows="1" cols="1"><?php echo esc_textarea( $comment_content ); ?></textarea>
  767. <div class="author-email"><?php echo esc_attr( $comment->comment_author_email ); ?></div>
  768. <div class="author"><?php echo esc_attr( $comment->comment_author ); ?></div>
  769. <div class="author-url"><?php echo esc_url( $comment->comment_author_url ); ?></div>
  770. <div class="comment_status"><?php echo $comment->comment_approved; ?></div>
  771. </div>
  772. <?php
  773. }
  774. }
  775. /**
  776. * @global string $comment_status
  777. *
  778. * @param WP_Comment $comment The comment object.
  779. */
  780. public function column_author( $comment ) {
  781. global $comment_status;
  782. $author_url = get_comment_author_url( $comment );
  783. $author_url_display = untrailingslashit( preg_replace( '|^http(s)?://(www\.)?|i', '', $author_url ) );
  784. if ( strlen( $author_url_display ) > 50 ) {
  785. $author_url_display = wp_html_excerpt( $author_url_display, 49, '&hellip;' );
  786. }
  787. echo '<strong>';
  788. comment_author( $comment );
  789. echo '</strong><br />';
  790. if ( ! empty( $author_url_display ) ) {
  791. // Print link to author URL, and disallow referrer information (without using target="_blank").
  792. printf(
  793. '<a href="%s" rel="noopener noreferrer">%s</a><br />',
  794. esc_url( $author_url ),
  795. esc_html( $author_url_display )
  796. );
  797. }
  798. if ( $this->user_can ) {
  799. if ( ! empty( $comment->comment_author_email ) ) {
  800. /** This filter is documented in wp-includes/comment-template.php */
  801. $email = apply_filters( 'comment_email', $comment->comment_author_email, $comment );
  802. if ( ! empty( $email ) && '@' !== $email ) {
  803. printf( '<a href="%1$s">%2$s</a><br />', esc_url( 'mailto:' . $email ), esc_html( $email ) );
  804. }
  805. }
  806. $author_ip = get_comment_author_IP( $comment );
  807. if ( $author_ip ) {
  808. $author_ip_url = add_query_arg(
  809. array(
  810. 's' => $author_ip,
  811. 'mode' => 'detail',
  812. ),
  813. admin_url( 'edit-comments.php' )
  814. );
  815. if ( 'spam' === $comment_status ) {
  816. $author_ip_url = add_query_arg( 'comment_status', 'spam', $author_ip_url );
  817. }
  818. printf( '<a href="%1$s">%2$s</a>', esc_url( $author_ip_url ), esc_html( $author_ip ) );
  819. }
  820. }
  821. }
  822. /**
  823. * @param WP_Comment $comment The comment object.
  824. */
  825. public function column_date( $comment ) {
  826. $submitted = sprintf(
  827. /* translators: 1: Comment date, 2: Comment time. */
  828. __( '%1$s at %2$s' ),
  829. /* translators: Comment date format. See https://www.php.net/manual/datetime.format.php */
  830. get_comment_date( __( 'Y/m/d' ), $comment ),
  831. /* translators: Comment time format. See https://www.php.net/manual/datetime.format.php */
  832. get_comment_date( __( 'g:i a' ), $comment )
  833. );
  834. echo '<div class="submitted-on">';
  835. if ( 'approved' === wp_get_comment_status( $comment ) && ! empty( $comment->comment_post_ID ) ) {
  836. printf(
  837. '<a href="%s">%s</a>',
  838. esc_url( get_comment_link( $comment ) ),
  839. $submitted
  840. );
  841. } else {
  842. echo $submitted;
  843. }
  844. echo '</div>';
  845. }
  846. /**
  847. * @param WP_Comment $comment The comment object.
  848. */
  849. public function column_response( $comment ) {
  850. $post = get_post();
  851. if ( ! $post ) {
  852. return;
  853. }
  854. if ( isset( $this->pending_count[ $post->ID ] ) ) {
  855. $pending_comments = $this->pending_count[ $post->ID ];
  856. } else {
  857. $_pending_count_temp = get_pending_comments_num( array( $post->ID ) );
  858. $pending_comments = $_pending_count_temp[ $post->ID ];
  859. $this->pending_count[ $post->ID ] = $pending_comments;
  860. }
  861. if ( current_user_can( 'edit_post', $post->ID ) ) {
  862. $post_link = "<a href='" . get_edit_post_link( $post->ID ) . "' class='comments-edit-item-link'>";
  863. $post_link .= esc_html( get_the_title( $post->ID ) ) . '</a>';
  864. } else {
  865. $post_link = esc_html( get_the_title( $post->ID ) );
  866. }
  867. echo '<div class="response-links">';
  868. if ( 'attachment' === $post->post_type ) {
  869. $thumb = wp_get_attachment_image( $post->ID, array( 80, 60 ), true );
  870. if ( $thumb ) {
  871. echo $thumb;
  872. }
  873. }
  874. echo $post_link;
  875. $post_type_object = get_post_type_object( $post->post_type );
  876. echo "<a href='" . get_permalink( $post->ID ) . "' class='comments-view-item-link'>" . $post_type_object->labels->view_item . '</a>';
  877. echo '<span class="post-com-count-wrapper post-com-count-', $post->ID, '">';
  878. $this->comments_bubble( $post->ID, $pending_comments );
  879. echo '</span> ';
  880. echo '</div>';
  881. }
  882. /**
  883. * @since 5.9.0 Renamed `$comment` to `$item` to match parent class for PHP 8 named parameter support.
  884. *
  885. * @param WP_Comment $item The comment object.
  886. * @param string $column_name The custom column's name.
  887. */
  888. public function column_default( $item, $column_name ) {
  889. /**
  890. * Fires when the default column output is displayed for a single row.
  891. *
  892. * @since 2.8.0
  893. *
  894. * @param string $column_name The custom column's name.
  895. * @param string $comment_id The comment ID as a numeric string.
  896. */
  897. do_action( 'manage_comments_custom_column', $column_name, $item->comment_ID );
  898. }
  899. }