Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
76.60% covered (warning)
76.60%
36 / 47
81.25% covered (warning)
81.25%
13 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Forms
76.60% covered (warning)
76.60%
36 / 47
81.25% covered (warning)
81.25%
13 / 16
33.01
0.00% covered (danger)
0.00%
0 / 1
 register_feature_flags
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 load_contact_form
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
4.00
 plugin_url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 assets_url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_feedback_dashboard_enabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_legacy_menu_item_retired
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_mailpoet_enabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_hostinger_reach_enabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_integrations_enabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_conditional_logic_enabled
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 is_slack_enabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_webhooks_enabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 should_honor_content_destinations
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 show_dashboard_integrations
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 show_block_integrations
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 show_integration_icons
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Package description here
4 *
5 * @package automattic/jetpack-forms
6 */
7
8namespace Automattic\Jetpack\Forms;
9
10use Automattic\Jetpack\Current_Plan;
11use Automattic\Jetpack\Feature_Flags\Feature_Flags;
12use Automattic\Jetpack\Forms\ContactForm\Feedback_Source;
13use Automattic\Jetpack\Forms\ContactForm\Util;
14use Automattic\Jetpack\Forms\Dashboard\Dashboard;
15/**
16 * Understands the Jetpack Forms package.
17 */
18class Jetpack_Forms {
19
20    const PACKAGE_VERSION = '8.3.4';
21
22    /**
23     * Plan feature slug gating field conditional logic.
24     */
25    const CONDITIONAL_LOGIC_FEATURE = 'form-conditional-logic';
26
27    /**
28     * Name of the feature flag gating the Slack integration.
29     */
30    const SLACK_FLAG = 'forms-slack';
31
32    /**
33     * Register the package's feature flags.
34     *
35     * Registration is unconditional and happens as the package loads, so the full set stays
36     * discoverable through `Feature_Flags::all()` and nothing can call `is_enabled()` on an
37     * unregistered flag.
38     *
39     * @return void
40     */
41    public static function register_feature_flags() {
42        Feature_Flags::register(
43            self::SLACK_FLAG,
44            array(
45                'default'     => false,
46                'description' => 'Post new form responses to a Slack channel.',
47                'owner'       => 'jetpack-forms',
48            )
49        );
50    }
51
52    /**
53     * Load the contact form module.
54     */
55    public static function load_contact_form() {
56        self::register_feature_flags();
57
58        Util::init();
59
60        if ( self::is_feedback_dashboard_enabled() ) {
61            $dashboard = new Dashboard();
62            $dashboard->init();
63        }
64
65        if ( is_admin() && apply_filters_deprecated( 'tmp_grunion_allow_editor_view', array( true ), '0.30.5', '', 'This functionality will be removed in an upcoming version.' ) ) {
66            add_action( 'current_screen', '\Automattic\Jetpack\Forms\ContactForm\Editor_View::add_hooks' );
67        }
68
69        add_action( 'init', '\Automattic\Jetpack\Forms\ContactForm\Util::register_pattern' );
70
71        // Add hook to delete file attachments when a feedback post is deleted
72        add_action( 'before_delete_post', array( '\Automattic\Jetpack\Forms\ContactForm\Contact_Form', 'delete_feedback_files' ) );
73
74        // Invalidate the source post IDs cache when a feedback post is permanently deleted.
75        add_action( 'deleted_post', array( '\Automattic\Jetpack\Forms\ContactForm\Feedback', 'invalidate_source_ids_cache_on_delete' ), 10, 2 );
76
77        // Enforces the availability of block support controls in the UI for classic themes.
78        add_filter( 'wp_theme_json_data_default', array( '\Automattic\Jetpack\Forms\ContactForm\Contact_Form', 'add_theme_json_data_for_classic_themes' ) );
79
80        // Register the bundled integrations. Deferred to `init` because their titles are
81        // translated and this runs before WordPress can translate; nothing reads the
82        // registry earlier. Third-party integrations may register at any time.
83        add_action( 'init', array( '\Automattic\Jetpack\Forms\Integrations\Built_In_Integrations', 'register' ) );
84
85        // Hand new responses to the integrations that asked for them.
86        \Automattic\Jetpack\Forms\Integrations\Integration_Dispatcher::init();
87
88        // Load the scripts integrations supply their settings UI from.
89        \Automattic\Jetpack\Forms\Integrations\Integration_Assets::init();
90
91        // Initialize abilities registration for WordPress Abilities API (WP 6.9+)
92        \Automattic\Jetpack\Forms\Abilities\Forms_Abilities::init();
93    }
94
95    /**
96     * Get the plugin URL.
97     */
98    public static function plugin_url() {
99        return plugin_dir_url( __FILE__ );
100    }
101
102    /**
103     * Get the assets URL.
104     */
105    public static function assets_url() {
106        return plugin_dir_url( __DIR__ ) . 'assets';
107    }
108
109    /**
110     * Returns true if the feedback dashboard is enabled.
111     *
112     * @return boolean
113     */
114    public static function is_feedback_dashboard_enabled() {
115        /**
116         * Enable the Jetpack Forms dashboard.
117         *
118         * Returning false hides the Forms admin menu entry and skips the dashboard
119         * entirely. Unrelated to which dashboard renders — there is only one.
120         *
121         * @module contact-form
122         * @since 0.3.0
123         *
124         * @param bool $enabled Should the Jetpack Forms dashboard be enabled? Default true.
125         */
126        return apply_filters( 'jetpack_forms_dashboard_enable', true );
127    }
128
129    /**
130     * Returns true if the legacy menu item is retired.
131     *
132     * @return boolean
133     */
134    public static function is_legacy_menu_item_retired() {
135        return apply_filters( 'jetpack_forms_retire_legacy_menu_item', true );
136    }
137
138    /**
139     * Returns true if MailPoet integration is enabled.
140     *
141     * @return boolean
142     */
143    public static function is_mailpoet_enabled() {
144        /**
145         * Enable MailPoet integration.
146         *
147         * @param bool false Whether MailPoet integration be enabled. Default is false.
148         */
149        return apply_filters( 'jetpack_forms_mailpoet_enable', true );
150    }
151
152    /**
153     * Returns true if Hostinger Reach integration is enabled.
154     *
155     * @return boolean
156     */
157    public static function is_hostinger_reach_enabled() {
158        /**
159         * Enable Hostinger Reach integration.
160         *
161         * @param bool false Whether Hostinger Reach integration be enabled. Default is false.
162         */
163        return apply_filters( 'jetpack_forms_hostinger_reach_enable', false );
164    }
165
166    /**
167     * Returns true if the Integrations UI should be enabled.
168     *
169     * @return boolean
170     */
171    public static function is_integrations_enabled() {
172        /**
173         * Whether to enable the Integrations UI.
174         *
175         * @param bool true Whether to enable the Integrations UI. Default true.
176         */
177        return apply_filters( 'jetpack_forms_is_integrations_enabled', true );
178    }
179
180    /**
181     * Returns true if the site's plan includes field conditional logic.
182     *
183     * One switch for the whole feature: the editor panel, the front-end show/hide, and the
184     * submission-time enforcement in validation and storage. Gating them together means a
185     * form can never hide a field from the visitor while still requiring it on submit.
186     *
187     * Losing the plan on a form that already has conditions is safe: the conditions are
188     * simply ignored, so every field renders, validates and stores as an ordinary field.
189     *
190     * @return boolean
191     */
192    public static function is_conditional_logic_enabled() {
193        // Fail closed on WordPress.com until its WPCOM_Features copy knows the slug, or supports()
194        // falls back to the Jetpack free plan, which includes it. Drop once that copy ships.
195        $supported = ( ! function_exists( 'wpcom_feature_exists' ) || wpcom_feature_exists( self::CONDITIONAL_LOGIC_FEATURE ) )
196            && Current_Plan::supports( self::CONDITIONAL_LOGIC_FEATURE );
197
198        /**
199         * Whether field conditional logic is available on this site.
200         *
201         * @since 8.3.0
202         *
203         * @param bool $supported Whether the site's plan includes conditional logic.
204         */
205        return (bool) apply_filters( 'jetpack_forms_conditional_logic_enabled', $supported );
206    }
207
208    /**
209     * Returns true if the Slack integration is enabled.
210     *
211     * One switch for the whole feature. With the flag off the integration is not registered as
212     * available, so it is absent from the REST responses and the integrations modal rather
213     * than shown in a disabled state, and no response is ever sent to Slack — including from a
214     * form that was configured while the flag was on.
215     *
216     * @return boolean
217     */
218    public static function is_slack_enabled() {
219        return Feature_Flags::is_enabled( self::SLACK_FLAG );
220    }
221
222    /**
223     * Returns true if webhooks are enabled.
224     *
225     * @return boolean
226     */
227    public static function is_webhooks_enabled() {
228        /**
229         * Whether to enable webhooks for Jetpack Forms.
230         *
231         * @param bool true Whether webhooks should be enabled. Default true.
232         */
233        return apply_filters( 'jetpack_forms_webhooks_enabled', true );
234    }
235
236    /**
237     * Whether author-configured outbound destinations from a form source should be honored.
238     *
239     * Destinations declared in the form content (webhooks, the legacy postToUrl attribute and
240     * the Salesforce integration) carry submission data to an outbound URL, so they are only
241     * honored when whoever placed the form had an administrator-level capability:
242     *
243     * - For post/page forms (`single`, numeric source id) the source post's author must have
244     *   the `manage_options` capability. Editor/Author/Contributor-authored forms are dropped.
245     * - For block templates, block template parts and widgets the source id is not a numeric
246     *   post, so there is no author to check. Editing any of those surfaces already requires
247     *   the `edit_theme_options` capability — administrator-tier, strictly above the
248     *   capabilities an Editor/Author/Contributor holds — so their destinations are trusted.
249     *
250     * The source type is established server-side (from the signed JWT, or by re-rendering the
251     * real template/template part on the legacy path) and is not forgeable by the submitter.
252     *
253     * Returns false for any other unresolved source (non-numeric id of an unknown type, or a
254     * numeric id with no admin-capable author).
255     *
256     * @param int|string $source_id   The form source id: a post id for post/page forms, or a
257     *                                non-numeric value for widget or block-template sources.
258     * @param string     $source_type The form source type: single, widget, block_template or
259     *                                block_template_part. Defaults to 'single'.
260     * @return boolean
261     */
262    public static function should_honor_content_destinations( $source_id, $source_type = 'single' ) {
263        // Block templates, template parts and widgets can only be authored by users with the
264        // administrator-tier `edit_theme_options` capability, so their destinations are trusted
265        // even though the source id is not a numeric post.
266        if ( in_array( $source_type, Feedback_Source::ADMIN_TIER_SOURCE_TYPES, true ) ) {
267            return true;
268        }
269
270        if ( ! is_numeric( $source_id ) ) {
271            return false;
272        }
273
274        $source_id = (int) $source_id;
275        if ( $source_id <= 0 ) {
276            return false;
277        }
278
279        $author_id = (int) get_post_field( 'post_author', $source_id );
280
281        return $author_id > 0 && user_can( $author_id, 'manage_options' );
282    }
283
284    /**
285     * Returns true if the Integrations UI should be shown in the Forms dashboard.
286     *
287     * @since 6.22.0
288     *
289     * @return boolean
290     */
291    public static function show_dashboard_integrations() {
292        /**
293         * Whether to show Integrations UI in the Forms dashboard.
294         *
295         * @since 6.22.0
296         *
297         * @param bool true Whether to show the Integrations UI in the dashboard. Default true.
298         */
299        return apply_filters( 'jetpack_forms_show_dashboard_integrations', true );
300    }
301
302    /**
303     * Returns true if the Integrations UI should be shown in the Form block editor.
304     *
305     * @since 6.22.0
306     *
307     * @return boolean
308     */
309    public static function show_block_integrations() {
310        /**
311         * Whether to show Integrations UI in the Form block editor.
312         *
313         * @since 6.22.0
314         *
315         * @param bool true Whether to show the Integrations UI in the editor. Default true.
316         */
317        return apply_filters( 'jetpack_forms_show_block_integrations', true );
318    }
319
320    /**
321     * Returns true if integration icons should be shown (editor sidebar and integrations modal).
322     *
323     * @since 6.22.0
324     *
325     * @return boolean
326     */
327    public static function show_integration_icons() {
328        /**
329         * Whether to show integration icons in the UI.
330         *
331         * If set to false, the ActiveIntegrations component (editor sidebar) will be hidden
332         * and integration icons in the integrations modal will not be rendered.
333         *
334         * @since 6.22.0
335         *
336         * @param bool true Whether to show integration icons. Default true.
337         */
338        return apply_filters( 'jetpack_forms_show_integration_icons', true );
339    }
340}