array( 'name' => __( 'Form Submissions', 'submitdock' ), 'singular_name' => __( 'Submission', 'submitdock' ), 'menu_name' => __( 'Form Submissions', 'submitdock' ), 'all_items' => __( 'Submissions', 'submitdock' ), 'edit_item' => __( 'Submission', 'submitdock' ), 'view_item' => __( 'View submission', 'submitdock' ), 'search_items' => __( 'Search submissions', 'submitdock' ), 'not_found' => __( 'No submissions yet.', 'submitdock' ), 'not_found_in_trash' => __( 'No submissions in the Trash.', 'submitdock' ), ), 'public' => false, 'publicly_queryable' => false, 'exclude_from_search' => true, 'show_ui' => true, 'show_in_menu' => true, 'show_in_rest' => false, 'rewrite' => false, 'query_var' => false, 'menu_icon' => 'dashicons-email-alt2', 'capability_type' => 'post', 'capabilities' => array( 'create_posts' => 'do_not_allow' ), 'map_meta_cap' => true, 'supports' => array( 'title' ), ) ); } /** * Saves a submission and returns its ID. * * @param array $fields Sanitized form fields. * @param array $visitor Visitor details. * @param string $form Form identifier, or an empty string. * @param string $email Sender email address, or an empty string. * @return int|WP_Error */ public static function create( array $fields, array $visitor, $form, $email ) { $post_id = wp_insert_post( array( 'post_type' => self::POST_TYPE, 'post_status' => 'private', 'post_title' => self::title_for( $fields, $form ), 'meta_input' => array( self::META_FIELDS => $fields, self::META_VISITOR => $visitor, self::META_STATUS => 'pending', self::META_FORM => $form, self::META_EMAIL => strtolower( $email ), ), ), true ); return $post_id; } /** * Builds a readable title from the sender's name or email. * * @param array $fields Form fields. * @param string $form Form identifier. * @return string */ private static function title_for( array $fields, $form ) { $sender = self::sender_name( $fields ); if ( '' === $sender && isset( $fields['email'] ) && is_string( $fields['email'] ) ) { $sender = $fields['email']; } $parts = array_filter( array( $form, $sender ) ); return $parts ? implode( ' — ', $parts ) : __( 'Form submission', 'submitdock' ); } /** * Finds the sender's name in common field names. * * @param array $fields Form fields. * @return string */ public static function sender_name( array $fields ) { foreach ( array( 'name', 'full_name', 'fullname', 'your_name', 'your-name' ) as $key ) { if ( isset( $fields[ $key ] ) && is_string( $fields[ $key ] ) && '' !== trim( $fields[ $key ] ) ) { return trim( $fields[ $key ] ); } } $first = isset( $fields['first_name'] ) && is_string( $fields['first_name'] ) ? $fields['first_name'] : ''; $last = isset( $fields['last_name'] ) && is_string( $fields['last_name'] ) ? $fields['last_name'] : ''; return trim( $first . ' ' . $last ); } /** * Formats a stored value as text. * * @param mixed $value Stored value. * @return string */ public static function format_value( $value ) { if ( is_array( $value ) ) { $json = wp_json_encode( $value, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); return is_string( $json ) ? $json : ''; } if ( is_bool( $value ) ) { return $value ? __( 'Yes', 'submitdock' ) : __( 'No', 'submitdock' ); } if ( null === $value ) { return '—'; } return is_scalar( $value ) ? (string) $value : ''; } /** * Turns a field key such as project_type into a label. * * @param string $key Field key. * @return string */ public static function label_for( $key ) { return ucfirst( str_replace( array( '_', '-' ), ' ', (string) $key ) ); } /** * Human-readable email status. * * @param string $status Stored status. * @return string */ public static function status_label( $status ) { $labels = array( 'pending' => __( 'Pending', 'submitdock' ), 'sent' => __( 'Sent', 'submitdock' ), 'failed' => __( 'Failed, submission saved', 'submitdock' ), 'disabled' => __( 'Notifications off', 'submitdock' ), ); return $labels[ $status ] ?? __( 'Unknown', 'submitdock' ); } /** * Notification status with the recipient, such as "Sent to owner@example.com". * * @param int $post_id Submission ID. * @return string */ public static function delivery_label( $post_id ) { $status = (string) get_post_meta( $post_id, self::META_STATUS, true ); $to = (string) get_post_meta( $post_id, self::META_SENT_TO, true ); if ( '' === $to ) { return self::status_label( $status ); } if ( 'sent' === $status ) { /* translators: %s: recipient email addresses. */ return sprintf( __( 'Sent to %s', 'submitdock' ), $to ); } if ( 'failed' === $status ) { /* translators: %s: recipient email addresses. */ return sprintf( __( 'Failed to send to %s, submission saved', 'submitdock' ), $to ); } return self::status_label( $status ); } /** * Adds a View link and removes Quick Edit, which does not apply to submissions. * * @param array $actions Row actions. * @param WP_Post $post Submission. * @return array */ public static function row_actions( $actions, $post ) { if ( self::POST_TYPE !== $post->post_type ) { return $actions; } unset( $actions['inline hide-if-no-js'], $actions['edit'] ); $link = get_edit_post_link( $post->ID ); if ( $link ) { $actions = array_merge( array( 'view' => sprintf( '%3$s', esc_url( $link ), /* translators: %s: submission title. */ esc_attr( sprintf( __( 'View “%s”', 'submitdock' ), get_the_title( $post ) ) ), esc_html__( 'View', 'submitdock' ) ), ), $actions ); } return $actions; } /** * List table columns. * * @param array $columns Default columns. * @return array */ public static function columns( $columns ) { return array( 'cb' => $columns['cb'], 'title' => __( 'Submission', 'submitdock' ), 'submitdock_form' => __( 'Form', 'submitdock' ), 'submitdock_email' => __( 'Email', 'submitdock' ), 'submitdock_origin' => __( 'Website', 'submitdock' ), 'submitdock_delivery' => __( 'Notification', 'submitdock' ), 'date' => __( 'Received', 'submitdock' ), ); } /** * Renders a custom column. * * @param string $column Column key. * @param int $post_id Submission ID. */ public static function render_column( $column, $post_id ) { if ( 'submitdock_form' === $column ) { $form = (string) get_post_meta( $post_id, self::META_FORM, true ); echo esc_html( '' !== $form ? $form : '—' ); } elseif ( 'submitdock_email' === $column ) { $email = (string) get_post_meta( $post_id, self::META_EMAIL, true ); if ( is_email( $email ) ) { printf( '%2$s', esc_url( 'mailto:' . $email ), esc_html( $email ) ); } else { echo '—'; } } elseif ( 'submitdock_origin' === $column ) { $visitor = get_post_meta( $post_id, self::META_VISITOR, true ); $origin = is_array( $visitor ) && ! empty( $visitor['origin'] ) ? (string) $visitor['origin'] : ''; echo esc_html( '' !== $origin ? wp_parse_url( $origin, PHP_URL_HOST ) : '—' ); } elseif ( 'submitdock_delivery' === $column ) { echo esc_html( self::delivery_label( $post_id ) ); } } /** * Adds the form filter and export button above the list. * * @param string $post_type Current post type. * @param string $which Top or bottom navigation. */ public static function render_list_controls( $post_type, $which = 'top' ) { if ( self::POST_TYPE !== $post_type || 'top' !== $which ) { return; } $forms = self::form_names(); if ( $forms ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only list filter. $current = isset( $_GET['submitdock_form'] ) ? sanitize_text_field( wp_unslash( $_GET['submitdock_form'] ) ) : ''; echo ''; echo ''; } if ( current_user_can( 'read_private_posts' ) ) { $url = wp_nonce_url( admin_url( 'admin-post.php?action=submitdock_export' ), 'submitdock_export' ); printf( '%2$s', esc_url( $url ), esc_html__( 'Export CSV', 'submitdock' ) ); } } /** * Distinct form identifiers in use. * * @return string[] */ private static function form_names() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Distinct meta values have no core API. $forms = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value <> '' ORDER BY meta_value LIMIT 100", self::META_FORM ) ); return array_map( 'strval', $forms ); } /** * Applies the form filter to the submissions list. * * @param WP_Query $query Current query. */ public static function filter_by_form( $query ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only list filter. if ( ! is_admin() || ! $query->is_main_query() || self::POST_TYPE !== $query->get( 'post_type' ) || empty( $_GET['submitdock_form'] ) ) { return; } $query->set( 'meta_query', array( array( 'key' => self::META_FORM, // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only list filter. 'value' => sanitize_text_field( wp_unslash( $_GET['submitdock_form'] ) ), ), ) ); } /** * Adds the submission and visitor boxes. */ public static function add_meta_boxes() { add_meta_box( 'submitdock-fields', __( 'Submitted fields', 'submitdock' ), array( __CLASS__, 'render_fields_box' ), self::POST_TYPE, 'normal', 'high' ); add_meta_box( 'submitdock-visitor', __( 'Visitor details', 'submitdock' ), array( __CLASS__, 'render_visitor_box' ), self::POST_TYPE, 'normal' ); } /** * Renders the submitted fields. * * @param WP_Post $post Submission. */ public static function render_fields_box( $post ) { $fields = get_post_meta( $post->ID, self::META_FIELDS, true ); $form = (string) get_post_meta( $post->ID, self::META_FORM, true ); if ( ! is_array( $fields ) ) { echo '
' . esc_html__( 'No fields were saved.', 'submitdock' ) . '
'; return; } if ( '' !== $form ) { /* translators: %s: form identifier. */ echo '' . esc_html( sprintf( __( 'Form: %s', 'submitdock' ), $form ) ) . '
'; } echo '| ' . esc_html( self::label_for( $key ) ) . ' | '; if ( is_string( $value ) && is_email( $value ) ) { printf( '%2$s', esc_url( 'mailto:' . $value ), esc_html( $value ) ); } else { echo nl2br( esc_html( self::format_value( $value ) ) ); } echo ' |
|---|
| ' . esc_html( $label ) . ' | ' . esc_html( self::format_value( $value ) ) . ' |
|---|---|
| ' . esc_html__( 'Email notification', 'submitdock' ) . ' | ' . esc_html( self::delivery_label( $post->ID ) ) . ' |