PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-forms / src / dashboard / class-dashboard.php

class-dashboard.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at jetpack_vendor/automattic/jetpack-forms/src/dashboard/class-dashboard.php

700 lines 23.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Jetpack forms dashboard.
4 *
5 * @package automattic/jetpack-forms
6 */
7
8 namespace Automattic\Jetpack\Forms\Dashboard;
9
10 use Automattic\Jetpack\Admin_UI\Admin_Menu;
11 use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
12 use Automattic\Jetpack\Forms\ContactForm\Contact_Form;
13 use Automattic\Jetpack\Forms\ContactForm\Contact_Form_Plugin;
14 use Automattic\Jetpack\Tracking;
15 use Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit( 0 );
19 }
20
21 /**
22 * Handles the Jetpack Forms dashboard.
23 */
24 class Dashboard {
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 // Always load for the standalone Forms page.
31 $should_load = self::get_admin_query_page() === self::FORMS_WPBUILD_ADMIN_SLUG;
32
33 /**
34 * Filter whether to load the wp-build asset registrations.
35 * Host applications (e.g., CIAB) can return true to opt in.
36 *
37 * @param bool $should_load Whether build.php should be loaded.
38 */
39 $should_load = apply_filters( 'jetpack_forms_load_wp_build', $should_load );
40
41 if ( ! $should_load ) {
42 return;
43 }
44
45 $wp_build_index = self::wp_build_index_path();
46
47 if ( file_exists( $wp_build_index ) ) {
48 require_once $wp_build_index;
49 }
50
51 // The remaining setup only applies to the standalone Forms page.
52 if ( self::get_admin_query_page() !== self::FORMS_WPBUILD_ADMIN_SLUG ) {
53 return;
54 }
55
56 // When no route path is specified, redirect to the default view
57 // so the client-side router doesn't need a catch-all root route.
58 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
59 if ( ! isset( $_GET['p'] ) ) {
60 wp_safe_redirect( self::get_forms_admin_url( self::get_default_tab() ) );
61
62 exit;
63 }
64
65 // Register polyfills for WP < 7.0 (must run before enqueue).
66 WP_Build_Polyfills::register(
67 'jetpack-forms',
68 array_merge(
69 WP_Build_Polyfills::SCRIPT_HANDLES,
70 WP_Build_Polyfills::MODULE_IDS
71 )
72 );
73 }
74
75 /**
76 * Overrides the generated entry point path. Test seam, null in production.
77 *
78 * Private with no setter: tests reach it by reflection, so nothing outside the
79 * package can point the dashboard at another file.
80 *
81 * @var string|null
82 */
83 private static $wp_build_index = null;
84
85 /**
86 * Absolute path to the entry point generated by the WP build script.
87 *
88 * @return string
89 */
90 private static function wp_build_index_path() {
91 return self::$wp_build_index ?? dirname( __DIR__, 2 ) . '/build/build.php';
92 }
93
94 /**
95 * Script handle for the JS file we enqueue in the Feedback admin page.
96 *
97 * @var string
98 * @deprecated 8.2.0 The legacy dashboard bundle was removed.
99 */
100 const SCRIPT_HANDLE = 'jp-forms-dashboard';
101
102 const ADMIN_SLUG = 'jetpack-forms-admin';
103
104 /**
105 * Slug for the wp-admin integrated Responses UI (wp-build page).
106 *
107 * Note: This must be a valid submenu slug (sanitize_key compatible), not a full URL.
108 *
109 * @var string
110 */
111 const FORMS_WPBUILD_ADMIN_SLUG = 'jetpack-forms-responses-wp-admin';
112
113 /**
114 * Cookie holding the top tab the user last chose.
115 *
116 * Written by src/dashboard/last-tab-cookie.ts; the two names have to stay in step.
117 */
118 const LAST_TAB_COOKIE = 'jetpack_forms_last_tab';
119
120 /**
121 * Top tabs the dashboard can reopen on.
122 *
123 * Mirrored by TOP_TABS in src/dashboard/constants.ts.
124 *
125 * @var string[]
126 */
127 private const TOP_TABS = array( 'forms', 'responses' );
128
129 /**
130 * Priority for the dashboard menu.
131 * Needs to be high enough for us to be able to unregister the default edit.php menu item.
132 *
133 * @var int
134 */
135 const MENU_PRIORITY = 999;
136
137 /**
138 * Initialize the dashboard.
139 */
140 public function init() {
141 add_action( 'admin_menu', array( $this, 'add_admin_submenu' ), self::MENU_PRIORITY );
142 add_action( 'admin_menu', array( __CLASS__, 'redirect_dashboard_url_cross_variant' ), 1 );
143 add_action( 'admin_notices', array( __CLASS__, 'announce_retired_filter' ) );
144
145 self::load_wp_build();
146
147 add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_scripts' ) );
148 }
149
150 /**
151 * Tell anyone still filtering `jetpack_forms_alpha` that it no longer does anything.
152 *
153 * The filter gated the wp-build dashboard while it was in development. That dashboard
154 * is now the only one, so a `false` return has nothing left to select and is ignored.
155 *
156 * Announced rather than applied: _deprecated_hook() reports the hook without honoring
157 * it, where apply_filters_deprecated() would return a `false` this code can no longer
158 * act on. Guarded by has_filter() so sites that never used it stay silent.
159 *
160 * Hooked to `admin_notices` rather than called from init(). This class loads at
161 * `after_setup_theme` priority -2, so with WP_DEBUG display on the notice would print
162 * — and send headers — before load_wp_build() and redirect_dashboard_url_cross_variant()
163 * get to redirect, leaving both on "headers already sent" and a blank page.
164 *
165 * That hook also fires only while an admin screen renders, so the notice stays out of
166 * admin-ajax and admin-post responses, which `is_admin()` would have let through. And
167 * it runs late enough that has_filter() sees callbacks registered on `init`, not just
168 * those added at file scope.
169 *
170 * @since 8.1.0
171 */
172 public static function announce_retired_filter() {
173 if ( ! has_filter( 'jetpack_forms_alpha' ) ) {
174 return;
175 }
176
177 // Kept on one line: replace-next-version-tag.sh only recognizes the token in a
178 // single-line deprecation call, and errors the build out otherwise.
179 _deprecated_hook( 'jetpack_forms_alpha', 'jetpack-forms-8.1.0', '', 'The legacy Forms dashboard has been removed, so this filter no longer selects anything.' );
180 }
181
182 /**
183 * Send legacy dashboard URLs to the wp-build dashboard.
184 *
185 * Load-bearing, not a courtesy for stale links: Creative Mail's JITMs and post-install
186 * redirect, and My Jetpack's fallback URL, still emit the legacy slug today. Keep it.
187 */
188 public static function redirect_dashboard_url_cross_variant() {
189 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
190 $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
191
192 if ( $page !== self::ADMIN_SLUG ) {
193 return;
194 }
195
196 // The hash is never sent to the server. "inbox" used as default tab so we end up specifically in the responses
197 // route, where the client-side router will handle the redirect to the correct status in its beforeLoad hook.
198 wp_safe_redirect( self::get_forms_admin_url( 'inbox' ) );
199 exit;
200 }
201
202 /**
203 * Get the current query 'page' parameter.
204 *
205 * @return string
206 */
207 private static function get_admin_query_page() {
208 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
209 return isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
210 }
211
212 /**
213 * Load JavaScript for the dashboard.
214 */
215 public function load_admin_scripts() {
216 if ( ! self::is_jetpack_forms_admin_page() ) {
217 return;
218 }
219
220 // Attach the shared inline data (connection initial state + REST preload) to
221 // wp-api-fetch, which is always on the page.
222 $inline_handle = 'wp-api-fetch';
223 $preload_position = 'after';
224
225 // The i18n loader is registered on every admin page by jetpack-assets but
226 // only enqueued when depended on; the esbuild bundles don't pull it in.
227 // Enqueue it so the wp-build dashboard's init module can download its JS
228 // translation catalogs.
229 if ( wp_script_is( 'wp-jp-i18n-loader', 'registered' ) ) {
230 wp_enqueue_script( 'wp-jp-i18n-loader' );
231 }
232
233 if ( Contact_Form_Plugin::can_use_analytics() ) {
234 Tracking::register_tracks_functions_scripts( true );
235 }
236
237 // Adds Connection package initial state.
238 Connection_Initial_State::render_script( $inline_handle );
239
240 // Preload Forms endpoints needed in dashboard context.
241 // Pre-fetch the first inbox page so the UI renders instantly on first load.
242 $preload_params = array(
243 'context' => 'edit',
244 'fields_format' => 'collection',
245 'order' => 'desc',
246 'orderby' => 'date',
247 'page' => 1,
248 'per_page' => 20,
249 'status' => 'draft,publish',
250 );
251 \ksort( $preload_params );
252 $initial_responses_path = \add_query_arg( $preload_params, '/wp/v2/feedback' );
253 $initial_responses_locale_path = \add_query_arg(
254 \array_merge(
255 $preload_params,
256 array( '_locale' => 'user' )
257 ),
258 '/wp/v2/feedback'
259 );
260 $filters_path = '/wp/v2/feedback/filters';
261 $filters_locale_path = \add_query_arg( array( '_locale' => 'user' ), $filters_path );
262 $preload_paths = array(
263 '/wp/v2/types?context=view',
264 '/wp/v2/feedback/config',
265 '/wp/v2/feedback/integrations-metadata',
266 '/wp/v2/feedback/counts',
267 $filters_path,
268 $filters_locale_path,
269 $initial_responses_path,
270 $initial_responses_locale_path,
271 );
272
273 // Only preload the Forms list endpoint when centralized form management is enabled.
274 if ( Contact_Form_Plugin::has_editor_feature_flag( 'central-form-management' ) ) {
275 $forms_preload_params = array(
276 'context' => 'edit',
277 'page' => 1,
278 'jetpack_forms_context' => 'dashboard',
279 'order' => 'desc',
280 'orderby' => 'modified',
281 'per_page' => 20,
282 'status' => 'publish,draft,pending,future,private',
283 );
284 ksort( $forms_preload_params );
285 $preload_paths[] = add_query_arg( $forms_preload_params, '/wp/v2/jetpack-forms' );
286 $preload_paths[] = add_query_arg(
287 array_merge(
288 $forms_preload_params,
289 array( '_locale' => 'user' )
290 ),
291 '/wp/v2/jetpack-forms'
292 );
293 $preload_paths[] = '/wp/v2/jetpack-forms/status-counts';
294 $preload_paths[] = add_query_arg( array( '_locale' => 'user' ), '/wp/v2/jetpack-forms/status-counts' );
295 }
296 $preload_data_raw = array_reduce( $preload_paths, 'rest_preload_api_request', array() );
297
298 // Normalize keys to match what apiFetch will request (without domain).
299 $preload_data = array();
300 foreach ( $preload_data_raw as $key => $value ) {
301 $normalized_key = preg_replace( '#^https?://[^/]+/wp-json#', '', $key );
302 $preload_data[ $normalized_key ] = $value;
303 }
304
305 wp_add_inline_script(
306 $inline_handle,
307 sprintf(
308 'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );',
309 wp_json_encode( $preload_data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP )
310 ),
311 $preload_position
312 );
313 }
314
315 /**
316 * Whether the current request targets the Forms dashboard page.
317 *
318 * @return bool
319 */
320 public static function is_wp_build_dashboard_page() {
321 return self::get_admin_query_page() === self::FORMS_WPBUILD_ADMIN_SLUG;
322 }
323
324 /**
325 * Register the dashboard admin submenu Forms under Jetpack menu.
326 */
327 public function add_admin_submenu() {
328 // Report a missing build here rather than only on the page itself, so a partial
329 // deploy shows up on the first admin request instead of waiting for someone to
330 // open Forms. Keyed on the file and not on the generated callback: load_wp_build()
331 // only requires build.php on the Forms page, so the callback is legitimately
332 // absent on every other admin screen, which runs this method too.
333 if ( ! file_exists( self::wp_build_index_path() ) ) {
334 _doing_it_wrong(
335 __METHOD__,
336 '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.',
337 ''
338 );
339 }
340
341 // `jetpack_forms_jetpack_forms_responses_wp_admin_render_page` is the callback generated
342 // by the WP build script, named after the page slug. It only exists once `build/build.php`
343 // is loaded. Without it the page has nothing to render, so show an explanation.
344 $callback = function_exists( 'jetpack_forms_jetpack_forms_responses_wp_admin_render_page' )
345 ? 'jetpack_forms_jetpack_forms_responses_wp_admin_render_page'
346 : array( $this, 'render_wp_build_unavailable' );
347
348 Admin_Menu::add_menu(
349 /** "Jetpack Forms" and "Forms" are product names, do not translate. */
350 'Jetpack Forms',
351 'Forms',
352 'edit_pages',
353 self::FORMS_WPBUILD_ADMIN_SLUG,
354 $callback,
355 null,
356 // The key is not the slug: the page's URL still reads
357 // jetpack-forms-responses-wp-admin, which FORMS-795 tracks separately.
358 array(
359 'product' => 'jetpack-forms',
360 'key' => 'jetpack-forms',
361 )
362 );
363 }
364
365 /**
366 * Render the legacy dashboard mount point.
367 *
368 * Nothing registers this any more — the legacy dashboard was retired and its bundle
369 * is no longer enqueued, so the container it prints stays empty. Kept, and left
370 * printing the same markup, so any caller outside this package behaves as before.
371 *
372 * @deprecated 8.1.0 The legacy dashboard was retired.
373 */
374 public function render_dashboard() {
375 _deprecated_function( __METHOD__, 'jetpack-forms-8.1.0' );
376 ?>
377 <div id="jp-forms-dashboard"></div>
378 <?php
379 }
380
381 /**
382 * Render an error notice when the wp-build dashboard cannot render.
383 *
384 * The wp-build dashboard renders through a callback generated into `build/build.php`.
385 * That file is missing when the package ships without a complete build, and it is
386 * never loaded when a host application filters `jetpack_forms_load_wp_build` to false.
387 * So report the problem instead of rendering a blank page.
388 *
389 * @since 7.25.0
390 */
391 public function render_wp_build_unavailable() {
392 ?>
393 <div class="wrap">
394 <?php /* "Jetpack Forms" is a product name, do not translate. */ ?>
395 <h1>Jetpack Forms</h1>
396 <div class="notice notice-error">
397 <p><?php esc_html_e( 'The Forms dashboard is missing the files it needs to load.', 'jetpack-forms' ); ?></p>
398 <p><?php esc_html_e( 'Reinstalling or updating the plugin usually fixes this. If this site is configured not to load the Forms dashboard, contact your site administrator or host.', 'jetpack-forms' ); ?></p>
399 </div>
400 </div>
401 <?php
402 }
403
404 /**
405 * Returns true if there are any feedback posts on the site.
406 *
407 * @return boolean
408 */
409 public function has_feedback() {
410 $posts = new \WP_Query(
411 array(
412 'post_type' => 'feedback',
413 'post_status' => array( 'publish', 'draft', 'spam', 'trash' ),
414 'posts_per_page' => 1,
415 'fields' => 'ids',
416 'no_found_rows' => true,
417 'update_post_meta_cache' => false,
418 'update_post_term_cache' => false,
419 'suppress_filters' => true,
420 )
421 );
422 return $posts->have_posts();
423 }
424
425 /**
426 * Option name for storing classic forms state.
427 */
428 const CLASSIC_FORMS_OPTION = 'jetpack_forms_classic_state';
429
430 /**
431 * Classic forms state: site has classic (non-synced) form submissions.
432 */
433 const CLASSIC_FORMS_STATE_CLASSIC = 'classic';
434
435 /**
436 * Classic forms state: no classic form submissions detected.
437 */
438 const CLASSIC_FORMS_STATE_HIDDEN = 'hidden';
439
440 /**
441 * Classic forms state: user dismissed the classic forms notice.
442 */
443 const CLASSIC_FORMS_STATE_DISMISSED = 'dismissed';
444
445 /**
446 * Returns the classic forms state for the current site.
447 *
448 * Returns 'classic' if the site has form submissions (feedback posts) that were not
449 * created by a synced/reusable jetpack_form, 'dismissed' if the user dismissed the
450 * classic forms notice, or 'hidden' otherwise.
451 *
452 * The result is persisted in a WP option so the detection query only runs once per site.
453 * After that, the cached value is returned on every subsequent call. The cache is also
454 * updated eagerly via mark_classic_form_detected() when new classic submissions arrive.
455 *
456 * @since 7.14.0
457 *
458 * @return string 'classic', 'hidden', or 'dismissed'.
459 */
460 public function get_classic_forms_state() {
461 $state = get_option( self::CLASSIC_FORMS_OPTION );
462
463 if ( $state ) {
464 return $state;
465 }
466
467 $state = $this->detect_classic_forms();
468 update_option( self::CLASSIC_FORMS_OPTION, $state, false );
469
470 return $state;
471 }
472
473 /**
474 * Detects whether any feedback posts exist that are not linked to a jetpack_form post,
475 * indicating the site has classic (inline, widget, or template) forms.
476 *
477 * A feedback post is considered "classic" if:
478 * - It has no parent (post_parent = 0), meaning it was created by a form embedded in a
479 * widget, page template, or other non-post context.
480 * - Its parent exists but is not a jetpack_form post, meaning it was created by a form
481 * block or shortcode placed directly in a post or page.
482 *
483 * The query uses a LEFT JOIN on the posts table to find feedback posts with no matching
484 * jetpack_form parent. This leverages the primary key index for the join and the
485 * type_status_date index for filtering by post_type, making it efficient even on large
486 * sites. The LIMIT 1 ensures early exit as soon as one classic form is found.
487 *
488 * Note: An alternative approach would be to search post_content for the form block markup
489 * (<!-- wp:jetpack/contact-form) or shortcode ([contact-form]). However, that requires a
490 * full-text scan of the posts table (LIKE '%...%' on a TEXT column) with no usable index,
491 * making it significantly more expensive. The feedback-based approach also better fits the
492 * use case: we only need to surface the "Not seeing all your forms?" prompt when there are
493 * actual submissions that won't appear under any synced form in the dashboard.
494 *
495 * @since 7.14.0
496 *
497 * @return string 'classic' or 'hidden'.
498 */
499 private function detect_classic_forms() {
500 global $wpdb;
501
502 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
503 $result = $wpdb->get_var(
504 $wpdb->prepare(
505 "SELECT 1 FROM {$wpdb->posts} AS f
506 LEFT JOIN {$wpdb->posts} AS p
507 ON p.ID = f.post_parent AND p.post_type = %s
508 WHERE f.post_type = 'feedback'
509 AND p.ID IS NULL
510 LIMIT 1",
511 Contact_Form::POST_TYPE
512 )
513 );
514
515 return $result ? self::CLASSIC_FORMS_STATE_CLASSIC : self::CLASSIC_FORMS_STATE_HIDDEN;
516 }
517
518 /**
519 * Eagerly marks the site as having classic forms by setting the option to 'classic'.
520 *
521 * Called when a new form submission is saved that does not belong to a synced jetpack_form.
522 * This avoids re-running the detection query — once a classic submission is observed, the
523 * state is permanently set without needing to scan the database again.
524 *
525 * If the user has already dismissed the classic forms notice, the state is left as
526 * 'dismissed' so the notice does not reappear.
527 *
528 * @since 7.14.0
529 */
530 public static function mark_classic_form_detected() {
531 $current = get_option( self::CLASSIC_FORMS_OPTION );
532
533 if ( self::CLASSIC_FORMS_STATE_DISMISSED === $current ) {
534 return;
535 }
536
537 update_option( self::CLASSIC_FORMS_OPTION, self::CLASSIC_FORMS_STATE_CLASSIC, false );
538 }
539
540 /**
541 * Returns url of forms admin page.
542 *
543 * @param string|null $tab Tab to open in the forms admin page.
544 * @param int|null $post_id Post ID of response to open in the forms responses page.
545 *
546 * @return string
547 */
548 public static function get_forms_admin_url( $tab = null, $post_id = null ) {
549 $url = admin_url( 'admin.php' );
550 $url .= '?page=' . self::FORMS_WPBUILD_ADMIN_SLUG;
551 $url .= '&p=' . rawurlencode( self::get_forms_admin_path_wp_build( $tab, $post_id ) );
552
553 /**
554 * Filters the Forms admin page URL.
555 *
556 * @module contact-form
557 * @since 7.8.0
558 *
559 * @param string $url The Forms admin page URL.
560 * @param string|null $tab Tab to open in the forms admin page.
561 * @param int|null $post_id Post ID of response to open in the forms responses page.
562 *
563 * @return string The filtered Forms admin page URL.
564 */
565 return apply_filters( 'jetpack_forms_admin_url', $url, $tab, $post_id );
566 }
567
568 /**
569 * Returns the URL of the standalone single response page for a given response.
570 *
571 * The standalone page is a wp-build route (`/response/<id>`). A missing or empty
572 * post ID falls back to the responses list.
573 *
574 * @since 7.25.0
575 *
576 * @param int|null $post_id Post ID of the response to open.
577 *
578 * @return string
579 */
580 public static function get_single_response_admin_url( $post_id = null ) {
581 $post_id = ! empty( $post_id ) ? absint( $post_id ) : null;
582
583 return self::get_forms_admin_url( $post_id ? 'response' : 'inbox', $post_id );
584 }
585
586 /**
587 * The tab the standalone Forms page opens on when the URL names no route.
588 *
589 * A cookie, not a user preference: this redirect runs before any script could read one.
590 *
591 * @return string Tab slug understood by get_forms_admin_url().
592 */
593 public static function get_default_tab() {
594 if ( ! Contact_Form_Plugin::has_editor_feature_flag( 'central-form-management' ) ) {
595 // The Forms tab is not rendered at all here, so honouring a stored 'forms'
596 // would strand the user on a tab they cannot leave.
597 return 'inbox';
598 }
599
600 $remembered = sanitize_key( wp_unslash( $_COOKIE[ self::LAST_TAB_COOKIE ] ?? '' ) );
601
602 return in_array( $remembered, self::TOP_TABS, true ) ? $remembered : 'forms';
603 }
604
605 /**
606 * WP-Build path for the forms admin URL.
607 *
608 * @param string|null $tab Tab to open.
609 * @param int|null $post_id Post ID of response.
610 * @return string URL path (e.g. '/', '/responses/inbox', '/forms').
611 */
612 private static function get_forms_admin_path_wp_build( $tab, $post_id ) {
613 $post_id = ! empty( $post_id ) ? absint( $post_id ) : null;
614 $response_ids = ! empty( $post_id ) ? '?responseIds=["' . $post_id . '"]' : '';
615
616 // The standalone single response page, which addresses the response by path
617 // rather than selecting it in a list.
618 if ( $tab === 'response' && ! empty( $post_id ) ) {
619 return '/response/' . $post_id;
620 }
621
622 $path_map = array(
623 'inbox' => '/responses/inbox',
624 'spam' => '/responses/spam',
625 'trash' => '/responses/trash',
626 'forms' => '/forms',
627 'responses' => '/responses/inbox',
628 'responses/inbox' => '/responses/inbox',
629 );
630
631 if ( $tab !== null && $tab !== '' && isset( $path_map[ $tab ] ) ) {
632 return $path_map[ $tab ] . $response_ids;
633 }
634
635 if ( ! empty( $post_id ) ) {
636 return '/responses/inbox?responseIds=["' . $post_id . '"]';
637 }
638
639 return '/responses/inbox';
640 }
641
642 /**
643 * Returns true if the current screen is the Jetpack Forms admin page.
644 *
645 * @return boolean
646 */
647 public static function is_jetpack_forms_admin_page() {
648 if ( ! function_exists( 'get_current_screen' ) ) {
649 return false;
650 }
651
652 $screen = get_current_screen();
653
654 if ( ! $screen || ! isset( $screen->id ) ) {
655 return false;
656 }
657
658 return $screen->id === 'jetpack_page_' . self::FORMS_WPBUILD_ADMIN_SLUG;
659 }
660
661 /**
662 * Returns true if form notes feature is enabled.
663 *
664 * @return boolean
665 */
666 public static function is_notes_enabled() {
667 /**
668 * Enable form notes feature in Jetpack Forms .
669 *
670 * @module contact-form
671 * @since 7.3.0
672 *
673 * @param bool $enabled Should the form notes feature be enabled? Defaults to false.
674 */
675 return apply_filters( 'jetpack_forms_notes_enable', false );
676 }
677
678 /**
679 * Get admin URL for given screen ID.
680 *
681 * @deprecated 7.9.0 Use Dashboard::get_forms_admin_url() instead.
682 *
683 * @param string $screen_id Screen ID.
684 * @return string Admin URL.
685 */
686 public static function get_admin_url( $screen_id ) {
687 _deprecated_function( __METHOD__, 'jetpack-7.9.0', 'Dashboard::get_forms_admin_url' );
688
689 if ( 'edit-jetpack_form' === $screen_id ) {
690 return self::get_forms_admin_url( 'forms' );
691 }
692
693 if ( 'edit-feedback' === $screen_id ) {
694 return self::get_forms_admin_url( 'inbox' );
695 }
696
697 return self::get_forms_admin_url();
698 }
699 }
700