'entry', 'plural' => 'entries', 'ajax' => false, ) ); } /** * Admin URL for the entries list. * * @param array $args Extra query arguments. * @return string */ public static function url( array $args = array() ) { // add_query_arg() expects encoded values: a search for "tom & jerry" or "C++" // must stay one value on every generated link. return add_query_arg( urlencode_deep( array_merge( array( 'post_type' => 'accessible_form', 'page' => self::SLUG, ), $args ) ), // The screen sits under the Forms menu (edit.php?post_type=...). WordPress // cannot resolve admin.php with a post_type for such a screen. admin_url( 'edit.php' ) ); } /** * Column definitions. * * @return array */ public function get_columns() { $columns = array( 'cb' => '', 'summary' => __( 'Submission', 'accessible-forms-by-accessible-org' ), 'form' => __( 'Form', 'accessible-forms-by-accessible-org' ), 'created_at' => __( 'Received', 'accessible-forms-by-accessible-org' ), 'delivery' => __( 'Notification', 'accessible-forms-by-accessible-org' ), ); // Add-ons may add columns, such as follow-up progress. return (array) Extensions::filter( 'accessible_forms_entry_columns', $columns ); } /** * Sortable columns. * * @return array */ protected function get_sortable_columns() { return array( 'created_at' => array( 'created_at', true ), ); } /** * Bulk actions. * * @return array */ protected function get_bulk_actions() { $actions = array(); if ( current_user_can( 'manage_accessible_form_entries' ) ) { $actions = array( 'read' => __( 'Mark as read', 'accessible-forms-by-accessible-org' ), 'unread' => __( 'Mark as unread', 'accessible-forms-by-accessible-org' ), 'spam' => __( 'Mark as spam', 'accessible-forms-by-accessible-org' ), 'ham' => __( 'Accept / restore without notifying', 'accessible-forms-by-accessible-org' ), ); } if ( current_user_can( 'delete_accessible_form_entries' ) ) { $actions['trash'] = __( 'Move to Trash', 'accessible-forms-by-accessible-org' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only list view state; actions verify their own nonce. if ( 'trash' === sanitize_key( wp_unslash( $_GET['status'] ?? '' ) ) ) { $actions['delete'] = __( 'Delete permanently', 'accessible-forms-by-accessible-org' ); } } return (array) Extensions::filter( 'accessible_forms_entry_bulk_actions', $actions ); } /** * Status filter links above the table. * * @return array */ protected function get_views() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display only. $current = isset( $_GET['status'] ) ? sanitize_key( wp_unslash( $_GET['status'] ) ) : ''; // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only list filters. $filters = array( 'form_id' => isset( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : 0, 'search' => isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '', ); // phpcs:enable WordPress.Security.NonceVerification.Recommended $filters = array_merge( $filters, self::extra_filters() ); $counts = array(); foreach ( array( '', 'received', 'unread', 'review', 'spam', 'trash' ) as $status ) { $counts[ $status ] = Entries::count( Capabilities::query( array_merge( $filters, array( 'status' => $status ) ) ) ); } $labels = array( '' => __( 'All', 'accessible-forms-by-accessible-org' ), 'received' => __( 'Received', 'accessible-forms-by-accessible-org' ), 'unread' => __( 'Unread', 'accessible-forms-by-accessible-org' ), 'spam' => __( 'Spam', 'accessible-forms-by-accessible-org' ), 'review' => __( 'Needs review', 'accessible-forms-by-accessible-org' ), 'trash' => __( 'Trash', 'accessible-forms-by-accessible-org' ), ); $icons = array( '' => 'grid', 'received' => 'inbox', 'unread' => 'mail', 'spam' => 'shield', 'review' => 'alert', 'trash' => 'trash', ); $views = array(); foreach ( $labels as $key => $label ) { $views[ $key ? $key : 'all' ] = sprintf( '%s%s (%s)', esc_url( self::url( array_merge( self::extra_filters(), array( 'status' => $key, 'form_id' => $filters['form_id'], 's' => $filters['search'], ) ) ) ), $current === $key ? ' class="current" aria-current="page"' : '', Navigation::icon( $icons[ $key ] ), esc_html( $label ), esc_html( number_format_i18n( $counts[ $key ] ) ) ); } return $views; } /** * Row checkbox. * * @param array $item Entry row. * @return string */ protected function column_cb( $item ) { return sprintf( '' . '', esc_attr( (string) $item['id'] ), esc_html( sprintf( /* translators: %s: entry ID. */ __( 'Select entry %s', 'accessible-forms-by-accessible-org' ), $item['id'] ) ) ); } /** * The primary column: a readable précis plus row actions. * * @param array $item Entry row. * @return string */ protected function column_summary( $item ) { $parts = array(); foreach ( Entries::display_values( $item ) as $key => $value ) { $value = is_array( $value ) ? implode( ', ', $value ) : (string) $value; if ( '' === trim( $value ) ) { continue; } $parts[] = wp_trim_words( $value, count( $parts ) ? 12 : 8 ); if ( count( $parts ) >= 3 ) { break; } } // The first answer (usually a name) is the link; the next ones give context. $first = $parts ? array_shift( $parts ) : __( '(no values)', 'accessible-forms-by-accessible-org' ); $url = EntryView::url( $item['id'], Inbox::current() ); $unread = empty( $item['is_read'] ) && 'spam' !== $item['status']; /* translators: 1: visible response summary, 2: entry ID. */ $name = sprintf( __( '%1$s — entry %2$s', 'accessible-forms-by-accessible-org' ), $first, $item['id'] ); $title = sprintf( '%s', esc_attr( $name ), esc_url( $url ), esc_html( $first ) ); if ( $unread ) { $title .= ' ' . esc_html__( 'Unread', 'accessible-forms-by-accessible-org' ) . ''; } if ( $parts ) { $title .= '' . esc_html( implode( ' · ', $parts ) ) . ''; } return $title; } /** Render row actions once; row_actions() also supplies the native mobile toggle. */ protected function handle_row_actions( $item, $column_name, $primary ) { if ( $column_name !== $primary ) { return ''; } $title = $item['snapshot']['title'] ?? get_the_title( $item['form_id'] ); /* translators: 1: entry ID, 2: form title. */ $name = sprintf( __( 'View entry %1$s for %2$s', 'accessible-forms-by-accessible-org' ), $item['id'], wp_strip_all_tags( $title ) ?: __( '(deleted form)', 'accessible-forms-by-accessible-org' ) ); $actions = array( 'view' => sprintf( '%s', esc_attr( $name ), esc_url( EntryView::url( $item['id'], Inbox::current() ) ), Navigation::label( 'eye', __( 'View', 'accessible-forms-by-accessible-org' ) ) ), ); return $this->row_actions( $actions, true ); } /** * Default column renderer. * * @param array $item Entry row. * @param string $column Column key. * @return string */ protected function column_default( $item, $column ) { switch ( $column ) { case 'created_at': $timestamp = \AccessibleForms\EntryTime::timestamp( $item ); if ( null === $timestamp ) { return esc_html( \AccessibleForms\EntryTime::display( $item ) ); } return esc_html( sprintf( /* translators: %s: human-readable time difference. */ __( '%s ago', 'accessible-forms-by-accessible-org' ), human_time_diff( $timestamp, time() ) ) ) . ''; case 'form': $title = $item['snapshot']['title'] ?? get_the_title( $item['form_id'] ); return esc_html( $title ? $title : __( '(deleted form)', 'accessible-forms-by-accessible-org' ) ); case 'delivery': if ( 'review' === $item['status'] ) { return '' . esc_html__( 'Needs review', 'accessible-forms-by-accessible-org' ) . ''; } if ( 'queued' === $item['mail_status'] ) { return '' . esc_html__( 'Queued', 'accessible-forms-by-accessible-org' ) . ''; } if ( 'uncertain' === $item['mail_status'] ) { return '' . esc_html__( 'Delivery uncertain', 'accessible-forms-by-accessible-org' ) . ''; } if ( 'spam' === $item['status'] ) { $labels = Spam::reason_labels() + array( 'manual' => __( 'Marked by hand', 'accessible-forms-by-accessible-org' ) ); $reason = $item['spam_reason'] ? $labels[ $item['spam_reason'] ] ?? $item['spam_reason'] : ''; return '' . esc_html__( 'Spam', 'accessible-forms-by-accessible-org' ) . '' . ( $reason ? '' : '' ); } if ( 'failed' === $item['mail_status'] ) { return '' . esc_html__( 'Email failed', 'accessible-forms-by-accessible-org' ) . '' . ''; } if ( 'sent' === $item['mail_status'] ) { return '' . esc_html__( 'Accepted by mail service', 'accessible-forms-by-accessible-org' ) . ''; } if ( 'skipped' === $item['mail_status'] ) { return '' . esc_html__( 'Not sent', 'accessible-forms-by-accessible-org' ) . ''; } return '' . esc_html__( 'Unknown', 'accessible-forms-by-accessible-org' ) . ''; } // Add-on columns render themselves. return (string) Extensions::filter( 'accessible_forms_entry_column', '', $column, $item ); } /** * Highlight unread rows. * * @param array $item Entry row. */ public function single_row( $item ) { $classes = empty( $item['is_read'] ) && 'spam' !== $item['status'] ? 'af-unread' : ''; echo '
' . esc_html( sprintf( /* translators: %d: number of days. */ _n( 'Messages here are deleted automatically %d day after they arrive.', 'Messages here are deleted automatically %d days after they arrive.', $days, 'accessible-forms-by-accessible-org' ), $days ) ) . ' ' . esc_html( sprintf( /* translators: %d: number of messages. */ _n( '%d will be deleted in the next 7 days.', '%d will be deleted in the next 7 days.', $counts['expiring'], 'accessible-forms-by-accessible-org' ), $counts['expiring'] ) ) . ' '; if ( 'review' === $status ) { esc_html_e( 'Open a message and accept it to keep it with your received submissions.', 'accessible-forms-by-accessible-org' ); } if ( current_user_can( 'manage_accessible_forms_settings' ) ) { echo ' ' . esc_html__( 'Change how long these messages are kept', 'accessible-forms-by-accessible-org' ) . ''; } echo '
' . esc_html__( 'Try a broader date range, a different search, or clear your filters.', 'accessible-forms-by-accessible-org' ) . '
' . esc_html__( 'Clear filters', 'accessible-forms-by-accessible-org' ) . ''; return; } $empty = array( 'unread' => array( __( 'No unread submissions', 'accessible-forms-by-accessible-org' ), __( 'Received submissions you have not opened appear here. Use Received to revisit messages you have already read.', 'accessible-forms-by-accessible-org' ) ), 'spam' => array( __( 'No spam to review', 'accessible-forms-by-accessible-org' ), __( 'Messages marked as spam will appear here. You can review and accept them before their retention period ends.', 'accessible-forms-by-accessible-org' ) ), 'review' => array( __( 'No messages need review', 'accessible-forms-by-accessible-org' ), __( 'Messages with incomplete verification will appear here for you to review.', 'accessible-forms-by-accessible-org' ) ), 'trash' => array( __( 'Trash is empty', 'accessible-forms-by-accessible-org' ), __( 'Entries moved to Trash will appear here and can be restored before they are deleted.', 'accessible-forms-by-accessible-org' ) ), ); if ( isset( $empty[ $status ] ) ) { echo '' . esc_html( $empty[ $status ][0] ) . '' . esc_html( $empty[ $status ][1] ) . '
'; return; } echo '' . esc_html__( 'No submissions yet', 'accessible-forms-by-accessible-org' ) . '' . esc_html__( 'Publish and place your form, then send a test submission from its page. Your stored message will appear here.', 'accessible-forms-by-accessible-org' ) . '
'; $form_id = $filters['form_id']; if ( $form_id && PlacementScreen::can_view() && \AccessibleForms\Schema::get( $form_id ) ) { echo '' . esc_html__( 'Choose where this form appears', 'accessible-forms-by-accessible-org' ) . '
'; } echo '' . esc_html__( 'Manage your forms', 'accessible-forms-by-accessible-org' ) . '
'; } /** Filter summaries are server-rendered links and also work without JavaScript. */ private function filter_summary() { $filters = Inbox::url_filters( Inbox::current() ); $names = array( 'status' => __( 'Status', 'accessible-forms-by-accessible-org' ), 'form_id' => __( 'Form', 'accessible-forms-by-accessible-org' ), 's' => __( 'Search', 'accessible-forms-by-accessible-org' ), 'date_from' => __( 'From', 'accessible-forms-by-accessible-org' ), 'date_to' => __( 'To', 'accessible-forms-by-accessible-org' ), 'mail_status' => __( 'Notification', 'accessible-forms-by-accessible-org' ), 'workflow' => __( 'Progress', 'accessible-forms-by-accessible-org' ), 'assignee' => __( 'Assignee', 'accessible-forms-by-accessible-org' ) ); /* translators: %s: localized number of matching entries. */ echo '' . esc_html( sprintf( _n( '%s entry matches', '%s entries match', $this->get_pagination_arg( 'total_items' ), 'accessible-forms-by-accessible-org' ), number_format_i18n( $this->get_pagination_arg( 'total_items' ) ) ) ) . '
' . esc_html__( 'Review messages from all your forms. Search and filter the entries you need.', 'accessible-forms-by-accessible-org' ) . '
'; if ( 'deleted' === $notice ) { echo '' . esc_html__( 'Entry deleted.', 'accessible-forms-by-accessible-org' ) . '
' . esc_html__( 'Selected entries updated.', 'accessible-forms-by-accessible-org' ) . '
' . esc_html__( 'Some historical entry dates have an unconfirmed time zone. Their saved calendar values remain visible, but chronological sorting is approximate until their original zone is confirmed.', 'accessible-forms-by-accessible-org' ); if ( current_user_can( 'manage_accessible_forms_settings' ) ) { echo ' ' . esc_html__( 'Review historical dates', 'accessible-forms-by-accessible-org' ) . ''; } echo '