Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.62% covered (success)
90.62%
551 / 608
47.06% covered (danger)
47.06%
8 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
Contact_Form_Block
90.62% covered (success)
90.62%
551 / 608
47.06% covered (danger)
47.06%
8 / 17
48.82
0.00% covered (danger)
0.00%
0 / 1
 register_block
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 register_feature
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 find_nested_html_block
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
5
 render_wrapped_html_block
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 register_child_blocks
99.79% covered (success)
99.79%
482 / 483
0.00% covered (danger)
0.00%
0 / 1
2
 set_file_field_extension_available
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 pre_render_contact_form
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 count_form_steps_in_block
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 get_form_step_count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 render_fallback
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 render_email
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 gutenblock_render_form
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
7.99
 render_synced_form
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
6.01
 load_editor_styles
n/a
0 / 0
n/a
0 / 0
1
 load_editor_scripts
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
6
 preload_endpoints
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 load_view_scripts
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
2.00
 can_manage_block
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
1<?php
2/**
3 * Contact Form Block.
4 *
5 * @package automattic/jetpack
6 */
7
8namespace Automattic\Jetpack\Extensions\Contact_Form;
9
10use Automattic\Jetpack\Assets;
11use Automattic\Jetpack\Blocks;
12use Automattic\Jetpack\Current_Plan;
13use Automattic\Jetpack\Forms\ContactForm\Contact_Form;
14use Automattic\Jetpack\Forms\ContactForm\Contact_Form_Plugin;
15use Automattic\Jetpack\Forms\Dashboard\Dashboard as Forms_Dashboard;
16use Automattic\Jetpack\Forms\Jetpack_Forms;
17use Automattic\Jetpack\Modules;
18use Automattic\Jetpack\Status\Request;
19use Jetpack;
20
21/**
22 * Contact Form block render callback.
23 */
24class Contact_Form_Block {
25    /**
26     * Register the Contact Form block.
27     * We are core block dependent only on whether the jetpack contact form plugin
28     * is active or not. This is allowing us to make it more discoverable
29     * and enable the plugin in one click
30     */
31    public static function register_block() {
32        /*
33         * The block is available even when the module is not active,
34         * so we can display a nudge to activate the module instead of the block.
35         * However, since non-admins cannot activate modules, we do not display the empty block for them.
36         */
37        if ( ! self::can_manage_block() ) {
38            return;
39        }
40
41        Blocks::jetpack_register_block(
42            'jetpack/contact-form',
43            array(
44                'render_callback'       => array( __CLASS__, 'gutenblock_render_form' ),
45                'render_email_callback' => array( __CLASS__, 'render_email' ),
46            )
47        );
48
49        add_filter( 'render_block_data', array( __CLASS__, 'find_nested_html_block' ), 10, 3 );
50        add_filter( 'render_block_core/html', array( __CLASS__, 'render_wrapped_html_block' ), 10, 2 );
51        add_filter( 'jetpack_block_editor_feature_flags', array( __CLASS__, 'register_feature' ) );
52        add_filter( 'pre_render_block', array( __CLASS__, 'pre_render_contact_form' ), 10, 3 );
53
54        add_filter( 'block_editor_rest_api_preload_paths', array( __CLASS__, 'preload_endpoints' ) );
55
56        // Load scripts for the editing interface
57        add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'load_editor_scripts' ), 9 );
58    }
59    /**
60     * Register the contact form block feature flag.
61     *
62     * @param array $features - the features array.
63     *
64     * @return array
65     */
66    public static function register_feature( $features ) {
67        // Features that are only available to users with a paid plan.
68        $features['multistep-form'] = Current_Plan::supports( 'multistep-form' );
69        $features['form-webhooks']  = Current_Plan::supports( 'form-webhooks' );
70
71        return $features;
72    }
73
74    /**
75     *  Find nested html block that reside in the contact form block.
76     *  We are using this to wrap the html block with div if it is nested inside contact form block. So that the elements render as expected.
77     *
78     *  @param array  $parsed_block - the parsed block.
79     *  @param array  $source_block - the source block.
80     *  @param object $parent_block - the parent WP_Block.
81     *
82     *  @return array
83     */
84    public static function find_nested_html_block( $parsed_block, $source_block, $parent_block ) {
85        if ( ! empty( $parsed_block['blockName'] ) && $parsed_block['blockName'] === 'core/html' && isset( $parent_block->parsed_block ) && $parent_block->parsed_block['blockName'] === 'jetpack/contact-form' ) {
86            $parsed_block['hasJPFormParent'] = true;
87        }
88        return $parsed_block;
89    }
90
91    /**
92     * Render wrapped html block that is inside the form block with a wrapped div so that the elements render as expected.
93     * The extra div is needed because the form block has a `flex: 0 0 100%;` applied to all the children of the form block.
94     * This cases all the elementes inside the block to render in a single line and make it not possible to add have inline elements.
95     *
96     * @param string $content - the content of the block.
97     * @param array  $parsed_block - the parsed block.
98     *
99     * @return string
100     */
101    public static function render_wrapped_html_block( $content, $parsed_block ) {
102        if ( ! empty( $parsed_block['hasJPFormParent'] ) ) {
103            return '<div>' . $content . '</div>';
104        }
105
106        return $content;
107    }
108
109    /**
110     * Register the Child blocks of Contact Form
111     * We are registering child blocks only when Contact Form plugin is Active
112     */
113    public static function register_child_blocks() {
114        // Bail early if the user cannot manage the block.
115        if ( ! self::can_manage_block() ) {
116            return;
117        }
118
119        // Field inner block types.
120        Blocks::jetpack_register_block(
121            'jetpack/input',
122            array(
123                'supports'     => array(
124                    '__experimentalBorder' => array(
125                        'color'  => true,
126                        'radius' => true,
127                        'style'  => true,
128                        'width'  => true,
129                    ),
130                    'color'                => array(
131                        'text'       => true,
132                        'background' => true,
133                        'gradients'  => false,
134                    ),
135                    'typography'           => array(
136                        'fontSize'                     => true,
137                        'lineHeight'                   => true,
138                        '__experimentalFontFamily'     => true,
139                        '__experimentalFontWeight'     => true,
140                        '__experimentalFontStyle'      => true,
141                        '__experimentalTextTransform'  => true,
142                        '__experimentalTextDecoration' => true,
143                        '__experimentalLetterSpacing'  => true,
144                    ),
145                ),
146                'selectors'    => array(
147                    'border' => '.wp-block-jetpack-input, .is-style-outlined .notched-label:has(+ .wp-block-jetpack-input) > *,.is-style-outlined .wp-block-jetpack-input + .notched-label > *, .is-style-outlined .wp-block-jetpack-field-select .notched-label > *',
148                    'color'  => '.wp-block-jetpack-input, .is-style-outlined .notched-label:has(+ .wp-block-jetpack-input) > *,.is-style-outlined .wp-block-jetpack-input + .notched-label > *, .is-style-outlined .wp-block-jetpack-field-select .notched-label > *',
149                ),
150                'uses_context' => array( 'jetpack/field-default-value' ),
151            )
152        );
153        Blocks::jetpack_register_block(
154            'jetpack/label',
155            array(
156                'supports'     => array(
157                    'color'           => array(
158                        'text'       => true,
159                        'background' => false,
160                        'gradients'  => false,
161                    ),
162                    'typography'      => array(
163                        'fontSize'                     => true,
164                        'lineHeight'                   => true,
165                        '__experimentalFontFamily'     => true,
166                        '__experimentalFontWeight'     => true,
167                        '__experimentalFontStyle'      => true,
168                        '__experimentalTextTransform'  => true,
169                        '__experimentalTextDecoration' => true,
170                        '__experimentalLetterSpacing'  => true,
171                    ),
172                    'blockVisibility' => true,
173                ),
174                'uses_context' => array(
175                    'jetpack/field-required',
176                    'jetpack/field-date-format',
177                ),
178            )
179        );
180        Blocks::jetpack_register_block(
181            'jetpack/options',
182            array(
183                'supports'         => array(
184                    '__experimentalBorder' => array(
185                        'color'  => true,
186                        'radius' => true,
187                        'style'  => true,
188                        'width'  => true,
189                    ),
190                    'color'                => array(
191                        'text'       => false,
192                        'background' => true,
193                    ),
194                    'spacing'              => array(
195                        'blockGap' => false,
196                    ),
197                ),
198                'provides_context' => array(
199                    'jetpack/field-options-type' => 'type',
200                ),
201                'selectors'        => array(
202                    'border' => '.wp-block-jetpack-options, .is-style-outlined .notched-label:has(+ .wp-block-jetpack-options) > *',
203                    'color'  => '.wp-block-jetpack-options, .is-style-outlined .notched-label:has(+ .wp-block-jetpack-options) > *',
204                ),
205            )
206        );
207        Blocks::jetpack_register_block(
208            'jetpack/option',
209            array(
210                'supports'     => array(
211                    'color'      => array(
212                        'text'       => true,
213                        'background' => false,
214                        'gradients'  => false,
215                    ),
216                    'typography' => array(
217                        'fontSize'                     => true,
218                        'lineHeight'                   => true,
219                        '__experimentalFontFamily'     => true,
220                        '__experimentalFontWeight'     => true,
221                        '__experimentalFontStyle'      => true,
222                        '__experimentalTextTransform'  => true,
223                        '__experimentalTextDecoration' => true,
224                        '__experimentalLetterSpacing'  => true,
225                    ),
226                ),
227                'uses_context' => array(
228                    'jetpack/field-default-value',
229                    'jetpack/field-options-type',
230                    'jetpack/field-required',
231                ),
232            )
233        );
234
235        Blocks::jetpack_register_block(
236            'jetpack/phone-input',
237            array(
238                'supports'     => array(
239                    '__experimentalBorder' => array(
240                        'color'  => true,
241                        'radius' => true,
242                        'style'  => true,
243                        'width'  => true,
244                    ),
245                    'color'                => array(
246                        'text'       => true,
247                        'background' => true,
248                        'gradients'  => false,
249                    ),
250                    'typography'           => array(
251                        'fontSize'                     => true,
252                        'lineHeight'                   => true,
253                        '__experimentalFontFamily'     => true,
254                        '__experimentalFontWeight'     => true,
255                        '__experimentalFontStyle'      => true,
256                        '__experimentalTextTransform'  => true,
257                        '__experimentalTextDecoration' => true,
258                        '__experimentalLetterSpacing'  => true,
259                    ),
260                ),
261                'uses_context' => array(
262                    'jetpack/field-share-attributes',
263                    'jetpack/field-prefix-options',
264                    'jetpack/field-prefix-default',
265                    'jetpack/field-prefix-onChange',
266                    'jetpack/field-phone-country-toggle',
267                ),
268            )
269        );
270
271        Blocks::jetpack_register_block(
272            'jetpack/input-rating',
273            array(
274                'supports' => array(
275                    'color'      => array(
276                        'text'       => true,
277                        'background' => false,
278                    ),
279                    'typography' => array(
280                        'fontSize' => true,
281                    ),
282                ),
283            )
284        );
285
286        Blocks::jetpack_register_block(
287            'jetpack/input-range',
288            array(
289                'supports' => array(
290                    'color'      => array(
291                        'text'       => true,
292                        'background' => false,
293                    ),
294                    'typography' => array(
295                        'fontSize'                     => true,
296                        '__experimentalFontFamily'     => true,
297                        '__experimentalFontWeight'     => true,
298                        '__experimentalFontStyle'      => true,
299                        '__experimentalTextTransform'  => true,
300                        '__experimentalTextDecoration' => true,
301                        '__experimentalLetterSpacing'  => true,
302                    ),
303                ),
304            )
305        );
306
307        // Field render methods.
308        Blocks::jetpack_register_block(
309            'jetpack/field-text',
310            array(
311                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_text' ),
312                'provides_context' => array( 'jetpack/field-required' => 'required' ),
313            )
314        );
315        Blocks::jetpack_register_block(
316            'jetpack/field-name',
317            array(
318                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_name' ),
319                'provides_context' => array( 'jetpack/field-required' => 'required' ),
320            )
321        );
322        Blocks::jetpack_register_block(
323            'jetpack/field-email',
324            array(
325                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_email' ),
326                'provides_context' => array( 'jetpack/field-required' => 'required' ),
327            )
328        );
329        Blocks::jetpack_register_block(
330            'jetpack/field-url',
331            array(
332                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_url' ),
333                'provides_context' => array( 'jetpack/field-required' => 'required' ),
334            )
335        );
336        Blocks::jetpack_register_block(
337            'jetpack/field-date',
338            array(
339                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_date' ),
340                'provides_context' => array(
341                    'jetpack/field-required'    => 'required',
342                    'jetpack/field-date-format' => 'dateFormat',
343                ),
344            )
345        );
346        Blocks::jetpack_register_block(
347            'jetpack/field-telephone',
348            array(
349                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_telephone' ),
350                'attributes'       => array(
351                    'showCountrySelector' => array(
352                        'type' => 'boolean',
353                    ),
354                    'default'             => array(
355                        'type' => 'string',
356                        'role' => 'content',
357                    ),
358                    'searchPlaceholder'   => array(
359                        'type' => 'string',
360                        'role' => 'content',
361                    ),
362                ),
363                'supports'         => array(
364                    'interactivity' => true,
365                ),
366                'provides_context' => array(
367                    'jetpack/field-share-attributes'     => 'shareAttributes',
368                    'jetpack/field-required'             => 'required',
369                    'jetpack/field-prefix-default'       => 'default',
370                    'jetpack/field-phone-country-toggle' => 'showCountrySelector',
371                    'jetpack/field-phone-search-placeholder' => 'searchPlaceholder',
372                ),
373            )
374        );
375        Blocks::jetpack_register_block(
376            'jetpack/field-textarea',
377            array(
378                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_textarea' ),
379                'provides_context' => array( 'jetpack/field-required' => 'required' ),
380            )
381        );
382        Blocks::jetpack_register_block(
383            'jetpack/field-checkbox',
384            array(
385                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_checkbox' ),
386                'provides_context' => array(
387                    'jetpack/field-required'      => 'required',
388                    'jetpack/field-default-value' => 'defaultValue',
389                ),
390            )
391        );
392        Blocks::jetpack_register_block(
393            'jetpack/field-checkbox-multiple',
394            array(
395                'render_callback' => array( Contact_Form_Plugin::class, 'gutenblock_render_field_checkbox_multiple' ),
396            )
397        );
398
399        Blocks::jetpack_register_block(
400            'jetpack/field-option-checkbox',
401            array(
402                'render_callback' => array( Contact_Form_Plugin::class, 'gutenblock_render_field_option' ),
403            )
404        );
405
406        Blocks::jetpack_register_block(
407            'jetpack/field-radio',
408            array(
409                'render_callback' => array( Contact_Form_Plugin::class, 'gutenblock_render_field_radio' ),
410            )
411        );
412
413        Blocks::jetpack_register_block(
414            'jetpack/field-option-radio',
415            array(
416                'render_callback' => array( Contact_Form_Plugin::class, 'gutenblock_render_field_option' ),
417            )
418        );
419        Blocks::jetpack_register_block(
420            'jetpack/field-select',
421            array(
422                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_select' ),
423                'provides_context' => array( 'jetpack/field-required' => 'required' ),
424            )
425        );
426        Blocks::jetpack_register_block(
427            'jetpack/field-consent',
428            array(
429                'render_callback' => array( Contact_Form_Plugin::class, 'gutenblock_render_field_consent' ),
430            )
431        );
432
433        Blocks::jetpack_register_block(
434            'jetpack/field-number',
435            array(
436                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_number' ),
437                'provides_context' => array( 'jetpack/field-required' => 'required' ),
438            )
439        );
440
441        Blocks::jetpack_register_block(
442            'jetpack/field-file',
443            array(
444                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_file' ),
445                'provides_context' => array( 'jetpack/field-required' => 'required' ),
446                'plan_check'       => apply_filters( 'jetpack_unauth_file_upload_plan_check', true ),
447            )
448        );
449
450        Blocks::jetpack_register_block(
451            'jetpack/dropzone',
452            array(
453                'render_callback' => array( Contact_Form_Plugin::class, 'gutenblock_render_dropzone' ),
454            )
455        );
456
457        Blocks::jetpack_register_block(
458            'jetpack/field-hidden',
459            array(
460                'render_callback' => array( Contact_Form_Plugin::class, 'gutenblock_render_field_hidden' ),
461            )
462        );
463
464        Blocks::jetpack_register_block(
465            'jetpack/field-rating',
466            array(
467                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_rating' ),
468                'provides_context' => array(
469                    'jetpack/field-required' => 'required',
470                ),
471            )
472        );
473
474        Blocks::jetpack_register_block(
475            'jetpack/field-slider',
476            array(
477                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_slider' ),
478                'provides_context' => array( 'jetpack/field-required' => 'required' ),
479            )
480        );
481
482        Blocks::jetpack_register_block(
483            'jetpack/field-time',
484            array(
485                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_time' ),
486                'provides_context' => array( 'jetpack/field-required' => 'required' ),
487            )
488        );
489
490        // Paid file field block
491        add_action(
492            'jetpack_register_gutenberg_extensions',
493            array( __CLASS__, 'set_file_field_extension_available' )
494        );
495
496        /**
497         * The blocks 'jetpack/field-checkbox-multiple' and 'jetpack/field-radio' are wrapper blocks.
498         * Styles must be registered so that they are available to be overridden by the theme or global styles.
499         * Form field blocks define the block style via the settings in their index.js files.
500         * A follow up issue is to update them to use block.json files, which can be reused
501         * in both JS and PHP block registration.
502         */
503        register_block_style(
504            array( 'jetpack/field-checkbox-multiple', 'jetpack/field-radio' ),
505            array(
506                'name'       => 'list',
507                'label'      => __( 'List', 'jetpack-forms' ),
508                'is_default' => true,
509            )
510        );
511
512        register_block_style(
513            array( 'jetpack/field-checkbox-multiple', 'jetpack/field-radio' ),
514            array(
515                'name'  => 'button',
516                'label' => __( 'Button', 'jetpack-forms' ),
517            )
518        );
519
520        Blocks::jetpack_register_block(
521            'jetpack/form-step',
522            array(
523                'render_callback' => array( Contact_Form_Plugin::class, 'gutenblock_render_form_step' ),
524            )
525        );
526
527        Blocks::jetpack_register_block(
528            'jetpack/form-step-navigation',
529            array(
530                'render_callback' => array( Contact_Form_Plugin::class, 'gutenblock_render_form_step_navigation' ),
531            )
532        );
533
534        Blocks::jetpack_register_block(
535            'jetpack/form-progress-indicator',
536            array(
537                'render_callback' => array( Contact_Form_Plugin::class, 'gutenblock_render_form_progress_indicator' ),
538            )
539        );
540
541        Blocks::jetpack_register_block(
542            'jetpack/form-step-container'
543        );
544
545        Blocks::jetpack_register_block(
546            'jetpack/field-image-select',
547            array(
548                'render_callback'  => array( Contact_Form_Plugin::class, 'gutenblock_render_field_image_select' ),
549                'provides_context' => array(
550                    'jetpack/field-required' => 'required',
551                    'jetpack/field-image-select-show-labels' => 'showLabels',
552                    'jetpack/field-image-select-is-supersized' => 'isSupersized',
553                    'jetpack/field-image-select-is-multiple' => 'isMultiple',
554                    'jetpack/field-image-select-randomize-options' => 'randomizeOptions',
555                    'jetpack/field-image-select-show-other-option' => 'showOtherOption',
556                ),
557            )
558        );
559
560        Blocks::jetpack_register_block(
561            'jetpack/fieldset-image-options',
562            array(
563                'uses_context'     => array(
564                    'jetpack/field-image-select-is-supersized',
565                    'jetpack/field-image-select-is-multiple',
566                    'jetpack/field-share-attributes',
567                ),
568                'provides_context' => array(
569                    'jetpack/field-image-options-type' => 'type',
570                ),
571            )
572        );
573
574        Blocks::jetpack_register_block(
575            'jetpack/input-image-option',
576            array(
577                'supports'         => array(
578                    'color'                => array(
579                        'background'                    => true,
580                        'text'                          => true,
581                        'gradients'                     => false,
582                        '__experimentalDefaultControls' => array(
583                            'background' => true,
584                            'text'       => true,
585                        ),
586                    ),
587                    'typography'           => array(
588                        'fontSize'                      => true,
589                        'lineHeight'                    => true,
590                        '__experimentalFontFamily'      => true,
591                        '__experimentalFontWeight'      => true,
592                        '__experimentalFontStyle'       => true,
593                        '__experimentalTextTransform'   => true,
594                        '__experimentalTextDecoration'  => true,
595                        '__experimentalLetterSpacing'   => true,
596                        '__experimentalDefaultControls' => array(
597                            'fontSize' => true,
598                        ),
599                    ),
600                    '__experimentalBorder' => array(
601                        'color'                         => true,
602                        'radius'                        => true,
603                        'style'                         => true,
604                        'width'                         => true,
605                        '__experimentalDefaultControls' => array(
606                            'color'  => true,
607                            'radius' => true,
608                            'style'  => true,
609                            'width'  => true,
610                        ),
611                    ),
612                    'spacing'              => array(
613                        'margin'                        => true,
614                        'padding'                       => true,
615                        '__experimentalDefaultControls' => array(
616                            'margin'  => true,
617                            'padding' => true,
618                        ),
619                    ),
620                ),
621                'uses_context'     => array(
622                    'jetpack/field-image-select-is-supersized',
623                    'jetpack/field-image-select-show-labels',
624                    'jetpack/field-image-options-type',
625                    'jetpack/field-share-attributes',
626                ),
627                'provides_context' => array(
628                    'allowResize' => 'allowResize',
629                    'imageCrop'   => 'imageCrop',
630                    'fixedHeight' => 'fixedHeight',
631                ),
632            )
633        );
634    }
635
636    /**
637     * Set field-file extension available hook handler
638     */
639    public static function set_file_field_extension_available() {
640        if ( ! apply_filters( 'jetpack_unauth_file_upload_plan_check', true ) ) {
641            \Jetpack_Gutenberg::set_extension_available( 'field-file' );
642        }
643    }
644
645    /**
646     * Render the gutenblock form.
647     *
648     * @param array  $atts - the block attributes.
649     * @param string $content - html content.
650     *
651     * @return string
652     */
653    /**
654     * Static storage for form step count.
655     *
656     * @var int
657     */
658    private static $form_step_count = 1;
659
660    /**
661     * Hook into pre_render_block to count form steps before inner blocks render.
662     *
663     * @param string|null $pre_render   The pre-rendered content. Default null.
664     * @param array       $parsed_block The block being rendered.
665     * @return string|null
666     */
667    public static function pre_render_contact_form( $pre_render, $parsed_block ) {
668        // Only process contact form blocks
669        if ( ! isset( $parsed_block['blockName'] ) || $parsed_block['blockName'] !== 'jetpack/contact-form' ) {
670            return $pre_render;
671        }
672
673        // Count and store form steps
674        self::$form_step_count = self::count_form_steps_in_block( $parsed_block );
675
676        return $pre_render; // Don't actually pre-render, let normal rendering continue
677    }
678
679    /**
680     * Count form step blocks in a contact form block.
681     *
682     * @param array $block The contact form block.
683     * @return int Number of form steps found.
684     */
685    private static function count_form_steps_in_block( $block ) {
686        $step_count = 0;
687
688        if ( isset( $block['innerBlocks'] ) ) {
689            foreach ( $block['innerBlocks'] as $inner_block ) {
690                if ( $inner_block['blockName'] === 'jetpack/form-step' ) {
691                    ++$step_count;
692                }
693                // Also check nested blocks (like step containers)
694                $step_count += self::count_form_steps_in_block( $inner_block );
695            }
696        }
697
698        return $step_count;
699    }
700
701    /**
702     * Get the step count for forms (used by progress indicator).
703     *
704     * @return int The step count.
705     */
706    public static function get_form_step_count() {
707        return self::$form_step_count;
708    }
709
710    /**
711     * Render fallback for non-interactive contexts (email, feed, API, etc.).
712     *
713     * @param array $atts - the block attributes.
714     *
715     * @return string
716     */
717    private static function render_fallback( $atts ) {
718        return sprintf(
719            '<div class="%1$s"><a href="%2$s" target="_blank" rel="noopener noreferrer">%3$s</a></div>',
720            esc_attr( Blocks::classes( 'contact-form', $atts ) ),
721            esc_url( get_the_permalink() ),
722            esc_html__( 'Submit a form.', 'jetpack-forms' )
723        );
724    }
725
726    /**
727     * Render the contact form block for email contexts.
728     *
729     * This method is called by WordPress/WooCommerce email rendering system when a form block
730     * appears in email content. Forms are not interactive in email contexts, so we always return
731     * a fallback link that directs recipients to the form on the website.
732     *
733     * Note: The $block_content and $rendering_context parameters are required by the
734     * render_email_callback signature but are intentionally unused here. We only need the
735     * block attributes from $parsed_block to generate the fallback HTML, and we don't need
736     * to process the block content or use email-specific rendering context since we're
737     * always returning a simple fallback.
738     *
739     * @param string $block_content     The original block HTML content. Unused - we always return a fallback.
740     * @param array  $parsed_block      The parsed block data including attributes.
741     * @param object $rendering_context Email rendering context. Unused - not needed for fallback rendering.
742     *
743     * @return string HTML fallback with link to submit the form on the website.
744     */
745    public static function render_email( $block_content, array $parsed_block, $rendering_context ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
746        $atts = $parsed_block['attrs'] ?? array();
747
748        return self::render_fallback( $atts );
749    }
750
751    /**
752     * Render the gutenblock form.
753     *
754     * @param array  $atts - the block attributes.
755     * @param string $content - html content.
756     *
757     * @return string
758     */
759    public static function gutenblock_render_form( $atts, $content ) {
760        // We should not render block if the module is disabled on a site using the Jetpack plugin.
761        if ( class_exists( 'Jetpack' ) && ! ( new Modules() )->is_active( 'contact-form' ) ) {
762            return '';
763        }
764        // Render fallback in other contexts than frontend (i.e. feed, emails, API, etc.), unless the form is being submitted.
765        if ( ! Request::is_frontend() && ! isset( $_POST['contact-form-id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
766            return self::render_fallback( $atts );
767        }
768
769        self::load_view_scripts();
770
771        // Handle ref attribute - load form from jetpack-form post
772        if ( isset( $atts['ref'] ) ) {
773            $ref_id = absint( $atts['ref'] );
774            if ( $ref_id > 0 ) {
775                return self::render_synced_form( $ref_id );
776            } else {
777                return ''; // Invalid ref ID.
778            }
779        }
780
781        return Contact_Form::parse( $atts, do_blocks( $content ) );
782    }
783
784    /**
785     * Render a synced form by reference ID.
786     *
787     * @param int $ref_id The jetpack_form post ID.
788     * @return string Rendered form HTML.
789     */
790    private static function render_synced_form( $ref_id ) {
791        // Circular reference prevention.
792        static $seen_refs = array();
793
794        if ( isset( $seen_refs[ $ref_id ] ) ) {
795            // Return empty string to match other error cases and unit test expectations.
796            return '';
797        }
798
799        // Load the jetpack-form post.
800        $synced_form = get_post( $ref_id );
801
802        // Validate post.
803        if ( ! $synced_form || 'jetpack_form' !== $synced_form->post_type ) {
804            return '';
805        }
806
807        // Only render published forms statuses.
808        if ( ! in_array( $synced_form->post_status, array( 'publish' ), true ) ) {
809            return '';
810        }
811
812        // Mark as seen for circular reference prevention.
813        $seen_refs[ $ref_id ] = true;
814        Contact_Form::set_ref_id( $ref_id );
815        $output = '';
816        try {
817            // Parse and render blocks from post_content.
818            $blocks = parse_blocks( $synced_form->post_content );
819            foreach ( $blocks as $block ) {
820                $output .= render_block( $block );
821            }
822        } finally {
823            // Clean up.
824            unset( $seen_refs[ $ref_id ] );
825            Contact_Form::clear_ref_id();
826        }
827        return $output;
828    }
829
830    /**
831     * Load editor styles for the block.
832     *
833     * @deprecated $$next-version$$ This function is deprecated and will be removed in a future version.
834     */
835    public static function load_editor_styles() {
836        _deprecated_function( __FUNCTION__, 'jetpack-$$next-version$$' );
837    }
838
839    /**
840     * Loads scripts
841     */
842    public static function load_editor_scripts() {
843        global $post;
844        // Bail early if the user cannot manage the block.
845        if ( ! self::can_manage_block() ) {
846            return;
847        }
848
849        $handle = 'jp-forms-blocks';
850
851        Assets::register_script(
852            $handle,
853            '../../../dist/blocks/editor.js',
854            __FILE__,
855            array(
856                'dependencies' => array( 'jetpack-blocks-editor' ),
857                'in_footer'    => true,
858                'textdomain'   => 'jetpack-forms',
859                'enqueue'      => true,
860                'css_path'     => '../../../dist/blocks/editor.css',
861            )
862        );
863
864        // Create a Contact_Form instance to get the default values
865        $form_responses_url      = Forms_Dashboard::get_forms_admin_url();
866        $akismet_active_with_key = Jetpack::is_akismet_active();
867        $akismet_key_url         = admin_url( 'admin.php?page=akismet-key-config' );
868
869        $data = array(
870            'defaults' => array(
871                'to'                   => Contact_Form::get_default_to_for_editor( $post ),
872                'subject'              => Contact_Form::get_default_subject( array() ),
873                'formsResponsesUrl'    => $form_responses_url,
874                'akismetActiveWithKey' => $akismet_active_with_key,
875                'akismetUrl'           => $akismet_key_url,
876                'assetsUrl'            => Jetpack_Forms::assets_url(),
877                'isMailPoetEnabled'    => Jetpack_Forms::is_mailpoet_enabled(),
878            ),
879        );
880
881        wp_add_inline_script( $handle, 'window.jpFormsBlocks = ' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';', 'before' );
882    }
883
884    /**
885     * Add REST API endpoints to the block editor preload list.
886     *
887     * @param array $paths Existing paths to preload.
888     * @return array Updated paths to preload.
889     */
890    public static function preload_endpoints( $paths ) {
891        $paths[] = array( '/wp/v2/feedback/config', 'GET' );
892        $paths[] = array( '/wp/v2/feedback/config?_locale=user', 'GET' );
893        return $paths;
894    }
895
896    /**
897     * Loads scripts
898     */
899    public static function load_view_scripts() {
900        if ( is_admin() ) {
901            // A block's view assets will not be required in wp-admin.
902            return;
903        }
904
905        Assets::register_script(
906            'jp-forms-blocks',
907            '../../../dist/blocks/view.js',
908            __FILE__,
909            array(
910                'in_footer'  => true,
911                'strategy'   => 'defer',
912                'textdomain' => 'jetpack-forms',
913                'enqueue'    => true,
914            )
915        );
916    }
917
918    /**
919     * Check if the current user can view the block.
920     * Every user can see it if the Contact Form module is active,
921     * but if it is inactive, only admins can see it.
922     *
923     * This is only useful when the Contact Form package is used within the Jetpack plugin,
924     * where the module logic exists.
925     *
926     * @since 0.49.0
927     *
928     * @return bool
929     */
930    public static function can_manage_block() {
931        if (
932            /**
933             * Allow third-parties to override the form block's visibility.
934             *
935             * @since 0.49.0
936             *
937             * @module contact-form
938             *
939             * @param bool $can_manage_block Whether the current user can manage the block.
940             */
941            apply_filters( 'jetpack_contact_form_can_manage_block', false )
942        ) {
943            return true;
944        }
945
946        if ( ! class_exists( 'Jetpack' ) ) {
947            return true;
948        }
949
950        return ( new Modules() )->is_active( 'contact-form' ) || current_user_can( 'jetpack_activate_modules' );
951    }
952}