true, 'textdomain' => 'jetpack-forms', 'enqueue' => true, ) ); } if ( Contact_Form_Plugin::can_use_analytics() ) { Tracking::register_tracks_functions_scripts( true ); } // Adds Connection package initial state. Connection_Initial_State::render_script( $inline_handle ); // Preload Forms endpoints needed in dashboard context. // Pre-fetch the first inbox page so the UI renders instantly on first load. $preload_params = array( 'context' => 'edit', 'fields_format' => 'collection', 'order' => 'desc', 'orderby' => 'date', 'page' => 1, 'per_page' => 20, 'status' => 'draft,publish', ); \ksort( $preload_params ); $initial_responses_path = \add_query_arg( $preload_params, '/wp/v2/feedback' ); $initial_responses_locale_path = \add_query_arg( \array_merge( $preload_params, array( '_locale' => 'user' ) ), '/wp/v2/feedback' ); $filters_path = '/wp/v2/feedback/filters'; $filters_locale_path = \add_query_arg( array( '_locale' => 'user' ), $filters_path ); $preload_paths = array( '/wp/v2/types?context=view', '/wp/v2/feedback/config', '/wp/v2/feedback/integrations-metadata', '/wp/v2/feedback/counts', $filters_path, $filters_locale_path, $initial_responses_path, $initial_responses_locale_path, ); // Only preload the Forms list endpoint when centralized form management is enabled. if ( Contact_Form_Plugin::has_editor_feature_flag( 'central-form-management' ) ) { $forms_preload_params = array( 'context' => 'edit', 'page' => 1, 'jetpack_forms_context' => 'dashboard', 'order' => 'desc', 'orderby' => 'modified', 'per_page' => 20, 'status' => 'publish,draft,pending,future,private', ); ksort( $forms_preload_params ); $preload_paths[] = add_query_arg( $forms_preload_params, '/wp/v2/jetpack-forms' ); $preload_paths[] = add_query_arg( array_merge( $forms_preload_params, array( '_locale' => 'user' ) ), '/wp/v2/jetpack-forms' ); $preload_paths[] = '/wp/v2/jetpack-forms/status-counts'; $preload_paths[] = add_query_arg( array( '_locale' => 'user' ), '/wp/v2/jetpack-forms/status-counts' ); } $preload_data_raw = array_reduce( $preload_paths, 'rest_preload_api_request', array() ); // Normalize keys to match what apiFetch will request (without domain). $preload_data = array(); foreach ( $preload_data_raw as $key => $value ) { $normalized_key = preg_replace( '#^https?://[^/]+/wp-json#', '', $key ); $preload_data[ $normalized_key ] = $value; } wp_add_inline_script( $inline_handle, sprintf( 'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );', wp_json_encode( $preload_data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) ), $preload_position ); } /** * Whether the current request targets the wp-build (script-module) Forms dashboard, * as opposed to the legacy SPA dashboard. * * When true, the legacy dashboard bundle should not be enqueued: the wp-build page * (build/pages/jetpack-forms-responses/…) provides its own UI and asset loading. * * @return bool */ public static function is_wp_build_dashboard_page() { /** This filter is documented in class-dashboard.php::init */ return apply_filters( 'jetpack_forms_alpha', true ) && self::get_admin_query_page() === self::FORMS_WPBUILD_ADMIN_SLUG; } /** * Register the dashboard admin submenu Forms under Jetpack menu. */ public function add_admin_submenu() { /** This filter is documented in class-dashboard.php::init */ if ( apply_filters( 'jetpack_forms_alpha', true ) ) { // Report a missing build here rather than only on the page itself, so a partial // deploy shows up on the first admin request instead of waiting for someone to // open Forms. Keyed on the file and not on the generated callback: load_wp_build() // only requires build.php on the Forms page, so the callback is legitimately // absent on every other admin screen, which runs this method too. if ( ! file_exists( self::wp_build_index_path() ) ) { _doing_it_wrong( __METHOD__, 'The Jetpack Forms build output is missing: build/build.php is absent, so the dashboard has nothing to render. The package build did not run for this deploy.', '' ); } // `jetpack_forms_jetpack_forms_responses_wp_admin_render_page` is the callback generated // by the WP build script, named after the page slug. It only exists once `build/build.php` // is loaded. Without it the page has nothing to render, so show an explanation rather // than the legacy mount point, whose bundle load_admin_scripts() does not enqueue here. $callback = function_exists( 'jetpack_forms_jetpack_forms_responses_wp_admin_render_page' ) ? 'jetpack_forms_jetpack_forms_responses_wp_admin_render_page' : array( $this, 'render_wp_build_unavailable' ); Admin_Menu::add_menu( /** "Jetpack Forms" and "Forms" are product names, do not translate. */ 'Jetpack Forms', 'Forms', 'edit_pages', self::FORMS_WPBUILD_ADMIN_SLUG, $callback ); return; } Admin_Menu::add_menu( /** "Jetpack Forms" and "Forms" are Product names, do not translate. */ 'Jetpack Forms', 'Forms', 'edit_pages', self::ADMIN_SLUG, array( $this, 'render_dashboard' ) ); } /** * Render the dashboard. */ public function render_dashboard() { ?>

Jetpack Forms

'feedback', 'post_status' => array( 'publish', 'draft', 'spam', 'trash' ), 'posts_per_page' => 1, 'fields' => 'ids', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'suppress_filters' => true, ) ); return $posts->have_posts(); } /** * Option name for storing classic forms state. */ const CLASSIC_FORMS_OPTION = 'jetpack_forms_classic_state'; /** * Classic forms state: site has classic (non-synced) form submissions. */ const CLASSIC_FORMS_STATE_CLASSIC = 'classic'; /** * Classic forms state: no classic form submissions detected. */ const CLASSIC_FORMS_STATE_HIDDEN = 'hidden'; /** * Classic forms state: user dismissed the classic forms notice. */ const CLASSIC_FORMS_STATE_DISMISSED = 'dismissed'; /** * Returns the classic forms state for the current site. * * Returns 'classic' if the site has form submissions (feedback posts) that were not * created by a synced/reusable jetpack_form, 'dismissed' if the user dismissed the * classic forms notice, or 'hidden' otherwise. * * The result is persisted in a WP option so the detection query only runs once per site. * After that, the cached value is returned on every subsequent call. The cache is also * updated eagerly via mark_classic_form_detected() when new classic submissions arrive. * * @since 7.14.0 * * @return string 'classic', 'hidden', or 'dismissed'. */ public function get_classic_forms_state() { $state = get_option( self::CLASSIC_FORMS_OPTION ); if ( $state ) { return $state; } $state = $this->detect_classic_forms(); update_option( self::CLASSIC_FORMS_OPTION, $state, false ); return $state; } /** * Detects whether any feedback posts exist that are not linked to a jetpack_form post, * indicating the site has classic (inline, widget, or template) forms. * * A feedback post is considered "classic" if: * - It has no parent (post_parent = 0), meaning it was created by a form embedded in a * widget, page template, or other non-post context. * - Its parent exists but is not a jetpack_form post, meaning it was created by a form * block or shortcode placed directly in a post or page. * * The query uses a LEFT JOIN on the posts table to find feedback posts with no matching * jetpack_form parent. This leverages the primary key index for the join and the * type_status_date index for filtering by post_type, making it efficient even on large * sites. The LIMIT 1 ensures early exit as soon as one classic form is found. * * Note: An alternative approach would be to search post_content for the form block markup * (