Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
20.42% covered (danger)
20.42%
29 / 142
38.46% covered (danger)
38.46%
5 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Dashboard
20.00% covered (danger)
20.00%
28 / 140
38.46% covered (danger)
38.46%
5 / 13
459.59
0.00% covered (danger)
0.00%
0 / 1
 load_wp_build
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 init
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 get_admin_query_page
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 load_admin_scripts
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 1
30
 add_new_admin_submenu
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 add_forms_wpbuild_submenu
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 render_dashboard
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 has_feedback
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 get_forms_admin_url
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 append_tab_to_url
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 is_jetpack_forms_admin_page
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 is_notes_enabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_admin_url
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Jetpack forms dashboard.
4 *
5 * @package automattic/jetpack-forms
6 */
7
8namespace Automattic\Jetpack\Forms\Dashboard;
9
10use Automattic\Jetpack\Admin_UI\Admin_Menu;
11use Automattic\Jetpack\Assets;
12use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
13use Automattic\Jetpack\Forms\ContactForm\Contact_Form_Plugin;
14use Automattic\Jetpack\Tracking;
15
16if ( ! defined( 'ABSPATH' ) ) {
17    exit( 0 );
18}
19
20/**
21 * Handles the Jetpack Forms dashboard.
22 */
23class Dashboard {
24
25    /**
26     * Load wp-build generated files if available.
27     * This is for the new DataViews-based responses list.
28     */
29    public static function load_wp_build() {
30        if ( self::get_admin_query_page() === self::FORMS_WPBUILD_ADMIN_SLUG ) {
31            $wp_build_index = dirname( __DIR__, 2 ) . '/build/build.php';
32            if ( file_exists( $wp_build_index ) ) {
33                require_once $wp_build_index;
34            }
35        }
36    }
37
38    /**
39     * Script handle for the JS file we enqueue in the Feedback admin page.
40     *
41     * @var string
42     */
43    const SCRIPT_HANDLE = 'jp-forms-dashboard';
44
45    const ADMIN_SLUG = 'jetpack-forms-admin';
46
47    /**
48     * Slug for the wp-admin integrated Responses UI (wp-build page).
49     *
50     * Note: This must be a valid submenu slug (sanitize_key compatible), not a full URL.
51     *
52     * @var string
53     */
54    const FORMS_WPBUILD_ADMIN_SLUG = 'jetpack-forms-responses-wp-admin';
55
56    /**
57     * Priority for the dashboard menu.
58     * Needs to be high enough for us to be able to unregister the default edit.php menu item.
59     *
60     * @var int
61     */
62    const MENU_PRIORITY = 999;
63
64    /**
65     * Initialize the dashboard.
66     */
67    public function init() {
68        $is_wp_build_enabled = apply_filters( 'jetpack_forms_alpha', false );
69
70        if ( $is_wp_build_enabled ) {
71            // Load wp-build generated files for the new DataViews-based UI.
72            self::load_wp_build();
73        }
74
75        add_action( 'admin_menu', array( $this, 'add_new_admin_submenu' ), self::MENU_PRIORITY );
76
77        if ( $is_wp_build_enabled ) {
78            add_action( 'admin_menu', array( $this, 'add_forms_wpbuild_submenu' ), self::MENU_PRIORITY );
79        }
80
81        add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_scripts' ) );
82
83        // Removed all admin notices on the Jetpack Forms admin page.
84        if ( self::get_admin_query_page() === self::ADMIN_SLUG ) {
85            remove_all_actions( 'admin_notices' );
86        }
87    }
88
89    /**
90     * Get the current query 'page' parameter.
91     *
92     * @return string
93     */
94    private static function get_admin_query_page() {
95        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
96        return isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
97    }
98
99    /**
100     * Load JavaScript for the dashboard.
101     */
102    public function load_admin_scripts() {
103        if ( ! self::is_jetpack_forms_admin_page() ) {
104            return;
105        }
106
107        Assets::register_script(
108            self::SCRIPT_HANDLE,
109            '../../dist/dashboard/jetpack-forms-dashboard.js',
110            __FILE__,
111            array(
112                'in_footer'  => true,
113                'textdomain' => 'jetpack-forms',
114                'enqueue'    => true,
115            )
116        );
117
118        if ( Contact_Form_Plugin::can_use_analytics() ) {
119            Tracking::register_tracks_functions_scripts( true );
120        }
121
122        // Adds Connection package initial state.
123        Connection_Initial_State::render_script( self::SCRIPT_HANDLE );
124
125        // Preload Forms endpoints needed in dashboard context.
126        // Pre-fetch the first inbox page so the UI renders instantly on first load.
127        $preload_params = array(
128            'context'  => 'edit',
129            'order'    => 'desc',
130            'orderby'  => 'date',
131            'page'     => 1,
132            'per_page' => 20,
133            'status'   => 'draft,publish',
134        );
135        \ksort( $preload_params );
136        $initial_responses_path        = \add_query_arg( $preload_params, '/wp/v2/feedback' );
137        $initial_responses_locale_path = \add_query_arg(
138            \array_merge(
139                $preload_params,
140                array( '_locale' => 'user' )
141            ),
142            '/wp/v2/feedback'
143        );
144        $filters_path                  = '/wp/v2/feedback/filters';
145        $filters_locale_path           = \add_query_arg( array( '_locale' => 'user' ), $filters_path );
146        $preload_paths                 = array(
147            '/wp/v2/types?context=view',
148            '/wp/v2/feedback/config',
149            '/wp/v2/feedback/integrations-metadata',
150            '/wp/v2/feedback/counts',
151            $filters_path,
152            $filters_locale_path,
153            $initial_responses_path,
154            $initial_responses_locale_path,
155        );
156
157        // Only preload the Forms list endpoint when centralized form management is enabled.
158        if ( Contact_Form_Plugin::has_editor_feature_flag( 'central-form-management' ) ) {
159            $forms_preload_params = array(
160                'context'               => 'edit',
161                'page'                  => 1,
162                'jetpack_forms_context' => 'dashboard',
163                'order'                 => 'desc',
164                'orderby'               => 'modified',
165                'per_page'              => 20,
166                'status'                => 'publish,draft,pending,future,private',
167            );
168            ksort( $forms_preload_params );
169            $preload_paths[] = add_query_arg( $forms_preload_params, '/wp/v2/jetpack-forms' );
170            $preload_paths[] = add_query_arg(
171                array_merge(
172                    $forms_preload_params,
173                    array( '_locale' => 'user' )
174                ),
175                '/wp/v2/jetpack-forms'
176            );
177        }
178        $preload_data_raw = array_reduce( $preload_paths, 'rest_preload_api_request', array() );
179
180        // Normalize keys to match what apiFetch will request (without domain).
181        $preload_data = array();
182        foreach ( $preload_data_raw as $key => $value ) {
183            $normalized_key                  = preg_replace( '#^https?://[^/]+/wp-json#', '', $key );
184            $preload_data[ $normalized_key ] = $value;
185        }
186
187        wp_add_inline_script(
188            self::SCRIPT_HANDLE,
189            sprintf(
190                'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );',
191                wp_json_encode( $preload_data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP )
192            ),
193            'before'
194        );
195    }
196
197    /**
198     * Register the NEW dashboard admin submenu Forms under Jetpack menu.
199     */
200    public function add_new_admin_submenu() {
201        Admin_Menu::add_menu(
202            /** "Jetpack Forms" and "Forms" are Product names, do not translate. */
203            'Jetpack Forms',
204            'Forms',
205            'edit_pages',
206            self::ADMIN_SLUG,
207            array( $this, 'render_dashboard' ),
208            10
209        );
210    }
211
212    /**
213     * Register Forms (WP-Build) submenu under Jetpack menu using wp-build page.
214     */
215    public function add_forms_wpbuild_submenu() {
216        $callback = function_exists( 'jetpack_forms_jetpack_forms_responses_wp_admin_render_page' )
217            ? 'jetpack_forms_jetpack_forms_responses_wp_admin_render_page'
218            : array( $this, 'render_dashboard' );
219
220        Admin_Menu::add_menu(
221            'Jetpack Forms',
222            'Forms (WP-Build)',
223            'edit_pages',
224            self::FORMS_WPBUILD_ADMIN_SLUG,
225            $callback,
226            11
227        );
228    }
229
230    /**
231     * Render the dashboard.
232     */
233    public function render_dashboard() {
234        ?>
235        <div id="jp-forms-dashboard"></div>
236        <?php
237    }
238
239    /**
240     * Returns true if there are any feedback posts on the site.
241     *
242     * @return boolean
243     */
244    public function has_feedback() {
245        $posts = new \WP_Query(
246            array(
247                'post_type'              => 'feedback',
248                'post_status'            => array( 'publish', 'draft', 'spam', 'trash' ),
249                'posts_per_page'         => 1,
250                'fields'                 => 'ids',
251                'no_found_rows'          => true,
252                'update_post_meta_cache' => false,
253                'update_post_term_cache' => false,
254                'suppress_filters'       => true,
255            )
256        );
257        return $posts->have_posts();
258    }
259
260    /**
261     * Returns url of forms admin page.
262     *
263     * @param string|null $tab Tab to open in the forms admin page.
264     *
265     * @return string
266     */
267    public static function get_forms_admin_url( $tab = null ) {
268        $base_url = get_admin_url() . 'admin.php?page=jetpack-forms-admin';
269
270        return self::append_tab_to_url( $base_url, $tab );
271    }
272
273    /**
274     * Appends the appropriate tab parameter to the URL based on the view type.
275     *
276     * @param string $url              Base URL to append to.
277     * @param string $tab              Tab to open.
278     *
279     * @return string
280     */
281    private static function append_tab_to_url( $url, $tab ) {
282        $valid_tabs = array( 'spam', 'inbox', 'trash' );
283        if ( ! in_array( $tab, $valid_tabs, true ) ) {
284            return $url;
285        }
286
287        return $url . '#/responses?status=' . $tab;
288    }
289
290    /**
291     * Returns true if the current screen is the Jetpack Forms admin page.
292     *
293     * @return boolean
294     */
295    public static function is_jetpack_forms_admin_page() {
296        if ( ! function_exists( 'get_current_screen' ) ) {
297            return false;
298        }
299
300        $screen = get_current_screen();
301        return $screen && $screen->id === 'jetpack_page_jetpack-forms-admin';
302    }
303
304    /**
305     * Returns true if form notes feature is enabled.
306     *
307     * @return boolean
308     */
309    public static function is_notes_enabled() {
310        /**
311        * Enable form notes feature in Jetpack Forms .
312        *
313        * @module contact-form
314        * @since 7.3.0
315        *
316        * @param bool $enabled Should the form notes feature be enabled? Defaults to false.
317        */
318        return apply_filters( 'jetpack_forms_notes_enable', false );
319    }
320
321    /**
322     * Get admin URL for given screen ID.
323     *
324     * @param string $screen_id Screen ID.
325     * @return string|null Admin URL or null if not found.
326     */
327    public static function get_admin_url( $screen_id ) {
328        switch ( $screen_id ) {
329            case 'edit-jetpack_form':
330                return admin_url( 'admin.php?page=' . self::ADMIN_SLUG . '#/forms' );
331            case 'edit-feedback':
332                return admin_url( 'admin.php?page=' . self::ADMIN_SLUG . '#/responses' );
333        }
334        return null;
335    }
336}