PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-forms / src / contact-form / class-util.php
jetpack / jetpack_vendor / automattic / jetpack-forms / src / contact-form Last commit date
css 2 months ago images 3 days ago js 2 months ago libs 3 months ago templates 4 months ago class-conditional-logic.php 3 days ago class-contact-form-endpoint.php 1 week ago class-contact-form-field.php 3 days ago class-contact-form-plugin.php 3 days ago class-contact-form-shortcode.php 2 months ago class-contact-form.php 3 days ago class-editor-view.php 6 months ago class-feedback-author.php 3 days ago class-feedback-email-renderer.php 3 days ago class-feedback-field.php 3 days ago class-feedback-source.php 1 month ago class-feedback.php 3 days ago class-form-preview.php 3 days ago class-form-submission-error.php 8 months ago class-jetpack-form-endpoint.php 1 month ago class-util.php 1 month ago trait-country-code-utils.php 7 months ago
class-util.php
562 lines
1 <?php
2 /**
3 * Contact_Form_Util class.
4 *
5 * @package automattic/jetpack-forms
6 */
7
8 namespace Automattic\Jetpack\Forms\ContactForm;
9
10 /**
11 * This class serves as a container for what previously were standalone grunion functions.
12 * In the long term we should aim to move things to other classes and gradually get rid of this rather than adding more.
13 */
14 class Util {
15
16 /**
17 * Saved values of the block_template global while core/post-content blocks render.
18 *
19 * A stack so nested or sequential post-content renders (e.g. a query loop) restore
20 * correctly. @see grunion_contact_form_suspend_block_template_id_in_post_content().
21 *
22 * @var array
23 */
24 private static $block_template_id_suspended = array();
25
26 /**
27 * Registers all relevant actions and filters for this class.
28 */
29 public static function init() {
30 add_filter( 'template_include', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_set_block_template_attribute' );
31
32 add_action( 'render_block_core_template_part_post', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_set_block_template_part_id_global' );
33 add_action( 'render_block_core_template_part_file', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_set_block_template_part_id_global' );
34 add_action( 'render_block_core_template_part_none', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_set_block_template_part_id_global' );
35 add_action( 'gutenberg_render_block_core_template_part_post', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_set_block_template_part_id_global' );
36 add_action( 'gutenberg_render_block_core_template_part_file', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_set_block_template_part_id_global' );
37 add_action( 'gutenberg_render_block_core_template_part_none', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_set_block_template_part_id_global' );
38
39 add_filter( 'render_block', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_unset_block_template_part_id_global', 10, 2 );
40 add_filter( 'widget_block_content', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_filter_widget_block_content', 1, 3 );
41
42 // Suspend the block_template global while core/post-content renders, so forms in the post
43 // body are attributed to the post (and gated on the author), not to the template.
44 add_filter( 'pre_render_block', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_suspend_block_template_id_in_post_content', 10, 2 );
45 add_filter( 'render_block', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_contact_form_restore_block_template_id_after_post_content', 10, 2 );
46
47 // Register the default for the `central-form-management` feature flag at bootstrap
48 // so that early callers of Contact_Form_Plugin::has_editor_feature_flag() — such as
49 // the Forms dashboard default-tab redirect, which runs before the `init` hook fires —
50 // see the correct value. Only the lightweight default is registered here; paid-plan
51 // flags stay in Contact_Form_Block::register_feature(), hooked later from
52 // Contact_Form_Block::register_block() on `init` priority 9.
53 add_filter( 'jetpack_block_editor_feature_flags', '\Automattic\Jetpack\Extensions\Contact_Form\Contact_Form_Block::register_central_form_management_default' );
54
55 add_action( 'init', '\Automattic\Jetpack\Forms\ContactForm\Contact_Form_Plugin::init', 9 );
56 add_action( 'grunion_scheduled_delete', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_delete_old_spam' );
57 add_action( 'grunion_scheduled_delete_temp', '\Automattic\Jetpack\Forms\ContactForm\Util::grunion_delete_old_temp_feedback' );
58 add_action( 'grunion_pre_message_sent', '\Automattic\Jetpack\Forms\ContactForm\Util::jetpack_tracks_record_grunion_pre_message_sent', 12, 3 );
59 }
60
61 /**
62 * Registers contact form block patterns.
63 */
64 public static function register_pattern() {
65 $category_slug = 'forms';
66 register_block_pattern_category( $category_slug, array( 'label' => __( 'Forms', 'jetpack-forms' ) ) );
67
68 $patterns = array(
69 'contact-form' => array(
70 'title' => __( 'Contact Form', 'jetpack-forms' ),
71 'blockTypes' => array( 'jetpack/contact-form' ),
72 'categories' => array( $category_slug ),
73 'content' => '<!-- wp:jetpack/contact-form -->
74 <div class="wp-block-jetpack-contact-form">
75 <!-- wp:jetpack/field-name {"required":true} /-->
76 <!-- wp:jetpack/field-email {"required":true} /-->
77 <!-- wp:jetpack/field-textarea /-->
78 <!-- wp:button {"tagName":"button","type":"submit"} -->
79 <div class="wp-block-button"><button type="submit" class="wp-block-button__link wp-element-button">Contact us</button></div>
80 <!-- /wp:button -->
81 </div>
82 <!-- /wp:jetpack/contact-form -->',
83 ),
84 'newsletter-form' => array(
85 'title' => __( 'Lead Capture Form', 'jetpack-forms' ),
86 'blockTypes' => array( 'jetpack/contact-form' ),
87 'categories' => array( $category_slug ),
88 'content' => '<!-- wp:jetpack/contact-form -->
89 <div class="wp-block-jetpack-contact-form">
90 <!-- wp:jetpack/field-name {"required":true} /-->
91 <!-- wp:jetpack/field-email {"required":true} /-->
92 <!-- wp:jetpack/field-consent /-->
93 <!-- wp:button {"tagName":"button","type":"submit"} -->
94 <div class="wp-block-button"><button type="submit" class="wp-block-button__link wp-element-button">Subscribe</button></div>
95 <!-- /wp:button -->
96 </div>
97 <!-- /wp:jetpack/contact-form -->',
98 ),
99 'rsvp-form' => array(
100 'title' => __( 'RSVP Form', 'jetpack-forms' ),
101 'blockTypes' => array( 'jetpack/contact-form' ),
102 'categories' => array( $category_slug ),
103 'content' => '<!-- wp:jetpack/contact-form {"subject":"A new RSVP from your website"} -->
104 <div class="wp-block-jetpack-contact-form">
105 <!-- wp:jetpack/field-name {"required":true} /-->
106 <!-- wp:jetpack/field-email {"required":true} /-->
107 <!-- wp:jetpack/field-radio {"label":"Attending?","required":true,"options":["Yes","No"]} /-->
108 <!-- wp:jetpack/field-textarea {"label":"Other Details"} /-->
109 <!-- wp:button {"tagName":"button","type":"submit"} -->
110 <div class="wp-block-button"><button type="submit" class="wp-block-button__link wp-element-button">Send RSVP</button></div>
111 <!-- /wp:button -->
112 </div>
113 <!-- /wp:jetpack/contact-form -->',
114 ),
115 'registration-form' => array(
116 'title' => __( 'Registration Form', 'jetpack-forms' ),
117 'blockTypes' => array( 'jetpack/contact-form' ),
118 'categories' => array( $category_slug ),
119 'content' => '<!-- wp:jetpack/contact-form {"subject":"A new registration from your website"} -->
120 <div class="wp-block-jetpack-contact-form">
121 <!-- wp:jetpack/field-name {"required":true} /-->
122 <!-- wp:jetpack/field-email {"required":true} /-->
123 <!-- wp:jetpack/field-telephone {"label":"Phone Number"} /-->
124 <!-- wp:jetpack/field-select {"label":"How did you hear about us?","options":["Search Engine","Social Media","TV","Radio","Friend or Family"]} /-->
125 <!-- wp:jetpack/field-textarea {"label":"Other Details"} /-->
126 <!-- wp:button {"tagName":"button","type":"submit"} -->
127 <div class="wp-block-button"><button type="submit" class="wp-block-button__link wp-element-button">Send</button></div>
128 <!-- /wp:button -->
129 </div>
130 <!-- /wp:jetpack/contact-form -->',
131 ),
132 'appointment-form' => array(
133 'title' => __( 'Appointment Form', 'jetpack-forms' ),
134 'blockTypes' => array( 'jetpack/contact-form' ),
135 'categories' => array( $category_slug ),
136 'content' => '<!-- wp:jetpack/contact-form {"subject":"A new appointment booked from your website"} -->
137 <div class="wp-block-jetpack-contact-form">
138 <!-- wp:jetpack/field-name {"required":true} /-->
139 <!-- wp:jetpack/field-email {"required":true} /-->
140 <!-- wp:jetpack/field-telephone {"required":true} /-->
141 <!-- wp:jetpack/field-date {"label":"Date","required":true} /-->
142 <!-- wp:jetpack/field-radio {"label":"Time","required":true,"options":["Morning","Afternoon"]} /-->
143 <!-- wp:jetpack/field-textarea {"label":"Notes"} /-->
144 <!-- wp:button {"tagName":"button","type":"submit"} -->
145 <div class="wp-block-button"><button type="submit" class="wp-block-button__link wp-element-button">Book Appointment</button></div>
146 <!-- /wp:button -->
147 </div>
148 <!-- /wp:jetpack/contact-form -->',
149 ),
150 'feedback-form' => array(
151 'title' => __( 'Feedback Form', 'jetpack-forms' ),
152 'blockTypes' => array( 'jetpack/contact-form' ),
153 'categories' => array( $category_slug ),
154 'content' => '<!-- wp:jetpack/contact-form {"subject":"New feedback received from your website"} -->
155 <div class="wp-block-jetpack-contact-form">
156 <!-- wp:jetpack/field-name {"required":true} /-->
157 <!-- wp:jetpack/field-email {"required":true} /-->
158 <!-- wp:jetpack/field-rating {"required":true} -->
159 <div><!-- wp:jetpack/label {"label":"Please rate our website"} /-->
160 <!-- wp:jetpack/input-rating /--></div>
161 <!-- /wp:jetpack/field-rating -->
162 <!-- wp:jetpack/field-textarea {"label":"How could we improve?"} /-->
163 <!-- wp:button {"tagName":"button","type":"submit"} -->
164 <div class="wp-block-button"><button type="submit" class="wp-block-button__link wp-element-button">Send Feedback</button></div>
165 <!-- /wp:button -->
166 </div>
167 <!-- /wp:jetpack/contact-form -->',
168 ),
169 'salesforce-lead-form' => array(
170 'title' => __( 'Salesforce Lead Form', 'jetpack-forms' ),
171 'blockTypes' => array( 'jetpack/contact-form' ),
172 'categories' => array( $category_slug ),
173 'content' => '<!-- wp:jetpack/contact-form {"formTitle":"Salesforce Lead Form"} -->
174 <div class="wp-block-jetpack-contact-form">
175 <!-- wp:jetpack/field-name {"label":"First Name","required":true,"id":"first_name"} /-->
176 <!-- wp:jetpack/field-name {"label":"Last Name","required":true,"id":"last_name"} /-->
177 <!-- wp:jetpack/field-email {"label":"Email","required":true,"id":"email"} /-->
178 <!-- wp:jetpack/field-telephone {"label":"Phone","id":"phone"} /-->
179 <!-- wp:jetpack/field-text {"label":"Company","id":"company"} /-->
180 <!-- wp:jetpack/field-text {"label":"Job Title","id":"title"} /-->
181 <!-- wp:button {"tagName":"button","type":"submit"} -->
182 <div class="wp-block-button"><button type="submit" class="wp-block-button__link wp-element-button">Submit</button></div>
183 <!-- /wp:button -->
184 </div>
185 <!-- /wp:jetpack/contact-form -->',
186 ),
187 );
188
189 foreach ( $patterns as $name => $pattern ) {
190 register_block_pattern( $name, $pattern );
191 }
192 }
193
194 /**
195 * Sets the 'block_template' attribute on all instances of wp:jetpack/contact-form in
196 * the $_wp_current_template_content global variable.
197 *
198 * The $_wp_current_template_content global variable is hydrated immediately prior to
199 * 'template_include' in wp-includes/template-loader.php.
200 *
201 * This fixes Contact Form Blocks added to FSE _templates_ (e.g. Single or 404).
202 *
203 * @param string $template Template to be loaded.
204 */
205 public static function grunion_contact_form_set_block_template_attribute( $template ) {
206 global $_wp_current_template_content;
207 if ( ! is_string( $template ) ) {
208 return $template;
209 }
210
211 if ( 'template-canvas.php' === basename( $template ) ) {
212 Contact_Form::style_on();
213 $_wp_current_template_content = self::grunion_contact_form_apply_block_attribute(
214 $_wp_current_template_content,
215 array(
216 'block_template' => 'canvas',
217 )
218 );
219
220 // Mark that we are rendering a block template, so forms in the template chrome are
221 // attributed to it. This global is the trusted signal Feedback_Source::get_current()
222 // uses for the block_template source type (the content attribute is not trusted). It
223 // is suspended while core/post-content renders so a form in the post body is not
224 // mistaken for a template-authored form.
225 global $_wp_current_template_id;
226 $GLOBALS['grunion_block_template_id'] = ! empty( $_wp_current_template_id ) ? $_wp_current_template_id : 'canvas';
227 }
228 return $template;
229 }
230
231 /**
232 * Sets the $grunion_block_template_part_id global.
233 *
234 * This is part of the fix for Contact Form Blocks added to FSE _template parts_ (e.g footer).
235 * The global is processed in Contact_Form::parse().
236 *
237 * @param string $template_part_id ID for the currently rendered template part.
238 */
239 public static function grunion_contact_form_set_block_template_part_id_global( $template_part_id ) {
240 $GLOBALS['grunion_block_template_part_id'] = $template_part_id;
241 }
242
243 /**
244 * Unsets the global when block is done rendering.
245 *
246 * @param string $content Rendered block content.
247 * @param array $block The full block, including name and attributes.
248 * @return string
249 */
250 public static function grunion_contact_form_unset_block_template_part_id_global( $content, $block ) {
251 if ( isset( $block['blockName'] )
252 && 'core/template-part' === $block['blockName']
253 && isset( $GLOBALS['grunion_block_template_part_id'] ) ) {
254 unset( $GLOBALS['grunion_block_template_part_id'] );
255 }
256 return $content;
257 }
258
259 /**
260 * Suspends the block_template global while a core/post-content block renders.
261 *
262 * The core/post-content block renders the post body, which may contain a contact form
263 * authored by a user without edit_theme_options. Such a form must be attributed to the post
264 * (and gated on the post author), not to the surrounding template, so the trusted
265 * block_template signal is removed for the duration of the render and restored afterwards.
266 *
267 * Hooked on `pre_render_block`; returns its first argument unchanged so rendering proceeds.
268 *
269 * @param string|null $pre_render The pre-rendered content. Default null.
270 * @param array $parsed_block The block being rendered.
271 * @return string|null Unchanged $pre_render.
272 */
273 public static function grunion_contact_form_suspend_block_template_id_in_post_content( $pre_render, $parsed_block ) {
274 if ( isset( $parsed_block['blockName'] ) && 'core/post-content' === $parsed_block['blockName'] ) {
275 self::$block_template_id_suspended[] = $GLOBALS['grunion_block_template_id'] ?? null;
276 unset( $GLOBALS['grunion_block_template_id'] );
277 }
278 return $pre_render;
279 }
280
281 /**
282 * Restores the block_template global once a core/post-content block has finished rendering.
283 *
284 * Counterpart to grunion_contact_form_suspend_block_template_id_in_post_content(). Hooked on
285 * `render_block`; returns the block content unchanged.
286 *
287 * @param string $content Rendered block content.
288 * @param array $block The full block, including name and attributes.
289 * @return string Unchanged $content.
290 */
291 public static function grunion_contact_form_restore_block_template_id_after_post_content( $content, $block ) {
292 if ( isset( $block['blockName'] )
293 && 'core/post-content' === $block['blockName']
294 && ! empty( self::$block_template_id_suspended ) ) {
295 $restored = array_pop( self::$block_template_id_suspended );
296 if ( null !== $restored ) {
297 $GLOBALS['grunion_block_template_id'] = $restored;
298 }
299 }
300 return $content;
301 }
302
303 /**
304 * Sets the 'widget' attribute on all instances of the contact form in the widget block.
305 *
306 * @param string $content Existing widget block content.
307 * @param array $instance Array of settings for the current widget.
308 * @param \WP_Widget_Block $widget Current Block widget instance.
309 * @return string
310 */
311 public static function grunion_contact_form_filter_widget_block_content( $content, $instance, $widget ) {
312 Contact_Form::style_on();
313 // Inject 'block_template' => <widget-id> into all instances of the contact form block.
314 return self::grunion_contact_form_apply_block_attribute(
315 $content,
316 array(
317 'widget' => $widget->id,
318 )
319 );
320 }
321
322 /**
323 * Deletes old spam feedbacks to keep the posts table size under control.
324 */
325 public static function grunion_delete_old_spam() {
326 global $wpdb;
327
328 $grunion_delete_limit = 100;
329
330 $now_gmt = current_time( 'mysql', true );
331 // Use the spam status changed date if available, otherwise fall back to post_date_gmt for backward compatibility
332 $sql = $wpdb->prepare(
333 "
334 SELECT p.`ID`
335 FROM $wpdb->posts p
336 LEFT JOIN $wpdb->postmeta pm ON p.`ID` = pm.`post_id` AND pm.`meta_key` = '_spam_status_changed_gmt'
337 WHERE DATE_SUB( %s, INTERVAL 15 DAY ) > COALESCE( pm.`meta_value`, p.`post_date_gmt` )
338 AND p.`post_type` = 'feedback'
339 AND p.`post_status` = 'spam'
340 LIMIT %d
341 ",
342 $now_gmt,
343 $grunion_delete_limit
344 );
345 $post_ids = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
346
347 foreach ( (array) $post_ids as $post_id ) {
348 // force a full delete, skip the trash
349 wp_delete_post( $post_id, true );
350 }
351
352 if (
353 /**
354 * Filter if the module run OPTIMIZE TABLE on the core WP tables.
355 *
356 * @module contact-form
357 *
358 * @since 1.3.1
359 * @since 6.4.0 Set to false by default.
360 *
361 * @param bool $filter Should Jetpack optimize the table, defaults to false.
362 */
363 apply_filters( 'grunion_optimize_table', false )
364 ) {
365 $wpdb->query( "OPTIMIZE TABLE $wpdb->posts" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
366 }
367
368 // if we hit the max then schedule another run
369 if ( count( $post_ids ) >= $grunion_delete_limit ) {
370 wp_schedule_single_event( time() + 700, 'grunion_scheduled_delete' );
371 }
372 }
373
374 /**
375 * Deletes old temp feedback to keep the posts table size under control.
376 *
377 * @since 6.5.0
378 */
379 public static function grunion_delete_old_temp_feedback() {
380 global $wpdb;
381
382 $grunion_delete_limit = 100;
383
384 $now_gmt = current_time( 'mysql', true );
385 $sql = $wpdb->prepare(
386 "
387 SELECT `ID`
388 FROM $wpdb->posts
389 WHERE DATE_SUB( %s, INTERVAL 1 DAY ) > `post_date_gmt`
390 AND `post_type` = 'feedback'
391 AND `post_status` = 'jp-temp-feedback'
392 LIMIT %d
393 ",
394 $now_gmt,
395 $grunion_delete_limit
396 );
397
398 // The SQL query is already prepared with $wpdb->prepare() above, and direct query is needed for performance-critical cleanup operation
399 $post_ids = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
400
401 foreach ( (array) $post_ids as $post_id ) {
402 // force a full delete, skip the trash
403 wp_delete_post( $post_id, true );
404 }
405
406 if (
407 /**
408 * Filter if the module run OPTIMIZE TABLE on the core WP tables.
409 *
410 * @module contact-form
411 *
412 * @since 6.5.0
413 *
414 * @param bool $filter Should Jetpack optimize the table, defaults to false.
415 */
416 apply_filters( 'grunion_optimize_table', false )
417 ) {
418 // OPTIMIZE TABLE is a MySQL-specific maintenance command that cannot be prepared and is only run when explicitly enabled via filter
419 $wpdb->query( "OPTIMIZE TABLE $wpdb->posts" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
420 }
421
422 // if we hit the max then schedule another run
423 if ( count( $post_ids ) >= $grunion_delete_limit ) {
424 wp_schedule_single_event( time() + 700, 'grunion_scheduled_delete_temp' );
425 }
426 }
427
428 /**
429 * Send an event to Tracks on form submission.
430 *
431 * @param int $post_id - the post_id for the CPT that is created.
432 * @param array $all_values - array containing all form fields.
433 * @param array $extra_values - array containing extra form metadata.
434 *
435 * @return null|void
436 */
437 public static function jetpack_tracks_record_grunion_pre_message_sent( $post_id, $all_values = array(), $extra_values = array() ) {
438 $post = get_post( $post_id );
439 if ( $post ) {
440 $extra = gmdate( 'Y-W', strtotime( $post->post_date_gmt ) );
441 } else {
442 $extra = 'no-post';
443 }
444
445 /** This action is documented in jetpack/modules/widgets/social-media-icons.php */
446 do_action( 'jetpack_bump_stats_extras', 'jetpack_forms_message_sent', $extra );
447
448 $form_type = isset( $extra_values['widget'] ) ? 'widget' : 'block';
449
450 $context = '';
451 if ( isset( $extra_values['block_template'] ) ) {
452 $context = 'template';
453 } elseif ( isset( $extra_values['block_template_part'] ) ) {
454 $context = 'template_part';
455 }
456
457 $plugin = Contact_Form_Plugin::init();
458
459 $plugin->record_tracks_event(
460 'jetpack_forms_message_sent',
461 array(
462 'post_id' => $post_id,
463 'form_type' => $form_type,
464 'context' => $context,
465 'has_consent' => empty( $all_values['email_marketing_consent'] ) ? 0 : 1,
466 )
467 );
468 }
469
470 /**
471 * Adds a given attribute to all instances of the Contact Form block.
472 *
473 * @param string $content Existing content to process.
474 * @param array $new_attr New attributes to add.
475 * @return string
476 */
477 public static function grunion_contact_form_apply_block_attribute( $content, $new_attr ) {
478 if ( ! is_string( $content ) ) {
479 // If the content is not a string, we cannot process it.
480 return $content;
481 }
482
483 if ( false === stripos( $content, 'wp:jetpack/contact-form' ) ) {
484 return $content;
485 }
486
487 // Parse blocks using WordPress core function.
488 $blocks = parse_blocks( $content );
489
490 // Recursively modify contact form blocks.
491 $modified_blocks = self::modify_contact_form_blocks_recursive( $blocks, $new_attr );
492
493 // Serialize back to block markup.
494 return serialize_blocks( $modified_blocks );
495 }
496
497 /**
498 * Recursively modifies contact form blocks to add new attributes.
499 *
500 * @param array $blocks Array of parsed blocks.
501 * @param array $new_attr New attributes to add.
502 * @return array Modified blocks array.
503 */
504 private static function modify_contact_form_blocks_recursive( $blocks, $new_attr ) {
505 foreach ( $blocks as &$block ) {
506 // Check if this is a contact form block.
507 if ( 'jetpack/contact-form' === $block['blockName'] ) {
508 // Merge new attributes with existing ones.
509 $block['attrs'] = array_merge(
510 $block['attrs'] ?? array(),
511 $new_attr
512 );
513 }
514
515 // Recursively process inner blocks.
516 if ( ! empty( $block['innerBlocks'] ) ) {
517 $block['innerBlocks'] = self::modify_contact_form_blocks_recursive(
518 $block['innerBlocks'],
519 $new_attr
520 );
521 }
522 }
523
524 return $blocks;
525 }
526
527 /**
528 * Get a filename for export tasks
529 *
530 * @param string $source The filtered source for exported data.
531 * @return string The filename without source nor date suffix.
532 */
533 public static function get_export_filename( $source = '' ) {
534 return $source === ''
535 ? sprintf(
536 /* translators: Site title, used to craft the export filename, eg "MySite - Jetpack Form Responses" */
537 __( '%s - Jetpack Form Responses', 'jetpack-forms' ),
538 sanitize_file_name( get_bloginfo( 'name' ) )
539 )
540 : sprintf(
541 /* translators: 1: Site title; 2: post title. Used to craft the export filename, eg "MySite - Jetpack Form Responses - Contact" */
542 __( '%1$s - Jetpack Form Responses - %2$s', 'jetpack-forms' ),
543 sanitize_file_name( get_bloginfo( 'name' ) ),
544 sanitize_file_name( html_entity_decode( $source, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) )
545 );
546 }
547
548 /**
549 * Ensures a field label ends with a colon, unless it ends with a question mark.
550 *
551 * @param string $label The field label.
552 * @return string The formatted label.
553 */
554 public static function maybe_add_colon_to_label( $label ) {
555 $formatted_label = $label ? $label : '';
556 // Special case for the Terms consent field block which a period after the label.
557 $formatted_label = str_ends_with( $formatted_label, '?' ) ? $formatted_label : rtrim( $formatted_label, ':.' ) . ':';
558
559 return $formatted_label;
560 }
561 }
562