diff --git a/projects/packages/forms/changelog/fix-feedback-source-data b/projects/packages/forms/changelog/fix-feedback-source-data new file mode 100644 index 0000000000000..f5616aef97072 --- /dev/null +++ b/projects/packages/forms/changelog/fix-feedback-source-data @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Forms: Store the feedback source info with more context diff --git a/projects/packages/forms/src/contact-form/class-contact-form-endpoint.php b/projects/packages/forms/src/contact-form/class-contact-form-endpoint.php index 7a613264da59e..a0aa7d0023485 100644 --- a/projects/packages/forms/src/contact-form/class-contact-form-endpoint.php +++ b/projects/packages/forms/src/contact-form/class-contact-form-endpoint.php @@ -408,6 +408,16 @@ public function get_item_schema() { 'readonly' => true, ); + $schema['properties']['edit_form_url'] = array( + 'description' => __( 'The URL to edit the form.', 'jetpack-forms' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + 'readonly' => true, + ); + $schema['properties']['subject'] = array( 'description' => __( 'The subject line of the form submission.', 'jetpack-forms' ), 'type' => 'string', @@ -621,6 +631,9 @@ public function prepare_item_for_response( $item, $request ) { if ( rest_is_field_included( 'entry_permalink', $fields ) ) { $data['entry_permalink'] = $response->get_entry_permalink(); } + if ( rest_is_field_included( 'edit_form_url', $fields ) ) { + $data['edit_form_url'] = $response->get_edit_form_url(); + } if ( rest_is_field_included( 'subject', $fields ) ) { $data['subject'] = $response->get_subject(); } diff --git a/projects/packages/forms/src/contact-form/class-contact-form.php b/projects/packages/forms/src/contact-form/class-contact-form.php index 32fba3dcb6a41..437591a80ca07 100644 --- a/projects/packages/forms/src/contact-form/class-contact-form.php +++ b/projects/packages/forms/src/contact-form/class-contact-form.php @@ -121,6 +121,13 @@ class Contact_Form extends Contact_Form_Shortcode { */ public $has_verified_jwt = false; + /** + * The source of the feedback entry. + * + * @var Feedback_Source + */ + private $source; + /** * Construction function. * @@ -243,6 +250,7 @@ public static function get_instance_from_jwt( $jwt_token ) { } $form = new self( $data['attributes'], $data['content'], empty( $data['attributes']['id'] ) ); + $form->source = Feedback_Source::from_serialized( $data['source'] ); $form->hash = $data['hash']; $form->has_verified_jwt = true; return $form; @@ -354,17 +362,32 @@ public function get_attributes() { * @return string The JWT token. */ public function get_jwt() { - $attributes = $this->attributes; + $attributes = $this->attributes; + $this->source = Feedback_Source::get_current( $attributes ); return JWT::encode( array( 'attributes' => $attributes, 'content' => $this->content, 'hash' => $this->hash, + 'source' => $this->source->serialize(), ), self::get_secret() ); } + /** + * Get the current source obejct. That is relevent to the form and there current request. + * + * @return Feedback_Source Return the current feedback source object. + */ + public function get_source() { + if ( ! $this->source ) { + $attributes = $this->attributes; + $this->source = Feedback_Source::get_current( $attributes ); + } + return $this->source; + } + /** * Get the count of forms. * @@ -829,10 +852,6 @@ class='" . esc_attr( $form_classes ) . "' $form_aria_label $r .= "\t\t\n"; $r .= "\t\t\n"; - if ( $page && $page > 1 ) { - $r .= "\t\t\n"; - } - if ( ! $has_submit_button_block ) { $r .= "\t
\n"; } @@ -1627,15 +1646,9 @@ public function get_field_ids() { * Stores feedback. Sends email. */ public function process_submission() { - $page_number = 1; - - // We skip the nonce verification for since nonce earlier in process_form_submission. - if ( isset( $_POST['page'] ) ) { // phpcs:Ignore WordPress.Security.NonceVerification.Missing - $page_number = absint( wp_unslash( $_POST['page'] ) ); // phpcs:Ignore WordPress.Security.NonceVerification.Missing - } - - $response = Feedback::from_submission( $_POST, $this, $this->current_post, $page_number ); // phpcs:Ignore WordPress.Security.NonceVerification.Missing + $response = Feedback::from_submission( $_POST, $this ); // phpcs:Ignore WordPress.Security.NonceVerification.Missing + $response->set_source( $this->get_source() ); $plugin = Contact_Form_Plugin::init(); $id = $this->get_attribute( 'id' ); diff --git a/projects/packages/forms/src/contact-form/class-feedback-source.php b/projects/packages/forms/src/contact-form/class-feedback-source.php index 00183571427a8..45f34001ae275 100644 --- a/projects/packages/forms/src/contact-form/class-feedback-source.php +++ b/projects/packages/forms/src/contact-form/class-feedback-source.php @@ -17,9 +17,9 @@ class Feedback_Source { /** * The ID of the post or page that the feedback was created on. * - * @var int + * @var string */ - private $id = 0; + private $id = ''; /** * The title of the post or page that the feedback was created on. @@ -43,29 +43,62 @@ class Feedback_Source { */ private $page_number = 1; + /** + * The source type of the feedback entry. + * Possible values: single, widget, block_template, block_template_part + * + * @var string + */ + private $source_type = 'single'; + + /** + * The request URL of the feedback entry. + * + * @var string + */ + private $request_url = ''; + /** * Constructor for Feedback_Source. * - * @param int $id The ID of the feedback entry. - * @param string $title The title of the feedback entry. - * @param int $page_number The page number of the feedback entry, default is 1. + * @param string|int $id The Source ID = post ID, widget ID, block template ID, or 0 for homepage or non-post/page. + * @param string $title The title of the feedback entry. + * @param int $page_number The page number of the feedback entry, default is 1. + * @param string $source_type The source type of the feedback entry, default is 'single'. + * @param string $request_url The request URL of the feedback entry. */ - public function __construct( $id, $title, $page_number = 1 ) { + public function __construct( $id = 0, $title = '', $page_number = 1, $source_type = 'single', $request_url = '' ) { + + if ( is_numeric( $id ) ) { + $this->id = $id > 0 ? $id : 0; + } else { + $this->id = $id; + } - $this->id = $id > 0 ? (int) $id : 0; $this->title = $title; $this->page_number = $page_number; - $this->permalink = $this->id === 0 ? home_url() : ''; - - if ( $id <= 0 ) { - return; - } + $this->permalink = empty( $request_url ) ? home_url() : $request_url; + $this->source_type = $source_type; // possible source types: single, widget, block_template, block_template_part + $this->request_url = $request_url; - $entry_post = get_post( $id ); + if ( is_numeric( $id ) && ! empty( $id ) ) { + $entry_post = get_post( (int) $id ); + if ( $entry_post && $entry_post->post_status === 'publish' ) { + $this->permalink = get_permalink( $entry_post ); + $this->title = get_the_title( $entry_post ); + } elseif ( $entry_post ) { + $this->permalink = ''; - if ( $entry_post && $entry_post->post_status === 'publish' ) { - $this->permalink = get_permalink( $entry_post ); - $this->title = get_the_title( $entry_post ); + if ( $entry_post->post_status === 'trash' ) { + /* translators: %s is the post title */ + $this->title = sprintf( __( '(trashed) %s', 'jetpack-forms' ), $this->title ); + } + } + if ( empty( $entry_post ) ) { + /* translators: %s is the post title */ + $this->title = sprintf( __( '(deleted) %s', 'jetpack-forms' ), $this->title ); + $this->permalink = ''; + } } } @@ -83,11 +116,81 @@ public static function from_submission( $current_post, int $current_page_number return new self( 0, '', $current_page_number ); } - $title = $current_post->post_title ?? ''; + $title = $current_post->post_title ?? __( '(no title)', 'jetpack-forms' ); return new self( $id, $title, $current_page_number ); } + /** + * Get the title of the current page. That we can then use to display in the feedback entry. + * + * @return string The title of the current page. That we want to show to the user. To tell them where the feedback was left. + */ + private static function get_source_title() { + if ( is_front_page() ) { + return get_bloginfo( 'name' ); + } + if ( is_home() ) { + return get_the_title( get_option( 'page_for_posts', true ) ); + } + if ( is_singular() ) { + return get_the_title(); + } + if ( is_archive() ) { + return get_the_archive_title(); + } + if ( is_search() ) { + /* translators: %s is the search term */ + return sprintf( __( 'Search results for: %s', 'jetpack-forms' ), get_search_query() ); + } + if ( is_404() ) { + return __( '404 Not Found', 'jetpack-forms' ); + } + return get_bloginfo( 'name' ); + } + + /** + * Creates a Feedback_Source instance for a block template. + * + * @param array $attributes Form Shortcode attributes. + * + * @return Feedback_Source Returns an instance of Feedback_Source. + */ + public static function get_current( $attributes ) { + global $wp, $page; + $current_url = home_url( add_query_arg( array(), $wp->request ) ); + if ( isset( $attributes['widget'] ) && ! empty( $attributes['widget'] ) ) { + return new self( $attributes['widget'], self::get_source_title(), 1, 'widget', $current_url ); + } + + if ( isset( $attributes['block_template'] ) && ! empty( $attributes['block_template'] ) ) { + global $_wp_current_template_id; + return new self( $_wp_current_template_id, self::get_source_title(), $page, 'block_template', $current_url ); + } + + if ( isset( $attributes['block_template_part'] ) && ! empty( $attributes['block_template_part'] ) ) { + return new self( $attributes['block_template_part'], self::get_source_title(), $page, 'block_template_part', $current_url ); + } + + return new Feedback_Source( \get_the_ID(), \get_the_title(), $page, 'single', $current_url ); + } + + /** + * Creates a Feedback_Source instance from serialized data. + * + * @param array $data The serialized data. + * @return Feedback_Source Returns an instance of Feedback_Source. + */ + public static function from_serialized( $data ) { + $id = $data['source_id'] ?? 0; + $title = $data['entry_title'] ?? ''; + $page_number = $data['entry_page'] ?? 1; + $source_type = $data['source_type'] ?? 'single'; + $request_url = $data['request_url'] ?? ''; + + return new self( $id, $title, $page_number, $source_type, $request_url ); + } + /** * Get the permalink of the feedback entry. * @@ -100,6 +203,38 @@ public function get_permalink() { return $this->permalink; } + /** + * Get the edit URL of the form or page where the feedback was submitted from. + * + * @return string The edit URL of the form or page. + */ + public function get_edit_form_url() { + + if ( current_user_can( 'edit_theme_options' ) ) { + if ( $this->source_type === 'block_template' && \wp_is_block_theme() ) { + return admin_url( 'site-editor.php?p=' . esc_attr( '/wp_template/' . addslashes( $this->id ) ) . '&canvas=edit' ); + } + + if ( $this->source_type === 'block_template_part' && \wp_is_block_theme() ) { + return admin_url( 'site-editor.php?p=' . esc_attr( '/wp_template_part/' . addslashes( $this->id ) ) . '&canvas=edit' ); + } + + if ( $this->source_type === 'widget' && current_theme_supports( 'widgets' ) ) { + return admin_url( 'widgets.php' ); + } + } + + if ( $this->id && is_numeric( $this->id ) && $this->id > 0 && current_user_can( 'edit_post', (int) $this->id ) ) { + $entry_post = get_post( (int) $this->id ); + if ( $entry_post && $entry_post->post_status === 'trash' ) { + return ''; // No edit link is possible for trashed posts. They need to be restored first. + } + return \get_edit_post_link( (int) $this->id, 'url' ); + } + + return ''; + } + /** * Get the relative permalink of the feedback entry. * @@ -131,7 +266,7 @@ public function get_title() { /** * Get the post id of the feedback entry. * - * @return int The ID of the feedback entry. + * @return int|string The ID of the feedback entry. */ public function get_id() { return $this->id; @@ -146,6 +281,9 @@ public function serialize() { return array( 'entry_title' => $this->title, 'entry_page' => $this->page_number, + 'source_id' => $this->id, + 'source_type' => $this->source_type, + 'request_url' => $this->request_url, ); } } diff --git a/projects/packages/forms/src/contact-form/class-feedback.php b/projects/packages/forms/src/contact-form/class-feedback.php index 876b3debe3762..81c1f787cff6b 100644 --- a/projects/packages/forms/src/contact-form/class-feedback.php +++ b/projects/packages/forms/src/contact-form/class-feedback.php @@ -156,11 +156,14 @@ private function load_from_post( WP_Post $feedback_post ) { $this->feedback_time = $feedback_post->post_date; $this->fields = $parsed_content['fields'] ?? array(); + $source_id = $feedback_post->post_parent ? (int) $feedback_post->post_parent : 0; $this->source = new Feedback_Source( - $feedback_post->post_parent, + $parsed_content['source_id'] ?? $source_id, $parsed_content['entry_title'] ?? '', - $parsed_content['entry_page'] ?? 1 + $parsed_content['entry_page'] ?? 1, + $parsed_content['source_type'] ?? 'single', + $parsed_content['request_url'] ?? '' ); $this->ip_address = $parsed_content['ip'] ?? $this->get_first_field_of_type( 'ip' ); @@ -194,6 +197,15 @@ public static function from_submission( $post_data, $form, $current_post = null, return $instance; } + /** + * Set the source of the feedback entry. + * + * @param Feedback_Source $source The source object. + */ + public function set_source( $source ) { + $this->source = $source; + } + /** * Load from Form Submission. * @@ -767,7 +779,7 @@ public function set_status( $status ) { * * This is the post ID of the post or page that the feedback was submitted from. * - * @return int|null + * @return int|string */ public function get_entry_id() { return $this->source->get_id(); @@ -793,6 +805,15 @@ public function get_entry_title() { public function get_entry_permalink() { return $this->source->get_permalink(); } + + /** + * Get the editor URL where the user can edit the form. + * + * @return string + */ + public function get_edit_form_url() { + return $this->source->get_edit_form_url(); + } /** * Get the short permalink of a post. * diff --git a/projects/packages/forms/src/dashboard/inbox/dataviews/actions.js b/projects/packages/forms/src/dashboard/inbox/dataviews/actions.js index 56ea8675cc452..a28c19e201d9a 100644 --- a/projects/packages/forms/src/dashboard/inbox/dataviews/actions.js +++ b/projects/packages/forms/src/dashboard/inbox/dataviews/actions.js @@ -27,6 +27,22 @@ export const viewActionModal = { }, }; +export const editFormAction = { + id: 'edit-form', + label: __( 'Edit form', 'jetpack-forms' ), + icon: