_x( 'Form Submissions', 'post type general name', 'airo-wp' ), 'singular_name' => _x( 'Form Submission', 'post type singular name', 'airo-wp' ), 'menu_name' => _x( 'Form Submissions', 'admin menu', 'airo-wp' ), 'name_admin_bar' => _x( 'Form Submission', 'add new on admin bar', 'airo-wp' ), 'all_items' => __( 'All Submissions', 'airo-wp' ), 'view_item' => __( 'View Submission', 'airo-wp' ), 'search_items' => __( 'Search Submissions', 'airo-wp' ), 'not_found' => __( 'No submissions found', 'airo-wp' ), 'not_found_in_trash' => __( 'No submissions found in Trash', 'airo-wp' ), ); $args = array( 'labels' => $labels, 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_in_menu' => 'airo-wp', // Show under airo-wp menu. 'query_var' => true, 'rewrite' => false, 'show_in_rest' => false, // Prevent REST API access. 'capability_type' => 'dsgo_form_submission', 'capabilities' => array( 'edit_post' => 'manage_options', 'read_post' => 'manage_options', 'delete_post' => 'manage_options', 'edit_posts' => 'manage_options', 'edit_others_posts' => 'manage_options', 'edit_private_posts' => 'manage_options', 'edit_published_posts' => 'manage_options', 'delete_posts' => 'manage_options', 'delete_others_posts' => 'manage_options', 'delete_private_posts' => 'manage_options', 'delete_published_posts' => 'manage_options', 'publish_posts' => 'manage_options', 'read_private_posts' => 'manage_options', 'create_posts' => 'do_not_allow', // Prevent manual creation - only via form submission. ), 'map_meta_cap' => false, // Disabled - all capabilities explicitly mapped to manage_options. 'has_archive' => false, 'hierarchical' => false, 'supports' => array( 'title' ), ); register_post_type( 'dsgo_form_submission', $args ); } /** * Add meta boxes to submission edit screen. */ public function add_meta_boxes() { add_meta_box( 'dsg_submission_details', __( 'Submission Details', 'airo-wp' ), array( $this, 'render_submission_details' ), 'dsgo_form_submission', 'normal', 'high' ); add_meta_box( 'dsg_submission_meta', __( 'Submission Information', 'airo-wp' ), array( $this, 'render_submission_meta' ), 'dsgo_form_submission', 'side', 'default' ); } /** * Render submission details meta box. * * @param \WP_Post $post Post object. */ public function render_submission_details( $post ) { $fields = get_post_meta( $post->ID, '_dsg_form_fields', true ); if ( empty( $fields ) || ! is_array( $fields ) ) { echo '

' . esc_html__( 'No form data available.', 'airo-wp' ) . '

'; return; } echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ( $fields as $field_name => $field_data ) { $raw = isset( $field_data['value'] ) ? $field_data['value'] : ''; $type = isset( $field_data['type'] ) ? $field_data['type'] : 'text'; $text = self::display_value( $raw, (string) $type ); $label = isset( $field_data['label'] ) && '' !== $field_data['label'] ? (string) $field_data['label'] : ''; // Format value based on type. if ( 'email' === $type && is_string( $raw ) ) { $value = '' . esc_html( $raw ) . ''; } elseif ( 'url' === $type && is_string( $raw ) ) { $value = '' . esc_html( $raw ) . ''; } elseif ( 'textarea' === $type ) { $value = '
' . esc_html( $text ) . '
'; } else { $value = esc_html( $text ); } $name_cell = '' !== $label ? '' . esc_html( $label ) . '
' . esc_html( $field_name ) . '' : '' . esc_html( $field_name ) . ''; echo ''; echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped above. echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo ''; echo ''; } echo ''; echo '
' . esc_html__( 'Field', 'airo-wp' ) . '' . esc_html__( 'Value', 'airo-wp' ) . '' . esc_html__( 'Type', 'airo-wp' ) . '
' . $name_cell . '' . $value . '' . esc_html( $type ) . '
'; } /** * Flatten a stored field value to display text. * * Checkbox groups and multi-selects store arrays; only scalar members are kept. * * @param mixed $value Stored value. * @return string Text. */ public static function format_field_value( $value ): string { if ( is_array( $value ) ) { return implode( ', ', array_map( 'strval', array_filter( $value, 'is_scalar' ) ) ); } return is_scalar( $value ) ? (string) $value : ''; } /** * A stored value as a site owner should read it. * * A ticked single checkbox stores its value attribute, "1" unless the * author set one; show that default as "Yes". Webhook payloads keep the * stored value. * * @param mixed $value Stored value. * @param string $type Field type. * @return string Text. */ public static function display_value( $value, string $type ): string { $text = self::format_field_value( $value ); return 'checkbox' === $type && '1' === $text ? __( 'Yes', 'airo-wp' ) : $text; } /** * Render submission meta information. * * @param \WP_Post $post Post object. */ public function render_submission_meta( $post ) { $form_id = get_post_meta( $post->ID, '_dsg_form_id', true ); $ip_address = get_post_meta( $post->ID, '_dsg_submission_ip', true ); $user_agent = get_post_meta( $post->ID, '_dsg_submission_user_agent', true ); $referer = get_post_meta( $post->ID, '_dsg_submission_referer', true ); $date = get_post_meta( $post->ID, '_dsg_submission_date', true ); $email_sent = get_post_meta( $post->ID, '_dsg_email_sent', true ); $email_to = get_post_meta( $post->ID, '_dsg_email_to', true ); $email_sent_date = get_post_meta( $post->ID, '_dsg_email_sent_date', true ); echo '
'; echo '' . esc_html__( 'Form ID:', 'airo-wp' ) . '
'; echo '' . esc_html( $form_id ) . ''; echo '
'; if ( $date ) { echo '
'; echo '' . esc_html__( 'Submitted:', 'airo-wp' ) . '
'; echo esc_html( wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $date ) ) ); echo '
'; } // Email delivery status. if ( '' !== $email_sent ) { $is_sent = ( 'yes' === $email_sent ); $status_class = $is_sent ? 'airo-wp-submission-status--success' : 'airo-wp-submission-status--error'; printf( '
', esc_attr( $status_class ) ); echo '' . esc_html__( 'Email Status:', 'airo-wp' ) . '
'; if ( $is_sent ) { echo '✓ ' . esc_html__( 'Sent Successfully', 'airo-wp' ) . ''; if ( $email_to ) { echo '
' . esc_html__( 'To:', 'airo-wp' ) . ' ' . esc_html( $email_to ) . ''; } if ( $email_sent_date ) { echo '
' . esc_html__( 'Sent:', 'airo-wp' ) . ' ' . esc_html( wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $email_sent_date ) ) . ''; } } else { echo '✗ ' . esc_html__( 'Failed to Send', 'airo-wp' ) . ''; if ( $email_to ) { echo '
' . esc_html__( 'Attempted to:', 'airo-wp' ) . ' ' . esc_html( $email_to ) . ''; } } echo '
'; } if ( $ip_address ) { echo '
'; echo '' . esc_html__( 'IP Address:', 'airo-wp' ) . '
'; echo '' . esc_html( $ip_address ) . ''; echo '
'; } if ( $referer ) { echo '
'; echo '' . esc_html__( 'Referrer:', 'airo-wp' ) . '
'; echo '' . esc_html( $referer ) . ''; echo '
'; } if ( $user_agent ) { echo '
'; echo '' . esc_html__( 'User Agent:', 'airo-wp' ) . '
'; echo '' . esc_html( $user_agent ) . ''; echo '
'; } } /** * Customize admin columns. * * @param array $columns Existing columns. * @return array Modified columns. */ public function custom_columns( $columns ) { $new_columns = array( 'cb' => $columns['cb'], 'title' => __( 'Submission', 'airo-wp' ), 'form_id' => __( 'Form ID', 'airo-wp' ), 'email_status' => __( 'Email', 'airo-wp' ), 'ip_address' => __( 'IP Address', 'airo-wp' ), 'date' => __( 'Date', 'airo-wp' ), ); return $new_columns; } /** * Render custom column content. * * @param string $column Column name. * @param int $post_id Post ID. */ public function custom_column_content( $column, $post_id ) { switch ( $column ) { case 'form_id': $form_id = get_post_meta( $post_id, '_dsg_form_id', true ); echo $form_id ? '' . esc_html( $form_id ) . '' : '—'; break; case 'email_status': $email_sent = get_post_meta( $post_id, '_dsg_email_sent', true ); if ( '' === $email_sent ) { echo '—'; } elseif ( 'yes' === $email_sent ) { echo '✓ ' . esc_html__( 'Sent', 'airo-wp' ) . ''; } else { echo '✗ ' . esc_html__( 'Failed', 'airo-wp' ) . ''; } break; case 'ip_address': $ip_address = get_post_meta( $post_id, '_dsg_submission_ip', true ); echo $ip_address ? '' . esc_html( $ip_address ) . '' : '—'; break; } } /** * Enqueue admin styles for the submission meta box. * * @param string $hook Current admin page hook suffix. */ public function enqueue_submission_styles( string $hook ): void { if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) { return; } wp_register_style( 'airowp-submissions', false, array(), AIRO_WP_VERSION ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_enqueue_style( 'airowp-submissions' ); wp_add_inline_style( 'airowp-submissions', '.airo-wp-submission-status{margin-bottom:1em;padding:10px;border-left:3px solid}.airo-wp-submission-status--success{background:#d4edda;border-color:#28a745}.airo-wp-submission-status--success .airo-wp-submission-status__label{color:#155724}.airo-wp-submission-status--error{background:#f8d7da;border-color:#dc3545}.airo-wp-submission-status--error .airo-wp-submission-status__label{color:#721c24}' ); } }