'post', 'plural' => 'posts', 'ajax' => false, ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only query argument for view selection. $this->current_view = ( isset( $_GET['view'] ) && 'excluded' === sanitize_key( wp_unslash( $_GET['view'] ) ) ) ? 'excluded' : 'stale'; } /** * Get views links (All Outdated / Excluded). * * @return array */ protected function get_views() { $views = array(); // Count stale items. $stale_query = new OCAP_Query( array( 'view' => 'stale' ) ); $stale_count = $stale_query->get_count(); // Count excluded items. $excluded_query = new OCAP_Query( array( 'view' => 'excluded' ) ); $excluded_count = $excluded_query->get_count(); $current_view = $this->current_view; $stale_class = ( 'stale' === $current_view ) ? ' class="current"' : ''; $excluded_class = ( 'excluded' === $current_view ) ? ' class="current"' : ''; $views['stale'] = sprintf( '%s (%s)', esc_url( ocap_get_report_url( array( 'view' => 'stale' ) ) ), $stale_class, esc_html__( 'All Outdated', 'contentcortex' ), number_format_i18n( $stale_count ) ); $views['excluded'] = sprintf( '%s (%s)', esc_url( ocap_get_report_url( array( 'view' => 'excluded' ) ) ), $excluded_class, esc_html__( 'Excluded items', 'contentcortex' ), number_format_i18n( $excluded_count ) ); return $views; } /** * Define columns. * * @return array */ public function get_columns() { return array( 'cb' => '', 'title' => __( 'Title', 'contentcortex' ), 'type' => __( 'Post Type', 'contentcortex' ), 'date' => ( 'published' === ocap_get_setting( 'date_basis', 'modified' ) ) ? __( 'Published', 'contentcortex' ) : __( 'Last Updated', 'contentcortex' ), 'days' => __( 'Days Since Update', 'contentcortex' ), 'limit' => __( 'Day Limit', 'contentcortex' ), 'author' => __( 'Author', 'contentcortex' ), ); } /** * Define sortable columns. * * @return array */ public function get_sortable_columns() { return array( 'title' => array( 'title', false ), 'type' => array( 'type', false ), 'date' => array( 'modified', false ), 'days' => array( 'days', false ), 'author' => array( 'author', false ), ); } /** * Define bulk actions. * * @return array */ protected function get_bulk_actions() { if ( 'excluded' === $this->current_view ) { return array( 'include' => __( 'Include again', 'contentcortex' ), ); } return array( 'mark_reviewed' => __( 'Mark as reviewed', 'contentcortex' ), // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- False positive: Bulk action identifier, not get_posts() parameter. 'exclude' => __( 'Exclude from report', 'contentcortex' ), ); } /** * Checkbox column. * * @param array $item Current row item. * @return string */ protected function column_cb( $item ) { return sprintf( '', absint( $item['ID'] ) ); } /** * Title column with row actions. * * @param array $item Current row item. * @return string */ protected function column_title( $item ) { $post_id = absint( $item['ID'] ); $edit_link = get_edit_post_link( $post_id ); $title = esc_html( $item['title'] ); if ( $edit_link && current_user_can( 'edit_post', $post_id ) ) { $title_output = sprintf( '%s', esc_url( $edit_link ), $title ); } else { $title_output = sprintf( '%s', $title ); } $actions = array(); if ( current_user_can( 'edit_post', $post_id ) ) { if ( $edit_link ) { $actions['edit'] = sprintf( '%s', esc_url( $edit_link ), esc_html__( 'Edit', 'contentcortex' ) ); } $actions['view'] = sprintf( '%s', esc_url( get_permalink( $post_id ) ), esc_html__( 'View', 'contentcortex' ) ); $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $redirect = rawurlencode( $request_uri ); if ( 'excluded' === $this->current_view ) { $include_url = wp_nonce_url( admin_url( 'admin-post.php?action=ocap_include_post&post_id=' . $post_id . '&redirect_to=' . $redirect ), 'ocap_include_post_' . $post_id ); $actions['include'] = sprintf( '%s', esc_url( $include_url ), esc_html__( 'Include again', 'contentcortex' ) ); } else { $review_url = wp_nonce_url( admin_url( 'admin-post.php?action=ocap_mark_reviewed&post_id=' . $post_id . '&redirect_to=' . $redirect ), 'ocap_mark_reviewed_' . $post_id ); $exclude_url = wp_nonce_url( admin_url( 'admin-post.php?action=ocap_exclude_post&post_id=' . $post_id . '&redirect_to=' . $redirect ), 'ocap_exclude_post_' . $post_id ); $actions['mark_reviewed'] = sprintf( '%s', esc_url( $review_url ), esc_html__( 'Mark as reviewed', 'contentcortex' ) ); // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- False positive: Row action array key, not WP_Query parameter. $actions['exclude'] = sprintf( '%s', esc_url( $exclude_url ), esc_html__( 'Exclude', 'contentcortex' ) ); } } return $title_output . $this->row_actions( $actions ); } /** * Post type column. * * @param array $item Current row item. * @return string */ protected function column_type( $item ) { return sprintf( '%s', esc_html( $item['post_type_label'] ) ); } /** * Date column (Last updated or published date). * * @param array $item Current row item. * @return string */ protected function column_date( $item ) { $basis = ocap_get_setting( 'date_basis', 'modified' ); $date_gmt = ( 'published' === $basis ) ? $item['post_date_gmt'] : $item['post_modified_gmt']; $timestamp = strtotime( $date_gmt . ' GMT' ); if ( ! $timestamp ) { return '—'; } $date_formatted = get_date_from_gmt( $date_gmt, get_option( 'date_format' ) ); $time_diff = human_time_diff( $timestamp, time() ); $output = sprintf( '%s
%s', esc_html( $date_formatted ), sprintf( /* translators: %s: Human-readable time difference e.g. 95 days */ esc_html__( '%s ago', 'contentcortex' ), esc_html( $time_diff ) ) ); if ( ! empty( $item['last_reviewed'] ) ) { $reviewed_ts = strtotime( $item['last_reviewed'] . ' GMT' ); if ( $reviewed_ts ) { $output .= sprintf( '
%s: %s', esc_html__( 'Reviewed', 'contentcortex' ), esc_html( get_date_from_gmt( $item['last_reviewed'], get_option( 'date_format' ) ) ) ); } } return $output; } /** * Days since update column with colored badge. * * @param array $item Current row item. * @return string */ protected function column_days( $item ) { $days = absint( $item['days_since_update'] ); $severity = ( 'high' === $item['severity'] ) ? 'ocap-badge-red' : 'ocap-badge-amber'; return sprintf( '%s', esc_attr( $severity ), sprintf( /* translators: %s: number of days */ _n( '%s day', '%s days', $days, 'contentcortex' ), number_format_i18n( $days ) ) ); } /** * Day limit column. * * @param array $item Current row item. * @return string */ protected function column_limit( $item ) { return sprintf( /* translators: %s: number of days */ esc_html__( '%s days', 'contentcortex' ), number_format_i18n( absint( $item['day_limit'] ) ) ); } /** * Author column. * * @param array $item Current row item. * @return string */ protected function column_author( $item ) { return esc_html( $item['author_name'] ? $item['author_name'] : __( 'Unknown', 'contentcortex' ) ); } /** * Extra controls between bulk actions and pagination (filters). * * @param string $which Location: 'top' or 'bottom'. */ protected function extra_tablenav( $which ) { if ( 'top' !== $which ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only filter parameters for list table view. $current_type = isset( $_GET['post_type_filter'] ) ? sanitize_key( wp_unslash( $_GET['post_type_filter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only filter parameters for list table view. $current_author = isset( $_GET['author_filter'] ) ? absint( $_GET['author_filter'] ) : 0; $settings = ocap_get_settings(); $enabled_types = isset( $settings['post_types'] ) ? $settings['post_types'] : array(); echo '
'; // Post type filter dropdown. echo ''; // Author filter dropdown. $authors = get_users( array( 'capability' => array( 'edit_posts' ), 'orderby' => 'display_name', 'order' => 'ASC', 'number' => 100, ) ); if ( ! empty( $authors ) ) { echo ''; } submit_button( __( 'Filter', 'contentcortex' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) ); // CSV Export button. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only search query argument for export link. $search_query = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; $export_url = wp_nonce_url( admin_url( 'admin-post.php?action=ocap_export_csv' . ( ! empty( $current_type ) ? '&post_type_filter=' . $current_type : '' ) . ( ! empty( $current_author ) ? '&author_filter=' . $current_author : '' ) . ( ! empty( $search_query ) ? '&s=' . rawurlencode( $search_query ) : '' ) . ( 'excluded' === $this->current_view ? '&view=excluded' : '' ) ), 'ocap_export_csv_nonce' ); printf( '%s', esc_url( $export_url ), esc_html__( 'Export CSV', 'contentcortex' ) ); echo '
'; } /** * Default fallback for columns. * * @param array $item Current row item. * @param string $column_name Column name. * @return string */ protected function column_default( $item, $column_name ) { return isset( $item[ $column_name ] ) ? esc_html( $item[ $column_name ] ) : ''; } /** * Prepare table items for display. */ public function prepare_items() { $per_page = $this->get_items_per_page( 'ocap_posts_per_page', 20 ); $current_page = $this->get_pagenum(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only table filter parameter. $filter_type = isset( $_GET['post_type_filter'] ) ? sanitize_key( wp_unslash( $_GET['post_type_filter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only table filter parameter. $filter_author = isset( $_GET['author_filter'] ) ? absint( $_GET['author_filter'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only table search parameter. $search = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only table orderby parameter. $orderby = isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : 'days'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only table order parameter. $order = isset( $_GET['order'] ) ? sanitize_key( wp_unslash( $_GET['order'] ) ) : 'DESC'; $query_args = array( 'view' => $this->current_view, 'post_type' => $filter_type, 'author' => $filter_author, 'search' => $search, 'orderby' => $orderby, 'order' => $order, 'per_page' => $per_page, 'paged' => $current_page, ); $this->query_instance = new OCAP_Query( $query_args ); $this->items = $this->query_instance->get_results(); $total_items = $this->query_instance->get_total_items(); $this->set_pagination_args( array( 'total_items' => $total_items, 'per_page' => $per_page, 'total_pages' => ceil( $total_items / $per_page ), ) ); } /** * Get query instance used for items. * * @return OCAP_Query|null */ public function get_query_instance() { return $this->query_instance; } /** * Message to show when no items are found. */ public function no_items() { if ( 'excluded' === $this->current_view ) { esc_html_e( 'No excluded content found.', 'contentcortex' ); } else { esc_html_e( 'Great job! No outdated content found matching your criteria.', 'contentcortex' ); } } }