PluginProbe
WPZOOM Forms – Drag & Drop Contact Form Builder for WordPress / trunk
WPZOOM Forms – Drag & Drop Contact Form Builder for WordPress vtrunk
2.0.9 2.0.8 2.0.7 2.0.6 2.0.5 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.2.0 1.2.1 1.2.10 All 43 releases
wpzoom-forms / wpzoom-forms.php

wpzoom-forms.php in WPZOOM Forms – Drag & Drop Contact Form Builder for WordPress trunk, at wpzoom-forms.php

3,621 lines 155.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPZOOM Forms - Custom forms for WordPress, by WPZOOM.
4 *
5 * @package WPZOOM_Forms
6 * @author WPZOOM
7 * @copyright 2023 WPZOOM
8 * @license GPL-2.0-or-later
9 *
10 * @wordpress-plugin
11 * Plugin Name: WPZOOM Forms
12 * Plugin URI: https://www.wpzoom.com/plugins/wpzoom-forms
13 * Description: Simple, user-friendly contact form plugin for WordPress with a dedicated drag-and-drop builder.
14 * Author: WPZOOM
15 * Author URI: https://www.wpzoom.com
16 * Version: 2.0.9
17 * License: GPL2+
18 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
19 */
20
21 // Exit if accessed directly
22 defined( 'ABSPATH' ) || exit;
23
24 // settings page url attribute
25 define( 'WPZOOM_FORMS_SETTINGS_PAGE', 'wpzf-settings' );
26
27 if ( ! defined( 'WPZOOM_FORMS_VERSION' ) ) {
28 define( 'WPZOOM_FORMS_VERSION', get_file_data( __FILE__, [ 'Version' ] )[0] ); // phpcs:ignore
29 }
30
31 define( 'WPZOOM_FORMS__FILE__', __FILE__ );
32 define( 'WPZOOM_FORMS_PLUGIN_BASE', plugin_basename( WPZOOM_FORMS__FILE__ ) );
33 define( 'WPZOOM_FORMS_PLUGIN_DIR', dirname( WPZOOM_FORMS_PLUGIN_BASE ) );
34
35 define( 'WPZOOM_FORMS_PATH', plugin_dir_path( WPZOOM_FORMS__FILE__ ) );
36 define( 'WPZOOM_FORMS_URL', plugin_dir_url( WPZOOM_FORMS__FILE__ ) );
37
38
39 // WPZOOM Notice Center (submodule at classes/notice-center).
40 $wpz_notice_center_path = WPZOOM_FORMS_PATH . 'classes/notice-center/';
41 $wpz_notice_center_url = WPZOOM_FORMS_URL . 'classes/notice-center/';
42 if ( is_admin() && ! class_exists( 'WPZOOM_Notice_Center' ) && file_exists( $wpz_notice_center_path . 'notice-center.php' ) ) {
43 require_once $wpz_notice_center_path . 'notice-center.php';
44 WPZOOM_Notice_Center::get_instance()->set_assets( array(
45 'css_url' => $wpz_notice_center_url . 'assets/notice-center.css',
46 'js_url' => $wpz_notice_center_url . 'assets/notice-center.js',
47 ) );
48 }
49
50 // Instance the plugin
51 $wpzoom_forms = new WPZOOM_Forms();
52
53 // Register plugin activation hook
54 register_activation_hook( __FILE__, array( $wpzoom_forms, 'activate' ) );
55
56 // Hook the plugin into WordPress
57 add_action( 'init', array( $wpzoom_forms, 'init' ), 9 );
58
59
60
61 /**
62 * Class WPZOOM_Forms
63 *
64 * Main container class of the ZOOM Forms WordPress plugin.
65 *
66 * @since 1.0.0
67 */
68 class WPZOOM_Forms {
69
70 /**
71 * Whether the plugin has been initialized.
72 *
73 * @var boolean
74 * @access public
75 * @since 1.0.0
76 */
77 public $initialized = false;
78
79 /**
80 * The path to this plugin's root directory.
81 *
82 * @var string
83 * @access public
84 * @since 1.0.0
85 */
86 public $plugin_dir_path;
87
88 /**
89 * The URL to this plugin's root directory.
90 *
91 * @var string
92 * @access public
93 * @since 1.0.0
94 */
95 public $plugin_dir_url;
96
97 /**
98 * The path to this plugin's "main" directory.
99 *
100 * @var string
101 * @access public
102 * @since 1.0.0
103 */
104 public $main_dir_path;
105
106 /**
107 * The URL to this plugin's "main" directory.
108 *
109 * @var string
110 * @access public
111 * @since 1.0.0
112 */
113 public $main_dir_url;
114
115 /**
116 * The URL to this plugin's "dist" directory.
117 *
118 * @var string
119 * @access public
120 * @since 1.0.0
121 */
122 public $dist_dir_url;
123
124 /**
125 * UTM source for header and footer links.
126 *
127 * @var string
128 */
129 public $utm_source = '?utm_source=wpadmin&utm_medium=wpzoom-forms-free&utm_campaign=header-footer-links';
130
131 /**
132 * Initializes the plugin and sets up needed hooks and features.
133 *
134 * @access public
135 * @return void
136 * @since 1.0.0
137 * @see ZOOM_Forms::block_setup()
138 */
139 public function init() {
140 if ( false === $this->initialized ) {
141 $this->plugin_dir_path = plugin_dir_path( __FILE__ );
142 $this->plugin_dir_url = plugin_dir_url( __FILE__ );
143 $this->main_dir_path = trailingslashit( $this->plugin_dir_path . 'build' );
144 $this->main_dir_url = trailingslashit( $this->plugin_dir_url . 'build' );
145 $this->dist_dir_url = trailingslashit( $this->plugin_dir_url . 'dist' );
146
147 load_plugin_textdomain( 'wpzoom-forms', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
148
149 // Register welcome guide setting
150 register_setting(
151 'general',
152 'wpzoom_forms_welcome_guide_shown',
153 array(
154 'type' => 'boolean',
155 'default' => false,
156 'show_in_rest' => true,
157 'sanitize_callback' => 'rest_sanitize_boolean',
158 )
159 );
160
161 add_filter( 'allowed_block_types_all', array( $this, 'filter_allowed_block_types' ), 10, 2 );
162 add_filter( 'block_categories_all', array( $this, 'filter_block_categories' ), 10, 2 );
163 add_filter( 'post_row_actions', array( $this, 'modify_row_actions' ), 10, 2 );
164 add_filter( 'bulk_actions-edit-wpzf-form', array( $this, 'remove_bulk_actions' ), 10 );
165 add_filter( 'bulk_actions-edit-wpzf-submission', array( $this, 'remove_bulk_actions' ), 10 );
166 add_filter( 'manage_edit-wpzf-form_columns', array( $this, 'post_list_columns_form' ), 10 );
167 add_filter( 'manage_edit-wpzf-submission_columns', array( $this, 'post_list_columns_submit' ), 10 );
168 add_filter( 'manage_edit-wpzf-submission_sortable_columns', array( $this, 'post_list_sortable_columns_submit' ), 10 );
169 add_filter( 'screen_options_show_screen', array( $this, 'remove_screen_options' ), 10, 2 );
170 add_filter( 'views_edit-wpzf-form', array( $this, 'post_list_views' ), 10 );
171 add_filter( 'list_table_primary_column', array( $this, 'post_list_primary_column' ), 10, 2 );
172 add_action( 'admin_menu', array( $this, 'admin_menu' ), 10 );
173 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ), 100 );
174 add_action( 'enqueue_block_editor_assets', array( $this, 'register_backend_assets' ), 10 );
175 add_action( 'enqueue_block_assets', array( $this, 'register_frontend_assets' ), 10 );
176 add_action( 'enqueue_block_assets', array( $this, 'block_frontend_assets' ), 10 );
177 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ), 10 );
178 add_action( 'all_admin_notices', array( $this, 'admin_page_header' ), 1 );
179 add_action( 'in_admin_footer', array( $this, 'admin_page_footer' ), 10 );
180 add_filter( 'admin_body_class', array( $this, 'admin_body_class_filter' ), 10 );
181 add_action( 'manage_wpzf-form_posts_custom_column', array( $this, 'post_list_custom_columns_form' ), 10, 2 );
182 add_action( 'manage_wpzf-submission_posts_custom_column', array( $this, 'post_list_custom_columns_submit' ), 10, 2 );
183 add_action( 'pre_get_posts', array( $this, 'sort_custom_column' ), 10 );
184 add_action( 'in_admin_header', array( $this, 'remove_meta_boxes' ), 100 );
185 add_action( 'add_meta_boxes_wpzf-submission', array( $this, 'add_meta_boxes' ), 10 );
186 add_action( 'admin_post_wpzf_submit', array( $this, 'action_form_post' ), 10 );
187 add_action( 'admin_post_nopriv_wpzf_submit', array( $this, 'action_form_post' ), 10 );
188
189 add_action( 'restrict_manage_posts', array( $this, 'custom_filter_by_form' ), 10 );
190 add_action( 'parse_query', array( $this, 'filter_posts_by_form' ), 10 );
191
192 // Upsell notice for PRO version (via WPZOOM Notice Center)
193 add_filter( 'wpzoom_notice_center_notices', array( $this, 'register_upsell_notice' ) );
194
195 register_post_type(
196 'wpzf-form',
197 array(
198 'label' => __( 'WPZOOM Forms', 'wpzoom-forms' ),
199 'labels' => array(
200 'name' => _x( 'WPZOOM Forms', 'post type general name', 'wpzoom-forms' ),
201 'singular_name' => _x( 'Form', 'post type singular name', 'wpzoom-forms' ),
202 'add_new' => _x( 'Add New', 'post', 'wpzoom-forms' ),
203 'add_new_item' => __( 'Add New Form', 'wpzoom-forms' ),
204 'edit_item' => __( 'Edit Form', 'wpzoom-forms' ),
205 'new_item' => __( 'New Form', 'wpzoom-forms' ),
206 'view_item' => __( 'View Form', 'wpzoom-forms' ),
207 'view_items' => __( 'View Forms', 'wpzoom-forms' ),
208 'search_items' => __( 'Search Forms', 'wpzoom-forms' ),
209 'not_found' => __( 'No forms found.', 'wpzoom-forms' ),
210 'not_found_in_trash' => __( 'No forms found in Trash.', 'wpzoom-forms' ),
211 'all_items' => __( 'All Forms', 'wpzoom-forms' ),
212 'archives' => __( 'Form Archives', 'wpzoom-forms' ),
213 'attributes' => __( 'Form Attributes', 'wpzoom-forms' ),
214 'insert_into_item' => __( 'Insert into form', 'wpzoom-forms' ),
215 'uploaded_to_this_item' => __( 'Uploaded to this form', 'wpzoom-forms' ),
216 'featured_image' => _x( 'Featured image', 'post', 'wpzoom-forms' ),
217 'set_featured_image' => _x( 'Set featured image', 'post', 'wpzoom-forms' ),
218 'remove_featured_image' => _x( 'Remove featured image', 'post', 'wpzoom-forms' ),
219 'use_featured_image' => _x( 'Use as featured image', 'post', 'wpzoom-forms' ),
220 'filter_items_list' => __( 'Filter forms list', 'wpzoom-forms' ),
221 'items_list_navigation' => __( 'Forms list navigation', 'wpzoom-forms' ),
222 'items_list' => __( 'Forms list', 'wpzoom-forms' ),
223 'item_published' => __( 'Form saved.', 'wpzoom-forms' ),
224 'item_published_privately' => __( 'Form saved privately.', 'wpzoom-forms' ),
225 'item_reverted_to_draft' => __( 'Form reverted to draft.', 'wpzoom-forms' ),
226 'item_scheduled' => __( 'Form scheduled.', 'wpzoom-forms' ),
227 'item_updated' => __( 'Form updated.', 'wpzoom-forms' )
228 ),
229 'public' => true,
230 'exclude_from_search' => true,
231 'publicly_queryable' => false,
232 'show_in_rest' => true,
233 'menu_position' => 30,
234 'menu_icon' => 'data:image/svg+xml;base64,' . base64_encode('<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" clipRule="evenodd" d="M7.33333 2H20.6667V22H4V5.33333H7.33333V2ZM17.3333 5.33333H10.6667V8.66667H7.33333V18.6667H10.6667V15.3333H14V12H10.6667V8.66667H17.3333V5.33333Z" fill="#a7aaad"/></svg>'),
235 'supports' => array( 'title', 'editor', 'custom-fields', 'revisions' )
236 )
237 );
238
239 register_post_type(
240 'wpzf-submission',
241 array(
242 'label' => __( 'WPZOOM Form Submissions', 'wpzoom-forms' ),
243 'labels' => array(
244 'name' => _x( 'WPZOOM Form Submissions', 'post type general name', 'wpzoom-forms' ),
245 'singular_name' => _x( 'WPZOOM Form Submission', 'post type singular name', 'wpzoom-forms' ),
246 'add_new' => _x( 'Add New', 'post', 'wpzoom-forms' ),
247 'add_new_item' => __( 'Add New Submission', 'wpzoom-forms' ),
248 'edit_item' => __( 'WPZOOM Form Submission', 'wpzoom-forms' ),
249 'new_item' => __( 'New Submission', 'wpzoom-forms' ),
250 'view_item' => __( 'View Submission', 'wpzoom-forms' ),
251 'view_items' => __( 'View Submissions', 'wpzoom-forms' ),
252 'search_items' => __( 'Search Submissions', 'wpzoom-forms' ),
253 'not_found' => __( 'No submissions found.', 'wpzoom-forms' ),
254 'not_found_in_trash' => __( 'No submissions found in Trash.', 'wpzoom-forms' ),
255 'all_items' => __( 'Submissions', 'wpzoom-forms' ),
256 'archives' => __( 'Submission Archives', 'wpzoom-forms' ),
257 'attributes' => __( 'Submission Attributes', 'wpzoom-forms' ),
258 'insert_into_item' => __( 'Insert into submission', 'wpzoom-forms' ),
259 'uploaded_to_this_item' => __( 'Uploaded to this submission', 'wpzoom-forms' ),
260 'featured_image' => _x( 'Featured image', 'post', 'wpzoom-forms' ),
261 'set_featured_image' => _x( 'Set featured image', 'post', 'wpzoom-forms' ),
262 'remove_featured_image' => _x( 'Remove featured image', 'post', 'wpzoom-forms' ),
263 'use_featured_image' => _x( 'Use as featured image', 'post', 'wpzoom-forms' ),
264 'filter_items_list' => __( 'Filter form submission list', 'wpzoom-forms' ),
265 'items_list_navigation' => __( 'Form Submissions list navigation', 'wpzoom-forms' ),
266 'items_list' => __( 'Form Submissions list', 'wpzoom-forms' ),
267 'item_published' => __( 'Form Submission saved.', 'wpzoom-forms' ),
268 'item_published_privately' => __( 'Form Submission saved privately.', 'wpzoom-forms' ),
269 'item_reverted_to_draft' => __( 'Form Submission reverted to draft.', 'wpzoom-forms' ),
270 'item_scheduled' => __( 'Form Submission scheduled.', 'wpzoom-forms' ),
271 'item_updated' => __( 'Form Submission updated.', 'wpzoom-forms' )
272 ),
273 'public' => true,
274 'exclude_from_search' => true,
275 'publicly_queryable' => false,
276 'show_in_nav_menus' => false,
277 'show_in_admin_bar' => false,
278 'show_in_rest' => false,
279 'show_in_menu' => 'edit.php?post_type=wpzf-form',
280 'menu_position' => 31,
281 'menu_icon' => 'dashicons-email-alt2',
282 'supports' => array( '' ),
283 'capabilities' => array(
284 'create_posts' => 'do_not_allow',
285 'publish_posts' => 'do_not_allow',
286 'edit_posts' => 'edit_posts',
287 'delete_posts' => 'delete_posts'
288 ),
289 'map_meta_cap' => true
290 )
291 );
292
293 register_post_status(
294 'spam',
295 array(
296 'label' => esc_html_x( 'Spam', 'post', 'wpzoom-forms' ),
297 'label_count' => _n_noop( 'Spam <span class="count">(%s)</span>', 'Spam <span class="count">(%s)</span>', 'wpzoom-forms' ),
298 'public' => false
299 )
300 );
301
302 register_meta(
303 'post',
304 '_form_method',
305 array(
306 'object_subtype' => 'wpzf-form',
307 'show_in_rest' => true,
308 'single' => true,
309 'type' => 'string',
310 'default' => 'email',
311 'sanitize_callback' => 'sanitize_text_field',
312 'auth_callback' => function() { return current_user_can( 'edit_posts' ); }
313 )
314 );
315
316 register_meta(
317 'post',
318 '_form_email',
319 array(
320 'object_subtype' => 'wpzf-form',
321 'show_in_rest' => true,
322 'single' => true,
323 'type' => 'string',
324 'default' => '',
325 'sanitize_callback' => 'sanitize_text_field',
326 'auth_callback' => function() { return current_user_can( 'edit_posts' ); }
327 )
328 );
329
330 register_meta(
331 'post',
332 '_form_subject',
333 array(
334 'object_subtype' => 'wpzf-form',
335 'show_in_rest' => true,
336 'single' => true,
337 'type' => 'string',
338 'default' => esc_html__( 'New Form Submission', 'wpzoom-forms' ),
339 'sanitize_callback' => 'sanitize_text_field',
340 'auth_callback' => function() { return current_user_can( 'edit_posts' ); }
341 )
342 );
343
344 register_meta(
345 'post',
346 '_form_success_message',
347 array(
348 'object_subtype' => 'wpzf-form',
349 'show_in_rest' => true,
350 'single' => true,
351 'type' => 'string',
352 'default' => __( 'Thanks! We\'ve received your submission!', 'wpzoom-forms' ),
353 'sanitize_callback' => function( $value ) {
354 return wp_kses( $value, array() );
355 },
356 'auth_callback' => function() { return current_user_can( 'edit_posts' ); }
357 )
358 );
359
360 register_meta(
361 'post',
362 '_form_failure_message',
363 array(
364 'object_subtype' => 'wpzf-form',
365 'show_in_rest' => true,
366 'single' => true,
367 'type' => 'string',
368 'default' => __( 'Submission failed!', 'wpzoom-forms' ),
369 'sanitize_callback' => function( $value ) {
370 return wp_kses( $value, array() );
371 },
372 'auth_callback' => function() { return current_user_can( 'edit_posts' ); }
373 )
374 );
375
376 add_shortcode( 'wpzf_form', array( $this, 'shortcode_output' ) );
377
378 //$form_pto = get_post_type_object( 'wpzf-form' );
379 //$form_pto->template = array( array( 'wpzoom-forms/form' ) );
380
381 if ( $this->is_post_type( 'wpzf-form' ) ) {
382 $this->forms_display();
383 $this->register_blocks();
384 } elseif ( $this->is_post_type( 'wpzf-submission' ) ) {
385 $this->submissions_display();
386 } else {
387 $this->register_form_block();
388 }
389
390 $this->initialized = true;
391 }
392 }
393
394 /**
395 * Runs once during the activation of the plugin to run some one-time setup functions.
396 *
397 * @access public
398 * @return void
399 * @since 1.0.0
400 * @see ZOOM_Forms::init()
401 */
402 public function activate() {
403 $this->init();
404
405 flush_rewrite_rules();
406
407 // If this is the first time activating the plugin...
408 if ( ! get_option( 'wpzf-form_first-activate' ) ) {
409 // Insert an initial example form post
410 wp_insert_post( array(
411 'post_type' => 'wpzf-form',
412 'post_status' => 'publish',
413 'post_title' => __( 'Example Form', 'wpzoom-forms' ),
414 'post_content' => '<!-- wp:wpzoom-forms/form -->
415 <div class="wp-block-wpzoom-forms-form"><!-- wp:wpzoom-forms/text-name-field {"id":"input_ae87379b","name":"Name"} -->
416 <label for="input_ae87379b"><span>Name</span><sup class="wp-block-wpzoom-forms-required">*</sup></label><input type="text" name="input_ae87379b" id="input_ae87379b" placeholder="" required class="wp-block-wpzoom-forms-text-name-field"/>
417 <!-- /wp:wpzoom-forms/text-name-field -->
418
419 <!-- wp:wpzoom-forms/text-email-field {"id":"input_8468ae36","name":"Email"} -->
420 <label for="input_8468ae36"><span>Email</span><sup class="wp-block-wpzoom-forms-required">*</sup></label><input type="email" name="input_8468ae36" id="input_8468ae36" placeholder="" required data-replyto="false" class="wp-block-wpzoom-forms-text-email-field"/>
421 <!-- /wp:wpzoom-forms/text-email-field -->
422
423 <!-- wp:wpzoom-forms/text-plain-field {"id":"input_85d61063","name":"Subject","label":"Subject","subject":true} -->
424 <label for="input_85d61063"><span>Subject</span><sup class="wp-block-wpzoom-forms-required">*</sup></label><input type="text" name="input_85d61063" id="input_85d61063" placeholder="" required data-subject="true" class="wp-block-wpzoom-forms-text-plain-field"/>
425 <!-- /wp:wpzoom-forms/text-plain-field -->
426
427 <!-- wp:wpzoom-forms/textarea-field {"id":"input_7f6bf203","name":"Message"} -->
428 <label for="input_7f6bf203"><span>Message</span></label><textarea name="input_7f6bf203" id="input_7f6bf203" cols="55" rows="10" placeholder="" class="wp-block-wpzoom-forms-textarea-field"></textarea>
429 <!-- /wp:wpzoom-forms/textarea-field -->
430
431 <!-- wp:columns {"verticalAlignment":"center"} -->
432 <div class="wp-block-columns are-vertically-aligned-center"><!-- wp:column {"verticalAlignment":"center","width":"30%"} -->
433 <div class="wp-block-column is-vertically-aligned-center" style="flex-basis:30%"><!-- wp:wpzoom-forms/submit-field {"id":"input_submit"} -->
434 <input type="submit" id="input_submit" value="Submit" class="wp-block-wpzoom-forms-submit-field"/>
435 <!-- /wp:wpzoom-forms/submit-field --></div>
436 <!-- /wp:column -->
437
438 <!-- wp:column {"verticalAlignment":"center","width":"70%"} -->
439 <div class="wp-block-column is-vertically-aligned-center" style="flex-basis:70%"><!-- wp:paragraph {"align":"right","style":{"typography":{"fontSize":16}}} -->
440 <p class="has-text-align-right" style="font-size:16px">Fields marked with <strong class="has-accent-color has-text-color">*</strong> are required.</p>
441 <!-- /wp:paragraph --></div>
442 <!-- /wp:column --></div>
443 <!-- /wp:columns --></div>
444 <!-- /wp:wpzoom-forms/form -->',
445 'meta_input' => array(
446 '_form_method' => 'email',
447 '_form_email' => trim( get_option( 'admin_email' ) ),
448 '_form_subject' => esc_html__( 'New Form Submission', 'wpzoom-forms' ),
449 '_form_success_message' => __( 'Thanks! We\'ve received your submission!', 'wpzoom-forms' ),
450 '_form_failure_message' => __( 'Submission failed!', 'wpzoom-forms' ),
451
452 )
453 ) );
454
455 // Make sure we don't insert an example form on every activation
456 update_option( 'wpzf-form_first-activate', true );
457 }
458 }
459
460 /**
461 * Modifies the way the backend forms list is displayed.
462 *
463 * @access public
464 * @return void
465 * @since 1.0.0
466 */
467 public function forms_display() {
468 global $wp_post_statuses;
469
470 $publish = $wp_post_statuses['publish'];
471 $publish->label = __( 'Saved', 'wpzoom-forms' );
472 $publish->label_count[0] = __( 'Saved <span class="count">(%s)</span>', 'wpzoom-forms' );
473 $publish->label_count[1] = __( 'Saved <span class="count">(%s)</span>', 'wpzoom-forms' );
474 $publish->label_count['singular'] = __( 'Saved <span class="count">(%s)</span>', 'wpzoom-forms' );
475 $publish->label_count['plural'] = __( 'Saved <span class="count">(%s)</span>', 'wpzoom-forms' );
476
477 $wp_post_statuses['publish'] = $publish;
478
479 $wp_post_statuses = array_diff_key(
480 $wp_post_statuses,
481 array_flip( array(
482 'future',
483 'pending',
484 'private',
485 'request-pending',
486 'request-confirmed',
487 'request-failed',
488 'request-completed'
489 ) )
490 );
491
492 add_filter( 'post_date_column_status', function() { return __( 'Last Modified', 'wpzoom-forms' ); } );
493 add_filter( 'post_date_column_time', function( $time, $post ) {
494 return sprintf(
495 __( '%1$s at %2$s', 'wpzoom-forms' ),
496 get_the_modified_time( __( 'Y/m/d', 'wpzoom-forms' ), $post ),
497 get_the_modified_time( __( 'g:i a', 'wpzoom-forms' ), $post )
498 );
499 }, 10, 2 );
500 }
501
502 /**
503 * Modifies the way the backend submissions list is displayed.
504 *
505 * @access public
506 * @return void
507 * @since 1.0.0
508 */
509 public function submissions_display() {
510 global $wp_post_statuses;
511
512 $wp_post_statuses = array_diff_key(
513 $wp_post_statuses,
514 array_flip( [
515 'future',
516 'pending',
517 'private',
518 'request-pending',
519 'request-confirmed',
520 'request-failed',
521 'request-completed'
522 ] )
523 );
524
525 add_filter( 'post_date_column_status', function() { return __( 'Submitted', 'wpzoom-forms' ); } );
526 }
527
528 /**
529 * Registers all the custom blocks for this plugin.
530 *
531 * @access public
532 * @return void
533 * @since 1.0.0
534 */
535 public function register_blocks() {
536 register_block_type(
537 'wpzoom-forms/form',
538 array(
539 'script' => 'wpzoom-forms-js-frontend-formblock',
540 'style' => 'wpzoom-forms-css-frontend-formblock',
541 'editor_script' => 'wpzoom-forms-js-backend-main',
542 'editor_style' => 'wpzoom-forms-css-backend-main'
543 )
544 );
545
546 foreach ( array( 'multi-checkbox', 'checkbox', 'email', 'label', 'name', 'phone', 'plain', 'radio', 'select', 'submit', 'textarea', 'website', 'datepicker' ) as $block ) {
547 register_block_type( $this->main_dir_path . 'fields/' . $block . '/block.json' );
548 }
549 }
550
551 /**
552 * Registers the main form block used for inserting a form into a regular post/page.
553 *
554 * @access public
555 * @return void
556 * @since 1.0.0
557 */
558 public function register_form_block() {
559 register_block_type(
560 'wpzoom-forms/form-block',
561 array(
562 'attributes' => array(
563 'formId' => array(
564 'type' => 'string',
565 'default' => '-1'
566 ),
567 'align' => array(
568 'type' => 'string',
569 'default' => 'none'
570 ),
571 'formBgColor' => array(
572 'type' => 'string',
573 'default' => ''
574 ),
575 'formBrd' => array(
576 'type' => 'string',
577 'default' => ''
578 ),
579
580 'fieldBrdStyle' => array(
581 'type' => 'string',
582 'default' => 'default'
583 ),
584 'fieldBrdWidth' => array(
585 'type' => 'number',
586 'default' => 0
587 ),
588 'fieldBrdRadius' => array(
589 'type' => 'number',
590 'default' => 0
591 ),
592
593 'fieldBrdColor' => array(
594 'type' => 'string',
595 'default' => ''
596 ),
597 'fieldTextColor' => array(
598 'type' => 'string',
599 'default' => ''
600 ),
601 'fieldBgColor' => array(
602 'type' => 'string',
603 'default' => ''
604 ),
605 'labelTextColor' => array(
606 'type' => 'string',
607 'default' => ''
608 ),
609 'btnBgColor' => array(
610 'type' => 'string',
611 'default' => ''
612 ),
613 'btnTextColor' => array(
614 'type' => 'string',
615 'default' => ''
616 ),
617 'btnBrdWidth' => array(
618 'type' => 'number',
619 'default' => 0
620 ),
621 'btnBrdStyle' => array(
622 'type' => 'string',
623 'default' => 'default'
624 ),
625 'btnBrdRadius' => array(
626 'type' => 'number',
627 'default' => 0
628 ),
629 'btnBrdColor' => array(
630 'type' => 'string',
631 'default' => ''
632 ),
633 'btnHoverBgColor' => array(
634 'type' => 'string',
635 'default' => ''
636 ),
637 'btnHoverTextColor' => array(
638 'type' => 'string',
639 'default' => ''
640 ),
641 'btnHoverBrdColor' => array(
642 'type' => 'string',
643 'default' => ''
644 ),
645
646 ),
647 'script' => 'wpzoom-forms-js-frontend-formblock',
648 'style' => 'wpzoom-forms-css-frontend-formblock',
649 'editor_script' => 'wpzoom-forms-js-backend-formblock',
650 'editor_style' => 'wpzoom-forms-css-backend-formblock',
651 'render_callback' => array( $this, 'form_block_render' )
652 )
653 );
654 }
655
656 /**
657 * Filters the allowed Gutenberg block types for a given post.
658 *
659 * @access public
660 * @param array $allowed_block_types An array of all the allowed block types.
661 * @param WP_Block_Editor_Context $block_editor_context The current block editor context.
662 * @return array
663 * @since 1.0.0
664 */
665 public function filter_allowed_block_types( $allowed_block_types, $block_editor_context ) {
666 if ( null !== $block_editor_context->post && 'wpzf-form' == $block_editor_context->post->post_type ) {
667 $allowed_block_types = array(
668 'wpzoom-forms/form',
669 'wpzoom-forms/datepicker-field',
670 'wpzoom-forms/text-plain-field',
671 'wpzoom-forms/text-name-field',
672 'wpzoom-forms/text-email-field',
673 'wpzoom-forms/text-website-field',
674 'wpzoom-forms/text-phone-field',
675 'wpzoom-forms/textarea-field',
676 'wpzoom-forms/select-field',
677 'wpzoom-forms/multi-checkbox-field',
678 'wpzoom-forms/checkbox-field',
679 'wpzoom-forms/radio-field',
680 'wpzoom-forms/label-field',
681 'wpzoom-forms/submit-field',
682 'core/paragraph',
683 'core/heading',
684 'core/list',
685 'core/quote',
686 'core/code',
687 'core/preformatted',
688 'core/pullquote',
689 'core/table',
690 'core/verse',
691 'core/image',
692 'core/gallery',
693 'core/audio',
694 'core/cover',
695 'core/file',
696 'core/media-text',
697 'core/video',
698 'core/buttons',
699 'core/columns',
700 'core/group',
701 'core/more',
702 'core/nextpage',
703 'core/separator',
704 'core/spacer'
705 );
706 }
707
708 return $allowed_block_types;
709 }
710
711 /**
712 * Adds needed categories to the Gutenberg block categories, if not already present.
713 *
714 * @access public
715 * @param array $categories Array containing all registered Gutenberg block categories.
716 * @param WP_Block_Editor_Context $block_editor_context The current block editor context.
717 * @return array
718 * @since 1.0.0
719 */
720 public function filter_block_categories( $categories, $block_editor_context ) {
721 if ( null !== $block_editor_context->post && 'wpzf-form' == $block_editor_context->post->post_type ) {
722 $category_slugs = wp_list_pluck( $categories, 'slug' );
723
724 if ( ! in_array( 'wpzoom-forms', $category_slugs, true ) ) {
725 array_unshift(
726 $categories,
727 array(
728 'slug' => 'wpzoom-forms',
729 'title' => __( 'WPZOOM Forms', 'wpzoom-forms' ),
730 'icon' => 'wordpress'
731 )
732 );
733 }
734 } else {
735 $category_slugs = wp_list_pluck( $categories, 'slug' );
736
737 if ( ! in_array( 'wpzoom-blocks', $category_slugs, true ) ) {
738 $categories = array_merge(
739 $categories,
740 array(
741 array(
742 'slug' => 'wpzoom-blocks',
743 'title' => __( 'WPZOOM Blocks', 'wpzoom-forms' ),
744 'icon' => 'wordpress'
745 )
746 )
747 );
748 }
749 }
750
751 return $categories;
752 }
753
754 /**
755 * Add some extra admin menu items.
756 *
757 * @access public
758 * @return void
759 * @since 1.0.0
760 */
761 public function admin_menu() {
762
763 global $submenu;
764
765 $page_title = esc_html__( 'WPZOOM Forms Settings Page', 'wpzoom-forms' );
766
767 add_submenu_page(
768 'edit.php?post_type=wpzf-form',
769 $page_title,
770 esc_html__( 'Settings', 'wpzoom-forms' ),
771 'manage_options',
772 'wpzf-settings',
773 array( $this, 'render_settings_page' )
774 );
775
776 add_submenu_page(
777 'edit.php?post_type=wpzf-form',
778 $page_title,
779 '<span style="color:#3496fe; font-weight: 600;">' . esc_html__( 'UPGRADE', 'wpzoom-forms' ) . ' &rarr; <span class="wpz-premium-badge" style="background-color: #3496fe; color: #fff; margin-left: 3px; font-size: 9px; min-height: 14px; border-radius: 3px; display: inline-block; font-weight: 600; line-height: 1.4; padding: 0 5px">PRO</span></span>',
780 'manage_options',
781 'wpzf-pro-page',
782 array( $this, 'render_upsell_page' )
783 );
784
785 add_submenu_page(
786 'edit.php?post_type=wpzf-form',
787 esc_html__( 'Import & Export', 'wpzoom-forms' ),
788 esc_html__( 'Import & Export', 'wpzoom-forms' ) . ' <span class="wpz-premium-badge" style="background-color: #3496fe; color: #fff; margin-left: 3px; font-size: 9px; min-height: 14px; border-radius: 3px; display: inline-block; font-weight: 600; line-height: 1.4; padding: 0 5px">PRO</span>',
789 'edit_posts',
790 'wpzf-import',
791 array( $this, 'render_import_export_upsell_page' )
792 );
793
794 }
795
796 /**
797 * Registers needed scripts for use on the admin backend.
798 *
799 * @access public
800 * @return void
801 * @since 1.0.0
802 */
803 public function admin_enqueue_scripts() {
804 if ( 'wpzf-submission' == get_post_type() ) {
805 wp_dequeue_script( 'autosave' );
806
807 wp_enqueue_style(
808 'wpzoom-forms-css-backend-submissions',
809 trailingslashit( $this->main_dir_url ) . 'submissions/backend/style.css',
810 array(),
811 WPZOOM_FORMS_VERSION
812 );
813 }
814
815 $current_page = get_current_screen()->id;
816
817 if ( 'edit-wpzf-form' == $current_page || 'wpzf-form' == $current_page || 'edit-wpzf-submission' == $current_page || 'wpzf-submission' == $current_page || 'wpzf-form_page_wpzf-settings' == $current_page || 'wpzf-form_page_wpzoom-forms-pro-license' == $current_page || 'wpzf-form_page_wpzf-pro-page' == $current_page || 'wpzf-form_page_wpzf-import' == $current_page ) {
818 wp_enqueue_style(
819 'wpzoom-forms-css-backend-main',
820 trailingslashit( $this->main_dir_url ) . 'main/backend/style.css',
821 array(),
822 WPZOOM_FORMS_VERSION
823 );
824 }
825
826 if ( 'edit-wpzf-form' == $current_page || 'wpzf-form' == $current_page ) {
827 wp_enqueue_script(
828 'wpzoom-forms-cpt',
829 WPZOOM_FORMS_URL . 'dist/assets/admin/js/forms-cpt.js',
830 array( 'wp-data' ),
831 WPZOOM_FORMS_VERSION,
832 true
833 );
834 }
835
836 }
837
838 /**
839 * Registers needed scripts and styles for use on the backend.
840 *
841 * @access public
842 * @return void
843 * @since 1.0.0
844 */
845 public function register_backend_assets() {
846 if ( 'wpzf-form' == get_post_type() ) {
847 wp_register_script(
848 'wpzoom-forms-js-backend-main',
849 trailingslashit( $this->main_dir_url ) . 'main/backend/script.js',
850 array( 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill' ),
851 WPZOOM_FORMS_VERSION,
852 true
853 );
854
855 wp_localize_script(
856 'wpzoom-forms-js-backend-main',
857 'wpzf_formblock',
858 array(
859 'admin_url' => trailingslashit( admin_url() ),
860 'admin_email' => '' . get_site_option( 'admin_email', '' )
861 )
862 );
863
864 wp_set_script_translations( 'wpzoom-forms-js-backend-main', 'wpzoom-forms' );
865
866 wp_register_style(
867 'wpzoom-forms-css-backend-main',
868 trailingslashit( $this->main_dir_url ) . 'main/backend/style.css',
869 array(),
870 WPZOOM_FORMS_VERSION
871 );
872 } else {
873 wp_register_script(
874 'wpzoom-forms-js-backend-formblock',
875 trailingslashit( $this->main_dir_url ) . 'form-block/backend/script.js',
876 array( 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill' ),
877 WPZOOM_FORMS_VERSION,
878 true
879 );
880
881 wp_localize_script(
882 'wpzoom-forms-js-backend-formblock',
883 'wpzf_formblock',
884 array(
885 'admin_url' => trailingslashit( admin_url() ),
886 'admin_email' => '' . get_site_option( 'admin_email', '' )
887 )
888 );
889
890 wp_set_script_translations( 'wpzoom-forms-js-backend-formblock', 'wpzoom-forms' );
891
892 wp_register_style(
893 'wpzoom-forms-css-backend-formblock',
894 trailingslashit( $this->main_dir_url ) . 'form-block/backend/style.css',
895 array(),
896 WPZOOM_FORMS_VERSION
897 );
898 }
899 }
900
901 /**
902 * Get normalized spam protection settings and whether the selected method is fully configured.
903 *
904 * @access private
905 * @return array
906 * @since 1.3.7
907 */
908 public function get_spam_protection_config() {
909 $service = sanitize_text_field( (string) WPZOOM_Forms_Settings::get( 'wpzf_global_captcha_service' ) );
910 $type = sanitize_text_field( (string) WPZOOM_Forms_Settings::get( 'wpzf_global_captcha_type' ) );
911
912 if ( ! in_array( $service, array( 'none', 'recaptcha', 'turnstile' ), true ) ) {
913 $service = 'none';
914 }
915
916 if ( ! in_array( $type, array( 'v2', 'v3' ), true ) ) {
917 $type = 'v2';
918 }
919
920 $recaptcha_v2_site_key = trim( sanitize_text_field( (string) WPZOOM_Forms_Settings::get( 'wpzf_global_captcha_site_key' ) ) );
921 $recaptcha_v2_secret_key = trim( sanitize_text_field( (string) WPZOOM_Forms_Settings::get( 'wpzf_global_captcha_secret_key' ) ) );
922 $recaptcha_v3_site_key = trim( sanitize_text_field( (string) WPZOOM_Forms_Settings::get( 'wpzf_global_captcha_site_key_v3' ) ) );
923 $recaptcha_v3_secret_key = trim( sanitize_text_field( (string) WPZOOM_Forms_Settings::get( 'wpzf_global_captcha_secret_key_v3' ) ) );
924 $turnstile_site_key = trim( sanitize_text_field( (string) WPZOOM_Forms_Settings::get( 'wpzf_global_turnstile_site_key' ) ) );
925 $turnstile_secret_key = trim( sanitize_text_field( (string) WPZOOM_Forms_Settings::get( 'wpzf_global_turnstile_secret_key' ) ) );
926
927 $active_service = 'none';
928
929 if ( 'recaptcha' === $service ) {
930 if ( 'v3' === $type && ! empty( $recaptcha_v3_site_key ) && ! empty( $recaptcha_v3_secret_key ) ) {
931 $active_service = 'recaptcha';
932 } elseif ( 'v2' === $type && ! empty( $recaptcha_v2_site_key ) && ! empty( $recaptcha_v2_secret_key ) ) {
933 $active_service = 'recaptcha';
934 }
935 } elseif ( 'turnstile' === $service && ! empty( $turnstile_site_key ) && ! empty( $turnstile_secret_key ) ) {
936 $active_service = 'turnstile';
937 }
938
939 return array(
940 'service' => $service,
941 'type' => $type,
942 'active_service' => $active_service,
943 'recaptcha_v2_site_key' => $recaptcha_v2_site_key,
944 'recaptcha_v2_secret_key' => $recaptcha_v2_secret_key,
945 'recaptcha_v3_site_key' => $recaptcha_v3_site_key,
946 'recaptcha_v3_secret_key' => $recaptcha_v3_secret_key,
947 'turnstile_site_key' => $turnstile_site_key,
948 'turnstile_secret_key' => $turnstile_secret_key,
949 );
950 }
951
952 /**
953 * Registers needed scripts and styles for use on the frontend.
954 *
955 * @access public
956 * @return void
957 * @since 1.0.0
958 */
959 public function register_frontend_assets() {
960
961 $depends = array( 'jquery' );
962 $captcha = $this->get_spam_protection_config();
963 $captcha_type = $captcha['type'];
964 $active_method = $captcha['active_service'];
965
966 if ( ! is_admin() ) {
967 if ( 'recaptcha' === $active_method ) {
968 $site_key = ( 'v3' === $captcha_type ) ? $captcha['recaptcha_v3_site_key'] : $captcha['recaptcha_v2_site_key'];
969
970 if ( 'v2' === $captcha_type ) {
971 wp_register_script(
972 'google-recaptcha',
973 'https://www.google.com/recaptcha/api.js',
974 array(),
975 WPZOOM_FORMS_VERSION,
976 true
977 );
978 }
979 elseif ( 'v3' === $captcha_type ) {
980 wp_register_script(
981 'google-recaptcha',
982 "https://www.google.com/recaptcha/api.js?render={$site_key}",
983 array(),
984 WPZOOM_FORMS_VERSION,
985 true
986 );
987 }
988
989 $depends[] = 'google-recaptcha';
990 } elseif ( 'turnstile' === $active_method ) {
991 wp_register_script(
992 'turnstile-recaptcha',
993 'https://challenges.cloudflare.com/turnstile/v0/api.js',
994 array(),
995 null,
996 array( 'strategy' => 'defer' ),
997 );
998
999 $depends[] = 'turnstile-recaptcha';
1000 }
1001 }
1002
1003 wp_register_script(
1004 'wpzoom-forms-js-frontend-formblock',
1005 trailingslashit( $this->dist_dir_url ) . 'assets/frontend/js/script.js',
1006 $depends,
1007 WPZOOM_FORMS_VERSION,
1008 array(
1009 'in_footer' => true,
1010 'strategy' => 'defer',
1011 )
1012 );
1013
1014 $use_theme_style = boolval( WPZOOM_Forms_Settings::get( 'wpzf_use_theme_styles' ) );
1015
1016 wp_register_style(
1017 'wpzoom-forms-css-frontend-formblock',
1018 ( $use_theme_style ? trailingslashit( $this->main_dir_url ) . 'form-block/frontend/style.css' : false ),
1019 array(),
1020 WPZOOM_FORMS_VERSION
1021 );
1022
1023 // Register style for datepicker field
1024 wp_register_style(
1025 'wpzoom-forms-css-frontend-flatpickr',
1026 trailingslashit( $this->dist_dir_url ) . 'assets/frontend/flatpickr/css/flatpickr.min.css',
1027 array(),
1028 WPZOOM_FORMS_VERSION
1029 );
1030
1031 //Register script for datepicker field
1032 wp_register_script(
1033 'wpzoom-forms-js-frontend-flatpickr',
1034 trailingslashit( $this->dist_dir_url ) . 'assets/frontend/flatpickr/js/flatpickr.js',
1035 array( 'jquery' ),
1036 WPZOOM_FORMS_VERSION,
1037 array(
1038 'in_footer' => true,
1039 'strategy' => 'defer',
1040 )
1041 );
1042
1043 wp_register_script(
1044 'wpzoom-forms-js-frontend-datepicker',
1045 trailingslashit( $this->dist_dir_url ) . 'assets/frontend/js/datepicker.js',
1046 array( 'wpzoom-forms-js-frontend-flatpickr' ),
1047 WPZOOM_FORMS_VERSION,
1048 array(
1049 'in_footer' => true,
1050 'strategy' => 'defer',
1051 )
1052 );
1053 }
1054
1055 /**
1056 * Enqueues needed scripts and styles for use on the frontend.
1057 *
1058 * @access public
1059 * @return void
1060 * @since 1.0.0
1061 */
1062 public function enqueue_frontend_scripts() {
1063
1064 $global_load_assets = boolval( WPZOOM_Forms_Settings::get( 'wpzf_global_assets_load' ) );
1065
1066 if ( $global_load_assets ) {
1067 wp_enqueue_style( 'wpzoom-forms-css-frontend-formblock' );
1068 wp_enqueue_script( 'wpzoom-forms-js-frontend-formblock' );
1069 }
1070
1071 }
1072
1073 /**
1074 * Enqueues needed scripts and styles for use on the frontend.
1075 *
1076 * @access public
1077 * @return void
1078 * @since 1.0.0
1079 */
1080 public function block_frontend_assets() {
1081
1082 if( self::has_block( 'wpzoom-forms/datepicker-field' ) || self::has_elementor_widget() || self::has_cpt_form_shortcode() ) {
1083 wp_enqueue_style( 'wpzoom-forms-css-frontend-flatpickr' );
1084 wp_enqueue_script( 'wpzoom-forms-js-frontend-flatpickr' );
1085 wp_enqueue_script( 'wpzoom-forms-js-frontend-datepicker' );
1086 }
1087
1088 }
1089
1090 // Check if a block exists in the post content
1091 static function has_block( $block_name ) {
1092
1093 if( ! has_block( 'wpzoom-forms/form-block' ) ) {
1094 return false;
1095 }
1096
1097 global $post;
1098
1099 $gutenberg_matches = array();
1100 $gutenberg_patern = '/<!--\s+wp:(wpzoom\-forms\/form\-block)(\s+(\{.*?\}))?\s+(\/)?-->/';
1101 preg_match_all( $gutenberg_patern, $post->post_content, $matches );
1102
1103 // Check if the block exists in the post content
1104 if ( isset( $matches[3] ) ) {
1105 foreach ( $matches[3] as $block_attributes_json ) {
1106 if ( ! empty( $block_attributes_json ) ) {
1107 $atts = json_decode( $block_attributes_json, true );
1108 }
1109 }
1110 }
1111
1112 $form_ID = isset( $atts['formId'] ) ? $atts['formId'] : '';
1113
1114 if( $form_ID ) {
1115 $form_post = get_post( $form_ID );
1116 if( has_block( $block_name, $form_post ) ) {
1117 return true;
1118 }
1119 }
1120
1121 return false;
1122
1123 }
1124
1125 // Check the form exists in the elementor posts content
1126 public static function has_elementor_widget( $post_id = 0 ) {
1127
1128 if ( ! defined( 'ELEMENTOR_VERSION' ) && ! is_callable( 'Elementor\Plugin::instance' ) ) {
1129 return false;
1130 }
1131
1132 $post_id = $post_id > 0 ? $post_id : get_the_ID();
1133
1134 $elementor_data = get_post_meta( $post_id, '_elementor_data' );
1135
1136 if ( isset( $elementor_data[0] ) && is_string( $elementor_data[0] ) ) {
1137
1138 $regExp = '/"widgetType":"([^"]*)/i';
1139 $outputArray = array();
1140
1141 // Define the pattern to match the shortcode
1142 $scPattern = '/\[wpzf_form([^\]]*)\]/';
1143 preg_match_all( $scPattern, $elementor_data[0], $matches );
1144
1145 // Check if any matches are found
1146 if ( ! empty( $matches[0] ) ) {
1147 return true;
1148 }
1149
1150 if ( preg_match_all( $regExp, $elementor_data[0], $outputArray, PREG_SET_ORDER) ) {}
1151
1152 foreach( $outputArray as $found ) {
1153 if( in_array( 'wpzoom-forms-widget-cpt', $found ) ) {
1154 return true;
1155 }
1156 }
1157
1158 }
1159
1160 return false;
1161 }
1162
1163 /**
1164 * Check the post content has cpt form shortcode
1165 *
1166 * @since 1.2.0
1167 * @param int $post_id The post ID.
1168 * @param boolean|int $content The post content.
1169 * @return boolean Return true if post content has cpt form shortcode, else return false.
1170 */
1171 public static function has_cpt_form_shortcode( $post_id = 0, $content = '' ) {
1172
1173 $post_id = $post_id > 0 ? $post_id : get_the_ID();
1174
1175 if ( empty( $content ) ) {
1176 $content = get_post_field( 'post_content', $post_id );
1177 }
1178
1179 if ( $content ) {
1180 if ( has_shortcode( $content, 'wpzf_form' ) ) {
1181 return true;
1182 }
1183 }
1184 return false;
1185 }
1186
1187 /**
1188 * Changes the row actions in post lists for certain post types.
1189 *
1190 * @access public
1191 * @param array $actions An array of all the possible post row actions.
1192 * @param WP_Post $post The post object for the post that is being displayed.
1193 * @return array
1194 * @since 1.0.0
1195 */
1196 public function modify_row_actions( $actions, $post ) {
1197 if ( 'wpzf-form' == $post->post_type || 'wpzf-submission' == $post->post_type ) {
1198 if ( 'wpzf-submission' == $post->post_type && isset( $actions['edit'] ) ) {
1199 $actions['edit'] = preg_replace( '/\<a([^>]+)\>(.+)\<\/a\>/i', '<a$1>' . __( 'View', 'wpzoom-forms' ) . '</a>', $actions['edit'] );
1200 }
1201
1202 if ( isset( $actions['inline hide-if-no-js'] ) ) {
1203 unset( $actions['inline hide-if-no-js'] );
1204 }
1205 }
1206
1207 return $actions;
1208 }
1209
1210 /**
1211 * Removes the Inline Edit option from post types it doesn't make sense for.
1212 *
1213 * @access public
1214 * @param array $actions An array of all the possible bulk actions.
1215 * @return array
1216 * @since 1.0.0
1217 */
1218 public function remove_bulk_actions( $actions ) {
1219 if ( isset( $actions['edit'] ) ) {
1220 unset( $actions['edit'] );
1221 }
1222
1223 return $actions;
1224 }
1225
1226 /**
1227 * Changes the columns displayed in the post list for the form custom post type.
1228 *
1229 * @access public
1230 * @param array $columns An array of all the columns.
1231 * @return array
1232 * @since 1.0.0
1233 */
1234 public function post_list_columns_form( $columns ) {
1235 return array(
1236 'cb' => $columns['cb'],
1237 'title' => __( 'Title', 'wpzoom-forms' ),
1238 'responses' => __( 'Responses', 'wpzoom-forms' ),
1239 'shortcode' => __( 'Shortcode', 'wpzoom-forms' ),
1240 'date' => $columns['date']
1241 );
1242 }
1243
1244 /**
1245 * Changes the columns displayed in the post list for the submissions custom post type.
1246 *
1247 * @access public
1248 * @param array $columns An array of all the columns.
1249 * @return array
1250 * @since 1.0.0
1251 */
1252 public function post_list_columns_submit( $columns ) {
1253 return array(
1254 'cb' => $columns['cb'],
1255 'submission_id' => __( 'ID', 'wpzoom-forms' ),
1256 'name' => __( 'Name', 'wpzoom-forms' ),
1257 'email' => __( 'Email', 'wpzoom-forms' ),
1258 'subject' => __( 'Subject', 'wpzoom-forms' ),
1259 'form' => __( 'Form', 'wpzoom-forms' ),
1260 'date' => $columns['date']
1261 );
1262 }
1263
1264 /**
1265 * Changes the sortable columns displayed in the post list for the submissions custom post type.
1266 *
1267 * @access public
1268 * @param array $columns An array of all the sortable columns.
1269 * @return array
1270 * @since 1.0.0
1271 */
1272 public function post_list_sortable_columns_submit( $columns ) {
1273 $columns['submission_id'] = 'ID';
1274 $columns['form'] = 'wpzf_form';
1275
1276 return $columns;
1277 }
1278
1279 // Add the filter dropdown to the posts list
1280 public function custom_filter_by_form() {
1281 global $wpdb;
1282
1283 // Check if we are on the posts list page
1284 if ( isset( $_GET['post_type'] ) && $_GET['post_type'] == 'wpzf-submission' ) {
1285
1286 $form_id_filter_selected = isset( $_GET['form_id_filter'] ) ? $_GET['form_id_filter'] : '';
1287
1288 // Fetch unique form IDs and their corresponding titles from your database
1289 $form_ids_query = $wpdb->get_results("SELECT ID AS form_id, post_title
1290 FROM $wpdb->posts
1291 WHERE post_type = 'wpzf-form'
1292 AND post_status = 'publish'");
1293
1294
1295 // If we have forms, display the dropdown
1296 if ( ! empty( $form_ids_query ) ) {
1297 // Initialize an array to store unique form IDs
1298 $form_ids = array();
1299 ?>
1300 <select name="form_id_filter" id="form_id_filter">
1301 <option value=""><?php echo esc_html__( 'All Forms', 'wpzoom-forms' ); ?></option>
1302 <?php
1303 foreach ( $form_ids_query as $result ) {
1304
1305 // Check if the form ID already exists in the array
1306 if ( ! in_array( $result->form_id, $form_ids ) ) {
1307
1308 // Add form ID to the array to prevent duplication
1309 $form_ids[] = $result->form_id;
1310
1311 // Output the option with form ID as value and post title as option name
1312 echo '<option '. selected( $result->form_id, $form_id_filter_selected, true ) .' value="' . $result->form_id . '">' . $result->post_title . '</option>';
1313 }
1314 }
1315 ?>
1316 </select>
1317 <?php
1318 }
1319
1320
1321 }
1322 }
1323
1324 // Modify the query based on the selected form ID
1325 public function filter_posts_by_form ( $query ) {
1326
1327 global $pagenow;
1328
1329 // Check if we are on the posts list page and a form ID filter is set
1330 if ( is_admin() && $pagenow == 'edit.php' && isset( $_GET['form_id_filter'] ) && $_GET['form_id_filter'] != '' ) {
1331
1332 $query->query_vars['meta_key'] = '_wpzf_form_id';
1333 $query->query_vars['meta_value'] = $_GET['form_id_filter'];
1334
1335 }
1336 }
1337
1338 /**
1339 * Changes the column content displayed in the post list for the form custom post type.
1340 *
1341 * @access public
1342 * @param string $column The name of the column to display.
1343 * @param int $post_id The ID of the current post.
1344 * @return array
1345 * @since 1.0.0
1346 */
1347 public function post_list_custom_columns_form( $column, $post_id ) {
1348
1349 if ( 'shortcode' == $column ) {
1350 printf( '<input type="text" value="[wpzf_form id=&quot;%d&quot;]" readonly />', absint( $post_id ) );
1351 }
1352
1353 if( 'responses' == $column ) {
1354
1355 $args = array(
1356 'post_type' => 'wpzf-submission',
1357 'post_status' => 'publish',
1358 'posts_per_page' => -1,
1359 'meta_query' => array(
1360 array(
1361 'key' => '_wpzf_form_id',
1362 'value' => $post_id,
1363 'compare' => '='
1364 )
1365 )
1366 );
1367
1368 $forms_count = 0;
1369
1370 $responses = new WP_Query( $args );
1371
1372 if( $responses && $responses->have_posts() ) {
1373 $forms_count = $responses->post_count;
1374 }
1375
1376 if( 0 < $forms_count ) {
1377 printf( '<a href="%s">%s</a>', admin_url( 'edit.php?post_type=wpzf-submission&&form_id_filter=' . $post_id ), $forms_count );
1378 } else {
1379 echo $forms_count;
1380 }
1381
1382 }
1383 }
1384
1385 /**
1386 * Changes the column content displayed in the post list for the submissions custom post type.
1387 *
1388 * @access public
1389 * @param string $column The name of the column to display.
1390 * @param int $post_id The ID of the current post.
1391 * @return array
1392 * @since 1.0.0
1393 */
1394 public function post_list_custom_columns_submit( $column, $post_id ) {
1395 $fields = get_post_meta( $post_id, '_wpzf_fields', true );
1396
1397 switch ( $column ) {
1398 case 'submission_id':
1399 printf(
1400 '<strong><a href="%s" class="row-title">%s #%s</a></strong>',
1401 esc_url( get_edit_post_link( $post_id ) ),
1402 esc_html__( 'Submission', 'wpzoom-forms' ),
1403 esc_html( $post_id )
1404 );
1405 break;
1406
1407 case 'name':
1408 $name_found = false;
1409
1410 if ( ! empty( $fields ) && is_array( $fields ) ) {
1411 // Common name field patterns
1412 $name_patterns = array( 'name', 'full_name', 'first_name', 'last_name', 'fullname', 'your_name' );
1413
1414 foreach ( $fields as $name => $value ) {
1415 $field_name = strtolower( $name );
1416 foreach ( $name_patterns as $pattern ) {
1417 if ( strpos( $field_name, $pattern ) !== false && ! empty( $value ) ) {
1418 echo '<strong>' . esc_html( $value ) . '</strong>';
1419 $name_found = true;
1420 break 2;
1421 }
1422 }
1423 }
1424 }
1425
1426 if ( ! $name_found ) {
1427 echo '&mdash;';
1428 }
1429 break;
1430
1431 case 'email':
1432 $email_found = false;
1433
1434 if ( ! empty( $fields ) && is_array( $fields ) ) {
1435 // Common email field patterns
1436 $email_patterns = array( 'email', 'e-mail', 'mail', 'email_address', 'your_email' );
1437
1438 foreach ( $fields as $name => $value ) {
1439 $field_name = strtolower( $name );
1440 foreach ( $email_patterns as $pattern ) {
1441 if ( strpos( $field_name, $pattern ) !== false && ! empty( $value ) ) {
1442 echo '<a href="mailto:' . esc_attr( $value ) . '">' . esc_html( $value ) . '</a>';
1443 $email_found = true;
1444 break 2;
1445 }
1446 }
1447 }
1448 }
1449
1450 if ( ! $email_found ) {
1451 echo '&mdash;';
1452 }
1453 break;
1454
1455 case 'subject':
1456 $subject_found = false;
1457
1458 if ( ! empty( $fields ) && is_array( $fields ) ) {
1459 // First try to find a subject field
1460 foreach ( $fields as $name => $value ) {
1461 if ( stripos( $name, 'subject' ) !== false || stripos( $name, 'title' ) !== false || stripos( $name, 'topic' ) !== false ) {
1462 echo esc_html( $value );
1463 $subject_found = true;
1464 break;
1465 }
1466 }
1467
1468 // If no subject field found, show first non-empty field (likely message)
1469 if ( ! $subject_found ) {
1470 foreach ( $fields as $name => $value ) {
1471 // Skip name and email fields
1472 $field_name = strtolower( $name );
1473 if ( strpos( $field_name, 'name' ) !== false || strpos( $field_name, 'email' ) !== false ) {
1474 continue;
1475 }
1476 if ( ! empty( $value ) ) {
1477 echo esc_html( wp_trim_words( $value, 10, '...' ) );
1478 $subject_found = true;
1479 break;
1480 }
1481 }
1482 }
1483 }
1484
1485 if ( ! $subject_found ) {
1486 echo '&mdash;';
1487 }
1488 break;
1489
1490 case 'form':
1491 $form_id = intval( get_post_meta( $post_id, '_wpzf_form_id', true ) );
1492 $form_name = __( '[Unknown]', 'wpzoom-forms' );
1493
1494 if ( $form_id > 0 ) {
1495 $form_post = get_post( $form_id );
1496
1497 if ( ! is_null( $form_post ) && 'wpzf-form' === $form_post->post_type ) {
1498 $form_title = get_the_title( $form_id );
1499
1500 if ( ! empty( $form_title ) && 'trash' !== $form_post->post_status ) {
1501 $form_name = '<a href="' . esc_url( get_edit_post_link( $form_id ) ) . '">' . esc_html( $form_title ) . '</a>';
1502 } elseif ( 'trash' === $form_post->post_status ) {
1503 $form_name = sprintf(
1504 '<span style="color: #d63638;">%s</span> <small>(%s)</small>',
1505 esc_html( $form_title ?: __( 'Untitled Form', 'wpzoom-forms' ) ),
1506 esc_html__( 'Trashed', 'wpzoom-forms' )
1507 );
1508 }
1509 }
1510 }
1511
1512 echo wp_kses( $form_name, array( 'a' => array( 'href' => array() ), 'span' => array( 'style' => array() ), 'small' => array() ) );
1513 break;
1514 }
1515 }
1516
1517 /**
1518 * Sets what the primary column is in the post list for certain post types.
1519 *
1520 * @access public
1521 * @param string $default The default/primary column.
1522 * @param string $screen Which screen is currently being rendered.
1523 * @return array
1524 * @since 1.0.0
1525 */
1526 public function post_list_primary_column( $default, $screen ) {
1527 if ( 'edit-wpzf-submission' == $screen ) {
1528 $default = 'submission_id';
1529 }
1530
1531 return $default;
1532 }
1533
1534 /**
1535 * Handles sorting of custom columns in the post list for certain post types.
1536 *
1537 * @access public
1538 * @param WP_Query $query The current query.
1539 * @return array
1540 * @since 1.0.0
1541 */
1542 public function sort_custom_column( $query ) {
1543 $orderby = $query->get( 'orderby' );
1544
1545 if ( 'wpzf_desc' == $orderby || 'wpzf_form' == $orderby ) {
1546 $key = 'wpzf_desc' == $orderby ? '_wpzf_fields' : '_wpzf_form_id';
1547
1548 $query->set( 'meta_query', array(
1549 'relation' => 'OR',
1550 array(
1551 'key' => $key,
1552 'compare' => 'NOT EXISTS'
1553 ),
1554 array(
1555 'key' => $key
1556 )
1557 ) );
1558
1559 $query->set( 'orderby', 'meta_value' );
1560 }
1561 }
1562
1563 /**
1564 * Changes things on the edit screen of certain post types.
1565 *
1566 * @access public
1567 * @return void
1568 * @since 1.0.0
1569 */
1570 public function remove_meta_boxes() {
1571 global $current_screen;
1572
1573 if ( 'wpzf-submission' == $current_screen->post_type && 'wpzf-submission' == $current_screen->id ) {
1574 global $wp_meta_boxes;
1575
1576 $wp_meta_boxes = array(
1577 'wpzf-submission' => array(
1578 'advanced' => array(),
1579 'side' => array(
1580 'high' => array(
1581 'wpzf-submission-details-mb' => $wp_meta_boxes['wpzf-submission']['side']['high']['wpzf-submission-details-mb']
1582 )
1583 ),
1584 'normal' => array(
1585 'high' => array(
1586 'wpzf-submission-mb' => $wp_meta_boxes['wpzf-submission']['normal']['high']['wpzf-submission-mb']
1587 )
1588 )
1589 )
1590 );
1591
1592 add_screen_option( 'layout_columns', array( 'max' => 2, 'default' => 2 ) );
1593 }
1594 }
1595
1596 /**
1597 * Removes screen options where not needed.
1598 *
1599 * @access public
1600 * @param bool $show_screen Whether to show Screen Options tab.
1601 * @param WP_Screen $screen Current WP_Screen instance.
1602 * @return bool
1603 * @since 1.0.0
1604 */
1605 public function remove_screen_options( $show_screen, $screen ) {
1606 return 'wpzf-submission' == $screen->post_type && 'post' == $screen->base ? false : $show_screen;
1607 }
1608
1609 /**
1610 * Changes the views items for certain post types.
1611 *
1612 * @access public
1613 * @param array $views All the possible views.
1614 * @return bool
1615 * @since 1.0.0
1616 */
1617 public function post_list_views( $views ) {
1618 if ( isset( $views['publish'] ) ) {
1619 unset( $views['publish'] );
1620 }
1621
1622 $current_page = get_current_screen()->id;
1623
1624 ?>
1625 <div class="wpzf_wrap wpzf_settings-add-new">
1626 <?php
1627 if ( 'edit-wpzf-form' == $current_page ) {
1628 echo '<a href="' . esc_url( admin_url( 'post-new.php?post_type=wpzf-form' ) ) . '" class="button-primary">' . __( 'Add new form', 'wpzoom-forms' ) . '</a>';
1629 }
1630 ?>
1631 </div>
1632 <?php
1633
1634 return $views;
1635 }
1636
1637 /**
1638 * Adds custom meta boxes to certain post types.
1639 *
1640 * @access public
1641 * @return void
1642 * @since 1.0.0
1643 */
1644 public function add_meta_boxes() {
1645 add_meta_box(
1646 'wpzf-submission-details-mb',
1647 __( 'Submission Details', 'wpzoom-forms' ),
1648 array( $this, 'submission_meta_box_details' ),
1649 'wpzf-submission',
1650 'side',
1651 'high'
1652 );
1653
1654 add_meta_box(
1655 'wpzf-submission-mb',
1656 __( 'Submission', 'wpzoom-forms' ),
1657 array( $this, 'submission_meta_box' ),
1658 'wpzf-submission',
1659 'normal',
1660 'high'
1661 );
1662 }
1663
1664 /**
1665 * Outputs the content for the submission details sidebar meta box.
1666 *
1667 * @access public
1668 * @return void
1669 * @since 1.3.5
1670 */
1671 public function submission_meta_box_details() {
1672 $post_id = get_the_ID();
1673 $post = get_post( $post_id );
1674
1675 // Get form info
1676 $form_id = get_post_meta( $post_id, '_wpzf_form_id', true );
1677 $form_name = __( '[Unknown]', 'wpzoom-forms' );
1678 $show_form_link = false;
1679
1680 if ( $form_id > 0 ) {
1681 $form_post = get_post( $form_id );
1682
1683 if ( ! is_null( $form_post ) && 'wpzf-form' === $form_post->post_type ) {
1684 $form_title = get_the_title( $form_id );
1685
1686 if ( ! empty( $form_title ) && 'trash' !== $form_post->post_status ) {
1687 $form_name = $form_title;
1688 $show_form_link = true;
1689 } elseif ( 'trash' === $form_post->post_status ) {
1690 $form_name = sprintf(
1691 '<span style="color: #d63638;">%s</span> <small>(%s)</small>',
1692 $form_title ?: __( 'Untitled Form', 'wpzoom-forms' ),
1693 __( 'Trashed', 'wpzoom-forms' )
1694 );
1695 }
1696 }
1697 }
1698
1699 // Get submission date
1700 $date = sprintf(
1701 __( 'Submitted on %1$s at %2$s', 'wpzoom-forms' ),
1702 get_the_date(),
1703 get_the_time()
1704 );
1705
1706 // Get trash/delete link
1707 $delete_link = '';
1708 $action_text = '';
1709 if ( current_user_can( 'delete_post', $post_id ) ) {
1710 if ( 'trash' === $post->post_status ) {
1711 $delete_link = get_delete_post_link( $post_id, '', true );
1712 $action_text = __( 'Delete Permanently', 'wpzoom-forms' );
1713 } else {
1714 $delete_link = get_delete_post_link( $post_id );
1715 $action_text = _x( 'Move to Trash', 'verb', 'wpzoom-forms' );
1716 }
1717 }
1718
1719 // Output the details
1720 ?>
1721 <style>
1722 #wpzf-submission-details-mb {
1723 display: block !important;
1724 }
1725 #wpzf-submission-details-mb .inside {
1726 margin: 0;
1727 padding: 0;
1728 }
1729 .wpzf-submission-details {
1730 margin: 0;
1731 padding: 0;
1732 list-style: none;
1733 display: block !important;
1734 }
1735 .wpzf-submission-details li {
1736 margin: 0;
1737 padding: 8px 12px;
1738 border-bottom: 1px solid #eee;
1739 display: block;
1740 line-height: 1.5;
1741 }
1742 .wpzf-submission-details li:last-child {
1743 border-bottom: none;
1744 }
1745 .wpzf-submission-details li strong {
1746 display: block;
1747 margin-bottom: 4px;
1748 color: #1d2327;
1749 }
1750 .wpzf-submission-details .wpzf-submission-actions {
1751 padding: 12px;
1752 }
1753 .wpzf-submission-details .submitdelete {
1754 color: #b32d2e;
1755 text-decoration: none;
1756 }
1757 .wpzf-submission-details .submitdelete:hover {
1758 color: #a00;
1759 }
1760 </style>
1761 <ul class="wpzf-submission-details">
1762 <li>
1763 <strong><?php esc_html_e( 'Submission ID:', 'wpzoom-forms' ); ?></strong>
1764 #<?php echo esc_html( $post_id ); ?>
1765 </li>
1766 <li>
1767 <strong><?php esc_html_e( 'Form:', 'wpzoom-forms' ); ?></strong>
1768 <?php if ( $show_form_link ) : ?>
1769 <a href="<?php echo esc_url( get_edit_post_link( $form_id ) ); ?>"><?php echo esc_html( $form_name ); ?></a>
1770 <?php else : ?>
1771 <?php echo wp_kses_post( $form_name ); ?>
1772 <?php endif; ?>
1773 </li>
1774 <li>
1775 <strong><?php esc_html_e( 'Date:', 'wpzoom-forms' ); ?></strong>
1776 <?php echo esc_html( $date ); ?>
1777 </li>
1778 <?php if ( ! empty( $delete_link ) ) : ?>
1779 <li class="wpzf-submission-actions">
1780 <a href="<?php echo esc_url( $delete_link ); ?>" class="submitdelete deletion">
1781 <?php echo esc_html( $action_text ); ?>
1782 </a>
1783 </li>
1784 <?php endif; ?>
1785 </ul>
1786 <?php
1787 }
1788
1789 /**
1790 * Outputs the content for the submission meta box.
1791 *
1792 * @access public
1793 * @return void
1794 * @since 1.0.0
1795 */
1796 public function submission_meta_box() {
1797 $fields = get_post_meta( get_the_ID(), '_wpzf_fields', true );
1798
1799 if ( ! is_null( $fields ) && false !== $fields && is_array( $fields ) && ! empty( $fields ) ) {
1800 echo '<ul class="wpzf-submission-view">';
1801
1802 $form_id = intval( get_post_meta( get_the_ID(), '_wpzf_form_id', true ) );
1803 $form_name = __( '[Unknown]', 'wpzoom-forms' );
1804
1805 if ( $form_id > 0 ) {
1806 $form_name = $form_id;
1807
1808 if ( ! is_null( get_post( $form_id ) ) ) {
1809 $form_name = '<a href="' . esc_url( get_edit_post_link( $form_id ) ) . '">' . get_the_title( $form_id ) . '</a>';
1810 }
1811 }
1812
1813 $form_name = wp_kses( $form_name, array( 'a' => array( 'href' => array() ) ) );
1814
1815 echo '<li class="top"><h3>' . sprintf( __( 'Form: %s', 'wpzoom-forms' ), $form_name ) . '</h3></li>';
1816
1817 foreach ( $fields as $name => $value ) {
1818 echo '<li><h3>' . esc_html( $name ) . '</h3><div>' . make_clickable( nl2br( esc_html( $value ) ) ) . '</div></li>';
1819 }
1820
1821 echo '</ul>';
1822
1823 return;
1824 }
1825
1826 printf( '<p class="empty">%s</p>', __( 'Submission is empty&hellip;', 'wpzoom-forms' ) );
1827 }
1828
1829 /**
1830 * Returns whether the current page is related to the given post type.
1831 *
1832 * @access public
1833 * @param string $type The type to check for.
1834 * @return bool
1835 * @since 1.0.0
1836 */
1837 public function is_post_type( $type ) {
1838 $typenow = '';
1839
1840 if ( is_admin() && current_user_can( 'edit_posts' ) ) {
1841 if ( isset( $_REQUEST['post_type'] ) && post_type_exists( sanitize_key( $_REQUEST['post_type'] ) ) ) {
1842 $typenow = sanitize_key( $_REQUEST['post_type'] );
1843 } else {
1844 $post_id = -1;
1845
1846 if ( isset( $_GET['post'] ) && isset( $_POST['post_ID'] ) && (int) $_GET['post'] !== (int) $_POST['post_ID'] ) {
1847 // Do nothing
1848 } elseif ( isset( $_GET['post'] ) ) {
1849 $post_id = (int) $_GET['post'];
1850 } elseif ( isset( $_POST['post_ID'] ) ) {
1851 $post_id = (int) $_POST['post_ID'];
1852 }
1853
1854 if ( $post_id > -1 ) {
1855 $post = get_post( $post_id );
1856
1857 if ( ! is_null( $post ) && $post instanceof WP_Post ) {
1858 $typenow = $post->post_type;
1859 }
1860 }
1861 }
1862 }
1863
1864 return $type == $typenow;
1865 }
1866
1867 /**
1868 * Called to render the form block on the frontend.
1869 *
1870 * @access public
1871 * @param array $attributes An array containing the attributes for the block.
1872 * @param string $content A string containing the content of the block.
1873 * @param WP_Block $block The WP_Block representation of the block.
1874 * @return string
1875 * @since 1.0.0
1876 */
1877 public function form_block_render( $attributes, $content = '', $block = null ) {
1878 global $current_screen;
1879
1880 $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
1881
1882 // Allow rendering during REST API requests (ServerSideRender / block preview)
1883 // and during Elementor's AJAX-driven widget preview in the editor.
1884 $is_rest = defined( 'REST_REQUEST' ) && REST_REQUEST;
1885 $is_elementor_ajax = defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_REQUEST['action'] ) && strpos( sanitize_key( wp_unslash( $_REQUEST['action'] ) ), 'elementor' ) === 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1886 if ( ( is_admin() && ! $is_rest && ! $is_elementor_ajax ) || ( ! is_null( $current_screen ) && $current_screen->is_block_editor() ) ) return '';
1887
1888 // Get form ID and validate form exists and is published
1889 $form_id = isset( $attributes['formId'] ) ? intval( $attributes['formId'] ) : 0;
1890 $form_post = get_post( $form_id );
1891
1892 // Check if form exists and is published
1893 if ( ! $form_post || 'wpzf-form' !== $form_post->post_type || 'publish' !== $form_post->post_status ) {
1894 // Show message only to administrators
1895 if ( current_user_can( 'manage_options' ) ) {
1896 $message = '';
1897 if ( ! $form_post ) {
1898 $message = sprintf(
1899 __( 'Contact form not found (ID: %d). Please select a different form or create a new one.', 'wpzoom-forms' ),
1900 $form_id
1901 );
1902 } elseif ( 'wpzf-form' !== $form_post->post_type ) {
1903 $message = sprintf(
1904 __( 'Invalid form type (ID: %d). Please select a valid WPZOOM form.', 'wpzoom-forms' ),
1905 $form_id
1906 );
1907 } elseif ( 'trash' === $form_post->post_status ) {
1908 $message = sprintf(
1909 __( 'Contact form "%s" is in trash (ID: %d). Please restore it or select a different form.', 'wpzoom-forms' ),
1910 $form_post->post_title,
1911 $form_id
1912 );
1913 } else {
1914 $message = sprintf(
1915 __( 'Contact form "%s" is not published (ID: %d). Please publish it or select a different form.', 'wpzoom-forms' ),
1916 $form_post->post_title,
1917 $form_id
1918 );
1919 }
1920
1921 return sprintf(
1922 '<div class="wpzoom-forms-admin-notice" style="background: #fff; border: 1px solid #c3c4c7; border-left: 4px solid #d63638; box-shadow: 0 1px 1px rgba(0,0,0,0.04); padding: 1em 12px; margin: 1em 0;">
1923 <p style="margin: 0; font-size: 14px; color: #d63638;">
1924 <strong>%s:</strong> %s
1925 </p>
1926 <p style="margin: 8px 0 0 0; font-size: 13px; color: #646970;">
1927 <a href="%s" target="_blank">%s</a>
1928 </p>
1929 </div>',
1930 esc_html__( 'WPZOOM Forms Admin Notice', 'wpzoom-forms' ),
1931 esc_html( $message ),
1932 esc_url( admin_url( 'edit.php?post_type=wpzf-form' ) ),
1933 esc_html__( 'Manage Forms', 'wpzoom-forms' )
1934 );
1935 }
1936
1937 // Don't show anything to non-administrators
1938 return '';
1939 }
1940
1941 // The align attribute is printed inside the form's class="" attribute, so it
1942 // must be restricted to the known block alignment values — any other value
1943 // (e.g. one containing quotes) could break out of the attribute (XSS).
1944 // See CVE-2026-66639.
1945 $align = isset( $attributes['align'] ) && is_scalar( $attributes['align'] ) ? (string) $attributes['align'] : 'none';
1946 if ( ! in_array( $align, array( 'left', 'center', 'right', 'wide', 'full' ), true ) ) {
1947 $align = 'none';
1948 }
1949
1950 //Get styles from the block
1951
1952 // All style attributes are sanitized before being printed inside the inline
1953 // <style> block below. Block attributes are attacker-controllable by any user
1954 // who can edit the form/post (e.g. Contributor role), so unsanitized values
1955 // would allow breaking out of the <style> tag and injecting scripts (XSS).
1956 // See CVE-2026-66639.
1957 $fieldBgColor = isset( $attributes['fieldBgColor'] ) ? $this->sanitize_css_color( $attributes['fieldBgColor'] ) : '';
1958 $fieldBrdStyle = isset( $attributes['fieldBrdStyle'] ) ? $this->sanitize_css_border_style( $attributes['fieldBrdStyle'] ) : '';
1959 $fieldBrdWidth = isset( $attributes['fieldBrdWidth'] ) ? $this->sanitize_css_length( $attributes['fieldBrdWidth'] ) : '';
1960 $fieldBrdRadius = isset( $attributes['fieldBrdRadius'] ) ? $this->sanitize_css_length( $attributes['fieldBrdRadius'] ) : '';
1961 $fieldBrdColor = isset( $attributes['fieldBrdColor'] ) ? $this->sanitize_css_color( $attributes['fieldBrdColor'] ) : '';
1962 $fieldTextColor = isset( $attributes['fieldTextColor'] ) ? $this->sanitize_css_color( $attributes['fieldTextColor'] ) : '';
1963 $labelTextColor = isset( $attributes['labelTextColor'] ) ? $this->sanitize_css_color( $attributes['labelTextColor'] ) : '';
1964 $btnBrdRadius = isset( $attributes['btnBrdRadius'] ) ? $this->sanitize_css_length( $attributes['btnBrdRadius'] ) : '';
1965 $btnBrdStyle = isset( $attributes['btnBrdStyle'] ) ? $this->sanitize_css_border_style( $attributes['btnBrdStyle'] ) : '';
1966 $btnTextColor = isset( $attributes['btnTextColor'] ) ? $this->sanitize_css_color( $attributes['btnTextColor'] ) : '';
1967 $btnBrdWidth = isset( $attributes['btnBrdWidth'] ) ? $this->sanitize_css_length( $attributes['btnBrdWidth'] ) : '';
1968 $btnBrdColor = isset( $attributes['btnBrdColor'] ) ? $this->sanitize_css_color( $attributes['btnBrdColor'] ) : '';
1969 $btnBgColor = isset( $attributes['btnBgColor'] ) ? $this->sanitize_css_color( $attributes['btnBgColor'] ) : '';
1970
1971 $form_ID = 'wpzf-' . intval( $attributes['formId'] );
1972 $form_notice_id = $form_ID . '-notice';
1973 $form_success_message = get_post_meta( intval( $attributes['formId'] ), '_form_success_message', true );
1974 $form_failure_message = get_post_meta( intval( $attributes['formId'] ), '_form_failure_message', true );
1975
1976 // Use default messages if custom ones are not set
1977 if ( empty( $form_success_message ) ) {
1978 $form_success_message = __( 'Thanks! We\'ve received your submission!', 'wpzoom-forms' );
1979 }
1980 if ( empty( $form_failure_message ) ) {
1981 $form_failure_message = __( 'Submission failed!', 'wpzoom-forms' );
1982 }
1983
1984 // Enqueue core block library styles for inner blocks (columns, group, spacer, etc.)
1985 wp_enqueue_style( 'wp-block-library' );
1986
1987 // Process form content through do_blocks() to render inner blocks and enqueue their styles
1988 $form_content = get_post_field( 'post_content', intval( $attributes['formId'] ), 'raw' );
1989 $form_content = do_blocks( $form_content );
1990 $form_content = preg_replace(
1991 array( '/<!--(.*)-->/Uis', '/<(input|textarea|select)(.*)name="([^"]+)"/Uis' ),
1992 array( '', '<$1$2name="wpzf_$3"' ),
1993 $form_content
1994 );
1995
1996 // If this (legacy) form actually contains a date field, make sure the
1997 // datepicker assets load. We enqueue here — at render time — rather than
1998 // relying solely on the page-scan in block_frontend_assets(), which only
1999 // inspects a single form-block per page and misses multi-form pages,
2000 // shortcode and Elementor embeds. The scripts are registered in_footer so
2001 // this late enqueue still prints correctly.
2002 if ( false !== strpos( $form_content, 'data-datepicker' ) ) {
2003 wp_enqueue_style( 'wpzoom-forms-css-frontend-flatpickr' );
2004 wp_enqueue_script( 'wpzoom-forms-js-frontend-flatpickr' );
2005 wp_enqueue_script( 'wpzoom-forms-js-frontend-datepicker' );
2006 }
2007
2008 $submitted_form_id = isset( $_GET['wpzf_submitted_form'] ) ? intval( $_GET['wpzf_submitted_form'] ) : -1;
2009 $show_notice = isset( $_GET['success'] ) && ( -1 === $submitted_form_id || $submitted_form_id === intval( $attributes['formId'] ) );
2010
2011 $content = sprintf(
2012 '<!-- ZOOM Forms Start -->
2013 <form id="wpzf-%2$s" method="post" action="%1$s" class="wpzoom-forms_form%6$s">
2014 <input type="hidden" name="action" value="wpzf_submit" />
2015 <input type="hidden" name="form_id" value="%2$s" />
2016 %3$s
2017 %5$s
2018 </form>
2019 %4$s
2020 <!-- ZOOM Forms End -->',
2021 admin_url( 'admin-post.php' ),
2022 intval( $attributes['formId'] ),
2023 wp_nonce_field( 'wpzf_submit', '_wpnonce', true, false ),
2024 ( $show_notice
2025 ? '<div id="' . esc_attr( $form_notice_id ) . '" class="notice ' . ( '1' == $_GET['success'] ? 'success' : 'error' ) . '" tabindex="-1"><p>' .
2026 ( '1' == $_GET['success'] ? wp_kses_post($form_success_message) : wp_kses_post($form_failure_message) ) .
2027 '</p></div>'
2028 : ''
2029 ),
2030 $form_content,
2031 ( 'none' !== $align ? ' align' . $align : '' )
2032 );
2033
2034 preg_match( '/<input(?:.*)name="([^"]+)"(?:.*)data-replyto="true"/is', $content, $match1 );
2035 preg_match( '/<input(?:.*)name="([^"]+)"(?:.*)data-subject="true"/is', $content, $match2 );
2036
2037 if ( ! empty( $match1 ) && is_array( $match1 ) && isset( $match1[1] ) ) {
2038 $content = preg_replace( '/<\/form>/is', '<input type="hidden" name="wpzf_replyto" value="' . esc_attr( $match1[1] ) . '" /></form>', $content );
2039 }
2040
2041 if ( ! empty( $match2 ) && is_array( $match2 ) && isset( $match2[1] ) ) {
2042 $content = preg_replace( '/<\/form>/is', '<input type="hidden" name="wpzf_subject" value="' . esc_attr( $match2[1] ) . '" /></form>', $content );
2043 }
2044
2045 $captcha_config = $this->get_spam_protection_config();
2046 $captcha_method = $captcha_config['active_service'];
2047 $recaptcha_type = $captcha_config['type'];
2048 $recaptcha_badge_location = esc_attr( sanitize_text_field( WPZOOM_Forms_Settings::get( 'wpzf_global_recaptcha_badge_location' ) ) );
2049 $turnstile_site_key = esc_attr( $captcha_config['turnstile_site_key'] );
2050 $turnstile_widget_theme = esc_attr( sanitize_text_field( WPZOOM_Forms_Settings::get( 'wpzf_global_turnstile_widget_theme' ) ) );
2051
2052 if ( 'recaptcha' === $captcha_method ) {
2053 $recaptcha_site_key = ( 'v3' === $recaptcha_type ) ? $captcha_config['recaptcha_v3_site_key'] : $captcha_config['recaptcha_v2_site_key'];
2054 $data_badge_location = '';
2055 if ( ! empty( $recaptcha_badge_location ) ) {
2056 $data_badge_location = 'data-badge="' . $recaptcha_badge_location . '"';
2057 }
2058
2059 $content = preg_replace( '/<input([^>]*)type="submit"([^>]*)class="([^"]+)"/i', '<input $1 type="submit" data-sitekey="' . $recaptcha_site_key . '" data-callback="wpzf_submit" data-action="submit" ' . $data_badge_location . ' $2 class="$3 g-recaptcha"', $content );
2060 } elseif ( 'turnstile' === $captcha_method ) {
2061 $turnstile_widget = '<div class="cf-turnstile" data-theme="' . $turnstile_widget_theme . '" data-sitekey="' . $turnstile_site_key . '"></div>';
2062 $content = preg_replace( '/<input([^>]*)type="submit"([^>]*)class="([^"]+)"([^>]*)>/i', '<input $1 type="submit" data-callback="wpzf_submit" data-action="submit" $2 class="$3 cf-captcha" $4>' . $turnstile_widget, $content );
2063 }
2064
2065 $style = $styleOutput = '';
2066
2067 // Add custom styles to the form
2068 $field_sel = '#' . $form_ID . ' input:not([type="submit"]), #' . $form_ID . ' textarea, #' . $form_ID . ' select';
2069 if( ! empty( $fieldBgColor ) ) {
2070 $styleOutput .= sprintf( $field_sel . ' { background-color: %s; }', $fieldBgColor );
2071 }
2072 if( ! empty( $fieldBrdStyle ) && 'default' !== $fieldBrdStyle ) {
2073 $styleOutput .= sprintf( $field_sel . ' { border-style: %s; }', $fieldBrdStyle );
2074 if( ! empty( $fieldBrdWidth ) ) {
2075 $styleOutput .= sprintf( $field_sel . ' { border-width: %s; }', $fieldBrdWidth );
2076 }
2077 if( ! empty( $fieldBrdRadius ) ) {
2078 $styleOutput .= sprintf( $field_sel . ' { border-radius: %s; }', $fieldBrdRadius );
2079 }
2080 }
2081
2082 if( ! empty( $fieldBrdColor ) ) {
2083 $styleOutput .= sprintf( $field_sel . ' { border-color: %s; }', $fieldBrdColor );
2084 }
2085 if( ! empty( $fieldTextColor ) ) {
2086 $styleOutput .= sprintf( $field_sel . ' { color: %s; }', $fieldTextColor );
2087 }
2088
2089 //Label styles
2090 if( ! empty( $labelTextColor ) ) {
2091 $styleOutput .= sprintf( '#' . $form_ID . ' label { color: %s; }', $labelTextColor );
2092 }
2093
2094 //Button styles — target both old <input type="submit"> and new builder's <button class="wpzf-submit__btn">
2095 $btn_sel = '#' . $form_ID . ' input[type="submit"], #' . $form_ID . ' button.wpzf-submit__btn';
2096 if( ! empty( $btnBrdStyle ) && 'default' !== $btnBrdStyle ) {
2097 $styleOutput .= sprintf( $btn_sel . ' { border-style: %s; }', $btnBrdStyle );
2098 if( ! empty( $btnBrdWidth ) ) {
2099 $styleOutput .= sprintf( $btn_sel . ' { border-width: %s; }', $btnBrdWidth );
2100 }
2101 if( ! empty( $btnBrdRadius ) ) {
2102 $styleOutput .= sprintf( $btn_sel . ' { border-radius: %s; }', $btnBrdRadius );
2103 }
2104 }
2105 if( ! empty( $btnTextColor ) ) {
2106 $styleOutput .= sprintf( $btn_sel . ' { color: %s; }', $btnTextColor );
2107 }
2108 if( ! empty( $btnBrdColor ) ) {
2109 $styleOutput .= sprintf( $btn_sel . ' { border-color: %s; }', $btnBrdColor );
2110 }
2111 if( ! empty( $btnBgColor ) ) {
2112 $styleOutput .= sprintf( $btn_sel . ' { background-color: %s; }', $btnBgColor );
2113 }
2114
2115 // Add styles for the notice
2116 $styleOutput .= sprintf( '#%s + .notice { margin-top: 20px; padding: 15px; border-radius: 4px; }', $form_ID );
2117 $styleOutput .= sprintf( '#%s + .notice.success { background-color: #e7f7ed; color: #227045; border-left: 4px solid #46b450; }', $form_ID );
2118 $styleOutput .= sprintf( '#%s + .notice.error { background-color: #fde8e8; color: #8a1f11; border-left: 4px solid #cc0000; }', $form_ID );
2119 $styleOutput .= sprintf( '#%s + .notice p { margin: 0; }', $form_ID );
2120 $styleOutput .= sprintf( '#%s { scroll-margin-top: 24px; }', $form_notice_id );
2121
2122 $style = sprintf( '<style>%s</style>',
2123 $styleOutput
2124 );
2125
2126 $content = $content . $style;
2127
2128 return $content;
2129 }
2130
2131 /**
2132 * Sanitize a CSS color value coming from a block attribute.
2133 *
2134 * Block attributes can be set by any user able to edit the form/post
2135 * (including low-privilege roles such as Contributor), so these values are
2136 * untrusted. Because the value is printed inside an inline <style> element,
2137 * we must strip any character that could be used to terminate the property,
2138 * rule or the <style> tag itself (e.g. `;`, `{`, `}`, `<`, `>`, quotes).
2139 *
2140 * We allow only the characters that make up legitimate CSS color values:
2141 * hex (`#fff`), functional notation (`rgb()`, `rgba()`, `hsl()`, `hsla()`,
2142 * `var()`) and named colors. Anything else is removed.
2143 *
2144 * @access private
2145 * @param mixed $value The raw attribute value.
2146 * @return string A safe CSS color value (may be empty).
2147 * @since 2.0.7
2148 */
2149 private function sanitize_css_color( $value ) {
2150 if ( ! is_scalar( $value ) ) {
2151 return '';
2152 }
2153
2154 $value = (string) $value;
2155
2156 // Keep only characters valid within CSS color values.
2157 $value = preg_replace( '/[^a-zA-Z0-9#(),.%\s_-]/', '', $value );
2158
2159 return trim( $value );
2160 }
2161
2162 /**
2163 * Sanitize a CSS border-style keyword coming from a block attribute.
2164 *
2165 * Only the standard CSS border-style keywords are permitted; any other
2166 * value is discarded to prevent injection into the inline <style> block.
2167 *
2168 * @access private
2169 * @param mixed $value The raw attribute value.
2170 * @return string A valid border-style keyword, or empty string.
2171 * @since 2.0.7
2172 */
2173 private function sanitize_css_border_style( $value ) {
2174 if ( ! is_scalar( $value ) ) {
2175 return '';
2176 }
2177
2178 $allowed = array( 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset' );
2179 $value = strtolower( trim( (string) $value ) );
2180
2181 return in_array( $value, $allowed, true ) ? $value : '';
2182 }
2183
2184 /**
2185 * Sanitize a CSS length coming from a numeric block attribute.
2186 *
2187 * The value is cast to a number and returned with a `px` unit, matching the
2188 * behaviour expected by the inline <style> block. This guarantees the output
2189 * contains only digits, an optional decimal point/sign and the `px` unit,
2190 * so it can never break out of the CSS context.
2191 *
2192 * @access private
2193 * @param mixed $value The raw attribute value.
2194 * @return string A safe CSS length (e.g. `2px`).
2195 * @since 2.0.7
2196 */
2197 private function sanitize_css_length( $value ) {
2198 return floatval( $value ) . 'px';
2199 }
2200
2201 /**
2202 * Render the contents of the settings page in the admin.
2203 *
2204 * @access public
2205 * @return void
2206 * @since 1.0.0
2207 */
2208 public function render_settings_page() {
2209 do_action( 'wpzoom_forms_admin_page' );
2210 }
2211
2212 /**
2213 * Render the contents of the upsell page in the admin.
2214 *
2215 * @access public
2216 * @return void
2217 * @since 1.0.0
2218 */
2219 public function render_upsell_page() {
2220 do_action( 'wpzoom_forms_admin_page_upsell' );
2221 }
2222
2223 /**
2224 * Render the Import & Export upsell page.
2225 *
2226 * @access public
2227 * @return void
2228 * @since 1.3.4
2229 */
2230 public function render_import_export_upsell_page() {
2231 if ( ! current_user_can( 'edit_posts' ) ) {
2232 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wpzoom-forms' ) );
2233 }
2234
2235 $pro_url = 'https://www.wpzoom.com/plugins/wpzoom-forms/?utm_source=wpadmin&utm_medium=wpzoom-forms-free&utm_campaign=import-export-upsell';
2236 ?>
2237 <div class="wrap wpzoom-forms-import-export-upsell-page" style="position: relative;">
2238 <h1><?php esc_html_e( 'Import & Export Forms', 'wpzoom-forms' ); ?></h1>
2239 <div class="wpzoom-forms-import-export-upsell-container">
2240 <!-- Blurred placeholder content -->
2241 <div class="wpzoom-forms-import-export-blurred-content" style="filter: blur(4px); pointer-events: none; opacity: 0.3; margin-bottom: 40px;">
2242 <div style="background: #fff; border: 1px solid #c3c4c7; padding: 20px; margin: 20px 0; border-radius: 4px;">
2243 <div style="display: flex; gap: 20px; margin-bottom: 30px;">
2244 <div style="flex: 1;">
2245 <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #1d2327;"><?php esc_html_e( 'Export Forms', 'wpzoom-forms' ); ?></label>
2246 <select style="width: 100%; padding: 8px; border: 1px solid #c3c4c7; border-radius: 4px;">
2247 <option><?php esc_html_e( 'Select forms to export...', 'wpzoom-forms' ); ?></option>
2248 <option><?php esc_html_e( 'Contact Form', 'wpzoom-forms' ); ?></option>
2249 <option><?php esc_html_e( 'Newsletter Form', 'wpzoom-forms' ); ?></option>
2250 </select>
2251 </div>
2252 <div style="flex: 1;">
2253 <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #1d2327;"><?php esc_html_e( 'Export Format', 'wpzoom-forms' ); ?></label>
2254 <select style="width: 100%; padding: 8px; border: 1px solid #c3c4c7; border-radius: 4px;">
2255 <option>JSON</option>
2256 <option>CSV</option>
2257 </select>
2258 </div>
2259 </div>
2260 <div style="display: flex; gap: 20px; margin-bottom: 30px;">
2261 <div style="flex: 1;">
2262 <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #1d2327;"><?php esc_html_e( 'Import Forms', 'wpzoom-forms' ); ?></label>
2263 <input type="file" style="width: 100%; padding: 8px; border: 1px solid #c3c4c7; border-radius: 4px;" />
2264 </div>
2265 <div style="flex: 1;">
2266 <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #1d2327;"><?php esc_html_e( 'Export Submissions', 'wpzoom-forms' ); ?></label>
2267 <select style="width: 100%; padding: 8px; border: 1px solid #c3c4c7; border-radius: 4px;">
2268 <option><?php esc_html_e( 'Select form...', 'wpzoom-forms' ); ?></option>
2269 </select>
2270 </div>
2271 </div>
2272 <div style="display: flex; gap: 10px;">
2273 <button style="padding: 10px 20px; background: #2271b1; color: #fff; border: none; border-radius: 4px; cursor: pointer;"><?php esc_html_e( 'Export', 'wpzoom-forms' ); ?></button>
2274 <button style="padding: 10px 20px; background: #2271b1; color: #fff; border: none; border-radius: 4px; cursor: pointer;"><?php esc_html_e( 'Import', 'wpzoom-forms' ); ?></button>
2275 <button style="padding: 10px 20px; background: #2271b1; color: #fff; border: none; border-radius: 4px; cursor: pointer;"><?php esc_html_e( 'Export CSV', 'wpzoom-forms' ); ?></button>
2276 </div>
2277 </div>
2278 </div>
2279
2280 <!-- Overlay with Popover Modal -->
2281 <div class="import-export-upsell-overlay">
2282 <div class="wpzoom-forms-import-export-popover" style="background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); padding: 40px; max-width: 500px; width: 90%;">
2283 <!-- Icon -->
2284 <div style="text-align: center; margin-bottom: 20px;">
2285 <div style="width: 60px; height: 60px; margin: 0 auto; background-color: #3496FF; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px rgba(52, 150, 255, 0.3);">
2286 <svg width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2287 <path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" fill="#fff"/>
2288 </svg>
2289 </div>
2290 </div>
2291
2292 <!-- Title -->
2293 <h2 style="text-align: center; margin: 0 0 15px 0; font-size: 24px; font-weight: 600; color: #1d2327;">
2294 <?php esc_html_e( 'Unlock Import & Export', 'wpzoom-forms' ); ?>
2295 </h2>
2296
2297 <!-- Description -->
2298 <p style="text-align: center; margin: 0 0 25px 0; color: #50575e; font-size: 14px; line-height: 1.6;">
2299 <?php esc_html_e( 'Transfer your forms between sites, back up your work, and export submissions for analysis. Manage your forms with powerful import and export tools.', 'wpzoom-forms' ); ?>
2300 </p>
2301
2302 <!-- Features List -->
2303 <ul style="list-style: none; padding: 0; margin: 0 0 30px 0;">
2304 <li style="display: flex; align-items: flex-start; margin-bottom: 12px; color: #1d2327; font-size: 14px;">
2305 <span style="display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; background: #3496FF; border-radius: 50%; margin-right: 12px; flex-shrink: 0; margin-top: 2px;">
2306 <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2307 <path d="M10 3L4.5 8.5L2 6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2308 </svg>
2309 </span>
2310 <span><?php esc_html_e( 'Export forms to JSON files for backup or migration', 'wpzoom-forms' ); ?></span>
2311 </li>
2312 <li style="display: flex; align-items: flex-start; margin-bottom: 12px; color: #1d2327; font-size: 14px;">
2313 <span style="display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; background: #3496FF; border-radius: 50%; margin-right: 12px; flex-shrink: 0; margin-top: 2px;">
2314 <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2315 <path d="M10 3L4.5 8.5L2 6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2316 </svg>
2317 </span>
2318 <span><?php esc_html_e( 'Import forms from JSON files to quickly set up new sites', 'wpzoom-forms' ); ?></span>
2319 </li>
2320 <li style="display: flex; align-items: flex-start; margin-bottom: 12px; color: #1d2327; font-size: 14px;">
2321 <span style="display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; background: #3496FF; border-radius: 50%; margin-right: 12px; flex-shrink: 0; margin-top: 2px;">
2322 <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2323 <path d="M10 3L4.5 8.5L2 6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2324 </svg>
2325 </span>
2326 <span><?php esc_html_e( 'Export form submissions to CSV for data analysis', 'wpzoom-forms' ); ?></span>
2327 </li>
2328 <li style="display: flex; align-items: flex-start; margin-bottom: 12px; color: #1d2327; font-size: 14px;">
2329 <span style="display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; background: #3496FF; border-radius: 50%; margin-right: 12px; flex-shrink: 0; margin-top: 2px;">
2330 <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2331 <path d="M10 3L4.5 8.5L2 6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2332 </svg>
2333 </span>
2334 <span><?php esc_html_e( 'Transfer forms between multiple WordPress sites', 'wpzoom-forms' ); ?></span>
2335 </li>
2336 <li style="display: flex; align-items: flex-start; margin-bottom: 12px; color: #1d2327; font-size: 14px;">
2337 <span style="display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; background: #3496FF; border-radius: 50%; margin-right: 12px; flex-shrink: 0; margin-top: 2px;">
2338 <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2339 <path d="M10 3L4.5 8.5L2 6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2340 </svg>
2341 </span>
2342 <span><?php esc_html_e( 'Backup your forms before making major changes', 'wpzoom-forms' ); ?></span>
2343 </li>
2344 </ul>
2345
2346 <!-- CTA Button -->
2347 <div style="text-align: center; margin-bottom: 15px;">
2348 <a href="<?php echo esc_url( $pro_url ); ?>" class="button button-primary button-large" style="background-color: #3496FF; border: none; border-radius: 6px; padding: 16px 20px; line-height: 1; font-size: 16px; font-weight: 600; color: #fff; text-decoration: none; display: inline-block; box-shadow: 0 4px 12px rgba(52, 150, 255, 0.3); transition: transform 0.2s, box-shadow 0.2s;" target="_blank" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 6px 16px rgba(52, 150, 255, 0.4)';" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 4px 12px rgba(52, 150, 255, 0.3)';">
2349 <?php esc_html_e( 'Upgrade to PRO', 'wpzoom-forms' ); ?>
2350 </a>
2351 </div>
2352
2353 <!-- Plugin Attribution -->
2354 <p style="text-align: center; margin: 0; color: #8c8f94; font-size: 12px;">
2355 <?php esc_html_e( 'Part of the WPZOOM Forms PRO plugin', 'wpzoom-forms' ); ?>
2356 </p>
2357 </div>
2358 </div>
2359 </di>
2360 </div>
2361
2362 <style>
2363 .wpzoom-forms-import-export-upsell-container {
2364 position: relative;
2365 min-height: 680px;
2366 }
2367 .wpzoom-forms-import-export-blurred-content {
2368 z-index: 1;
2369 }
2370 .import-export-upsell-overlay {
2371 position: absolute;
2372 top: 60px;
2373 left: 0;
2374 right: 0;
2375 bottom: 0;
2376 display: flex;
2377 align-items: flex-start;
2378 justify-content: center;
2379 padding-top: 80px;
2380 z-index: 100;
2381 background: hsla(0, 0%, 100%, .1);
2382 }
2383 .wpzoom-forms-import-export-popover {
2384 z-index: 10000;
2385 }
2386 @media screen and (max-width: 782px) {
2387 .wpzoom-forms-import-export-popover {
2388 padding: 30px 20px;
2389 max-width: 90%;
2390 }
2391 }
2392 </style>
2393 <?php
2394 }
2395
2396 /**
2397 * Page header used on all admin pages.
2398 *
2399 * @access public
2400 * @return void
2401 * @since 1.0.0
2402 */
2403 public function admin_page_header() {
2404
2405 $current_page = get_current_screen()->id;
2406
2407 if ( 'edit-wpzf-form' == $current_page || 'edit-wpzf-submission' == $current_page || 'wpzf-submission' == $current_page || 'wpzf-form_page_wpzf-settings' == $current_page || 'wpzf-form_page_wpzoom-forms-pro-license' == $current_page || 'wpzf-form_page_wpzf-pro-page' == $current_page ) {
2408 ?>
2409 <header class="wpzoom-new-admin-wrap wpzoom-new-admin_settings-header">
2410 <h1 class="wpzoom-new-admin_settings-main-title wp-heading">
2411 <?php
2412 echo apply_filters(
2413 'wpzf_admin-header-title',
2414 sprintf(
2415 __( 'WPZOOM Forms <small>Lite</small>', 'wpzoom-forms' )
2416 )
2417 );
2418 ?>
2419
2420 <span class="wpzoom-new-admin_settings-main-title-version">
2421 <?php
2422 echo apply_filters(
2423 'wpzf_admin-header-title-version',
2424 sprintf(
2425 esc_html__( 'v %s', 'wpzoom-forms' ),
2426 WPZOOM_FORMS_VERSION
2427 )
2428 );
2429 ?>
2430 </span>
2431 </h1>
2432
2433 <nav class="wpzoom-new-admin_settings-main-nav">
2434 <ul>
2435 <?php
2436 $pages = apply_filters(
2437 'wpzoom_forms_settings_menu_items',
2438 array(
2439 'edit-wpzf-form' => array(
2440 'name' => __( 'Forms', 'wpzoom-forms' ),
2441 'url' => admin_url( 'edit.php?post_type=wpzf-form' ),
2442 ),
2443 'edit-wpzf-submission' => array(
2444 'name' => __( 'Submissions', 'wpzoom-forms' ),
2445 'url' => admin_url( 'edit.php?post_type=wpzf-submission' ),
2446 'altid' => 'wpzf-submission'
2447 ),
2448 'wpzf-form_page_wpzf-settings' => array(
2449 'name' => esc_html__( 'Settings', 'wpzoom-forms' ),
2450 'url' => admin_url( 'edit.php?post_type=wpzf-form&page=wpzf-settings' ),
2451 ),
2452 'wpzf-form_page_wpzf-pro-page' => array(
2453 'name' => esc_html__( 'Upgrade to PRO', 'wpzoom-forms' ),
2454 'url' => admin_url( 'edit.php?post_type=wpzf-form&page=wpzf-pro-page' ),
2455 ),
2456 'wpzf-form_page_wpzf-import' => array(
2457 'name' => esc_html__( 'Import & Export', 'wpzoom-forms' ),
2458 'url' => admin_url( 'edit.php?post_type=wpzf-form&page=wpzf-import' ),
2459 ),
2460 )
2461 );
2462
2463 foreach ( $pages as $id => $atts ) {
2464 printf(
2465 // translators: %1$s = possible class attribute, %2$s = page url, %3$s = page name.
2466 _x( '<li%1$s><a href="%2$s">%3$s</a></li>', 'Main menu page item', 'wpzoom-forms' ),
2467 ( $current_page === $id || ( isset( $atts['altid'] ) && $current_page == $atts['altid'] ) ? ' class="active"' : '' ),
2468 esc_url( $atts['url'] ),
2469 esc_html( $atts['name'] )
2470 );
2471 }
2472 ?>
2473 </ul>
2474 </nav>
2475 </header>
2476 <?php
2477 }
2478
2479 if ( 'edit-wpzf-form' == $current_page || 'wpzf-form' == $current_page ) {
2480 $forms_count = wp_count_posts( 'wpzf-form' );
2481
2482 if ( $forms_count->publish < 1 && $forms_count->draft < 1 && $forms_count->trash < 1 ) {
2483 ?>
2484 <div class="wpzf_no-forms">
2485 <div class="left-column">
2486 <h3>
2487 <?php esc_html_e( 'Hi there!', 'wpzoom-forms' ); ?>
2488 </h3>
2489
2490 <h2>
2491 <?php esc_html_e( 'It appears that you haven&rsquo;t made any forms yet.', 'wpzoom-forms' ); ?>
2492 </h2>
2493
2494 <p>
2495 <?php esc_html_e( 'With WPZOOM Forms, you have the ability to create contact forms, surveys, and various other types of forms effortlessly and swiftly.', 'wpzoom-forms' ); ?>
2496 </p>
2497
2498 <a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=wpzf-form' ) ); ?>" class="button-primary wpzf-add-form-btn">
2499 <?php esc_html_e( 'Add new form', 'wpzoom-forms' ); ?>
2500 </a>
2501 </div>
2502
2503 <div class="right-column">
2504 <svg width="360" height="360" fill="none" xmlns="http://www.w3.org/2000/svg">
2505 <mask id="a" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="25" y="140" width="304" height="175">
2506 <path d="M305.985 314.458H47.535a24.293 24.293 0 0 1-3.753-.29c-3.966-.621-7.575-2.21-10.528-4.493a21.616 21.616 0 0 1-2.11-1.862.535.535 0 0 0 .502-.168l94.603-106.18 50.511 30.403 50.49-30.425 94.623 106.202a.531.531 0 0 0 .504.168 21.988 21.988 0 0 1-4.388 3.406 22.998 22.998 0 0 1-6.489 2.603 24 24 0 0 1-5.515.636zm16.809-7.078a.517.517 0 0 0-.126-.438l-37.526-42.119c.465.15.961.263 1.488.328 4.522.558 10.117-.941 13.717 7.925 1.072 2.64 2.755 3.646 4.524 3.646 4.168 0 8.8-5.601 6.971-8.59-2.605-4.255 2.223-7.364 4.674-12.255 1.591-3.176-.007-5.899-3.066-7.309-2.354-1.081-6.664-.35-7.987-3.181-.961-2.057-.308-4.881-.825-7.094-.735-3.151-2.695-4.059-4.592-4.059-1.46 0-2.883.538-3.683 1.003-1.056.616-2.896 1.835-4.431 1.835-1.137 0-2.108-.671-2.466-2.751-.628-3.647-4.487-5.975-8.17-5.975-1.26 0-2.499.273-3.581.859-.469.23-.835.496-1.121.781-2.489 2.275 0 5.251 0 5.251-4.981 4.511.69 11.772 2.376 13.454 1.684 1.682 3.294 4.052 2.067 8.025-.341 1.111-.408 2.215-.23 3.241l-52.631-59.072 24.464-14.743 75.566-45.435V294.001c0 4.5-1.578 8.66-4.253 12.039-.366.461-.753.909-1.159 1.34zm-292.07 0c-.024-.025-.048-.053-.073-.078-3.327-3.577-5.337-8.222-5.337-13.301V140.707l75.566 45.486 24.443 14.714-94.471 106.035a.522.522 0 0 0-.128.438z" fill="#fff" />
2507 </mask>
2508 <g mask="url(#a)">
2509 <path d="M305.983 314.458H47.534a24.564 24.564 0 0 1-3.752-.29c-3.966-.621-7.575-2.21-10.528-4.493a21.583 21.583 0 0 1-2.111-1.862.537.537 0 0 0 .503-.168l94.603-106.18 50.509 30.404 50.491-30.426 94.622 106.202a.538.538 0 0 0 .504.168 21.982 21.982 0 0 1-4.387 3.406 23.044 23.044 0 0 1-6.49 2.604c-1.762.415-3.61.635-5.515.635zm16.809-7.078a.513.513 0 0 0-.125-.438l-37.525-42.118c.465.15.961.262 1.488.327 4.52.559 10.117-.941 13.717 7.925 1.071 2.641 2.754 3.647 4.523 3.647 4.169 0 8.799-5.602 6.971-8.591-2.604-4.255 2.222-7.364 4.674-12.255 1.59-3.176-.009-5.899-3.067-7.308-2.352-1.082-6.663-.351-7.986-3.181-.961-2.058-.309-4.882-.825-7.094-.734-3.152-2.695-4.06-4.591-4.06-1.462 0-2.885.538-3.684 1.004-1.057.615-2.896 1.834-4.43 1.834-1.136 0-2.108-.67-2.468-2.751-.626-3.646-4.486-5.974-8.168-5.974-1.261 0-2.499.273-3.582.858a4.227 4.227 0 0 0-1.122.781c-2.488 2.276 0 5.252 0 5.252-4.98 4.51.691 11.771 2.378 13.453 1.683 1.683 3.293 4.053 2.066 8.025-.341 1.111-.409 2.215-.23 3.241l-52.631-59.071 24.464-14.743 75.566-45.436V294.001c0 4.501-1.579 8.661-4.254 12.04-.365.46-.751.908-1.159 1.339zm-292.067 0-.074-.078c-3.327-3.577-5.337-8.222-5.337-13.301V140.708l75.565 45.485 24.444 14.715-94.471 106.034a.522.522 0 0 0-.127.438z" fill="#083EA7" />
2510 </g>
2511 <mask id="b" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="25" y="52" width="304" height="180">
2512 <path d="m176.76 231.868-50.511-30.403.038-.04a.533.533 0 0 0-.399-.883.53.53 0 0 0-.396.178l-.169.187-24.443-14.714-75.565-45.486-.315-.19 17.42-10.512 58.46-35.278 65.693-39.64c3.197-1.93 6.689-2.894 10.184-2.894 3.496 0 6.993.966 10.19 2.894L252.64 94.74l60.632 36.579 15.248 9.198-.314.19-75.566 45.435-24.464 14.743-.148-.165a.53.53 0 0 0-.75-.044.533.533 0 0 0-.045.749l.017.018-50.49 30.425z" fill="#fff" />
2513 </mask>
2514 <g mask="url(#b)">
2515 <path d="m176.76 231.868-50.511-30.404.038-.039a.533.533 0 0 0-.399-.883.53.53 0 0 0-.396.178l-.169.187-24.443-14.714-75.565-45.486-.315-.19 17.42-10.512 58.46-35.277 65.693-39.643c3.196-1.929 6.69-2.89 10.184-2.89 3.497 0 6.993.964 10.189 2.89l65.695 39.655 60.63 36.579 15.249 9.198-.315.189-75.564 45.436-24.466 14.743-.147-.165a.529.529 0 0 0-.749-.044.533.533 0 0 0-.045.749l.017.018-50.491 30.425z" fill="#242628" />
2516 </g>
2517 <mask id="c" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="30" y="200" width="97" height="108">
2518 <path d="M31.25 307.823a.536.536 0 0 1-.107-.01 26.971 26.971 0 0 1-.418-.433.522.522 0 0 1 .127-.438l94.472-106.035.926.558-94.604 106.18a.53.53 0 0 1-.397.178z" fill="#fff" />
2519 </mask>
2520 <g mask="url(#c)">
2521 <path d="M31.25 307.823a.536.536 0 0 1-.107-.01 26.971 26.971 0 0 1-.418-.433.522.522 0 0 1 .127-.438l94.472-106.035.926.558-94.604 106.18a.53.53 0 0 1-.397.178z" fill="#fff" />
2522 </g>
2523 <mask id="d" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="227" y="200" width="96" height="108">
2524 <path d="M322.272 307.823a.531.531 0 0 1-.399-.178L227.25 201.443l.926-.558 52.631 59.072c.386 2.223 1.927 4.085 4.335 4.866l37.526 42.119a.517.517 0 0 1 .126.438c-.136.147-.276.29-.417.433a.533.533 0 0 1-.105.01z" fill="#fff" />
2525 </mask>
2526 <g mask="url(#d)">
2527 <path d="M322.27 307.822a.532.532 0 0 1-.397-.177l-94.622-106.202.924-.558 52.63 59.072c.387 2.223 1.927 4.085 4.336 4.866l37.527 42.118a.523.523 0 0 1 .126.439c-.137.147-.277.29-.417.433a.533.533 0 0 1-.107.009z" fill="#fff" />
2528 </g>
2529 <mask id="e" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="274" y="228" width="44" height="49">
2530 <path d="M304.871 276.722c-1.769 0-3.452-1.006-4.524-3.646-3.6-8.866-9.196-7.367-13.717-7.925a8.029 8.029 0 0 1-1.488-.328c-2.409-.781-3.949-2.643-4.336-4.866-.178-1.026-.11-2.13.231-3.241 1.227-3.973-.384-6.343-2.067-8.025-1.686-1.682-7.357-8.943-2.376-13.454 0 0-2.489-2.976 0-5.251-1.877 1.87-.259 4.62-.259 4.62l1.38-.961c5.861.078 3.5 6.673 3.5 6.673l.369.188s.638-.686 1.207-.686c.236 0 .462.118.625.451.552 1.136-.808 3.259-.808 3.259s.497 1.239 1.636 1.239c.502 0 1.129-.24 1.892-.936 1.882-1.707 2.845-3.868 2.567-6.854.072.398.24.894.619 1.269.364.366.861.549 1.471.549.055 0 .113-.003.17-.005.901-.048 2.186-.446 3.548-.869 1.769-.548 3.691-1.144 5.219-1.144.793 0 1.48.163 1.982.576.743.608 1.014 1.699.836 3.341l.205.02c.189-1.712-.11-2.866-.908-3.521-.544-.446-1.277-.619-2.115-.619-1.563 0-3.5.599-5.282 1.152-1.347.418-2.622.811-3.497.858a2.709 2.709 0 0 1-.156.005c-.554 0-1.001-.162-1.327-.485-.665-.664-.617-1.79-.615-1.8l-.208-.01c0 .01-.003.088.002.205a16.274 16.274 0 0 0-.634-2.743c-1.102-3.484-4.236-5.246-7.219-5.246-1.081 0-2.14.23-3.079.693 1.082-.586 2.321-.859 3.581-.859 3.683 0 7.542 2.328 8.169 5.975.359 2.08 1.33 2.751 2.467 2.751 1.535 0 3.375-1.219 4.431-1.835.8-.465 2.223-1.003 3.683-1.003 1.897 0 3.857.908 4.592 4.059.517 2.213-.136 5.037.825 7.094 1.323 2.831 5.633 2.1 7.987 3.181 3.058 1.41 4.657 4.133 3.066 7.309-2.452 4.891-7.279 8-4.675 12.255 1.83 2.989-2.802 8.59-6.97 8.59zm3.297-27.44-.028.205c.048.005 4.725.673 6.133 2.966.451.736.519 1.569.203 2.478-.66 1.902-1.894 3.487-2.983 4.886-1.516 1.942-2.823 3.619-2.339 5.604l.201-.05c-.459-1.887.818-3.529 2.298-5.429 1.102-1.409 2.346-3.008 3.019-4.943.338-.969.263-1.863-.224-2.654-1.46-2.375-6.084-3.036-6.28-3.063z" fill="#fff" />
2531 </mask>
2532 <g mask="url(#e)">
2533 <path d="M304.871 276.723c-1.77 0-3.455-1.006-4.526-3.647-3.601-8.866-9.194-7.367-13.717-7.925a8.002 8.002 0 0 1-1.488-.328c-2.409-.781-3.947-2.643-4.335-4.865-.179-1.027-.11-2.13.23-3.242 1.228-3.972-.382-6.343-2.066-8.024-1.688-1.683-7.358-8.944-2.376-13.454 0 0-2.49-2.976 0-5.252-1.879 1.87-.26 4.621-.26 4.621l1.38-.961c5.861.077 3.502 6.673 3.502 6.673l.368.188s.636-.686 1.206-.686c.238 0 .462.118.625.45.553 1.137-.808 3.259-.808 3.259s.498 1.239 1.638 1.239c.5 0 1.128-.24 1.889-.936 1.884-1.707 2.846-3.867 2.567-6.853.075.398.241.893.62 1.269.365.365.863.548 1.471.548.056 0 .114-.003.172-.005.899-.048 2.185-.445 3.546-.869 1.77-.548 3.693-1.144 5.22-1.144.793 0 1.479.163 1.983.576.741.609 1.012 1.7.835 3.342l.205.02c.188-1.712-.111-2.866-.907-3.522-.545-.446-1.278-.618-2.116-.618-1.563 0-3.5.598-5.281 1.151-1.35.418-2.622.811-3.499.859-.053.002-.102.005-.155.005-.556 0-1.001-.163-1.328-.486-.664-.663-.617-1.79-.614-1.8l-.207-.009c0 .009-.003.087 0 .205-.111-.849-.324-1.76-.634-2.744-1.101-3.484-4.235-5.246-7.219-5.246a6.967 6.967 0 0 0-3.079.694c1.081-.586 2.321-.859 3.582-.859 3.682 0 7.541 2.328 8.168 5.975.36 2.08 1.331 2.75 2.468 2.75 1.535 0 3.375-1.219 4.431-1.834.8-.466 2.221-1.004 3.682-1.004 1.898 0 3.856.909 4.592 4.06.517 2.213-.136 5.036.827 7.093 1.322 2.831 5.632 2.101 7.986 3.182 3.059 1.409 4.655 4.132 3.065 7.308-2.451 4.892-7.278 8-4.675 12.256 1.831 2.988-2.802 8.59-6.968 8.59zm3.294-27.441-.027.205c.049.005 4.724.674 6.132 2.967.454.735.52 1.569.205 2.478-.661 1.902-1.895 3.486-2.985 4.885-1.516 1.943-2.821 3.62-2.337 5.605l.199-.05c-.459-1.888.819-3.53 2.299-5.43 1.103-1.409 2.345-3.008 3.02-4.943.338-.969.263-1.862-.224-2.653-1.46-2.375-6.085-3.036-6.282-3.064z" fill="#242628" />
2534 </g>
2535 <mask id="f" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="261" y="282" width="18" height="42">
2536 <path d="M270.028 282.812s-3.402.526-7.229 3.079a1.904 1.904 0 0 0-.843 1.82c.459 3.619 2.344 15.338 8.556 26.785.326.603.422 1.306.276 1.977l-1.079 5.011-2.002 1.872h5.156l3.433-9.266-1.468-.463a2.772 2.772 0 0 1-1.94-2.596l-.173-9.939c.148-4.43-2.722-7.487-2.722-7.487l8.235-.398-8.2-10.395z" fill="#fff" />
2537 </mask>
2538 <g mask="url(#f)">
2539 <path d="M270.028 282.812s-3.402.526-7.229 3.079a1.898 1.898 0 0 0-.844 1.82c.46 3.619 2.343 15.338 8.556 26.785.326.603.422 1.306.275 1.977l-1.077 5.011-2.004 1.872h5.156l3.435-9.266-1.469-.463a2.774 2.774 0 0 1-1.941-2.596l-.171-9.939c.146-4.43-2.723-7.486-2.723-7.486l8.235-.399-8.199-10.395z" fill="url(#g)" />
2540 </g>
2541 <mask id="h" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="265" y="313" width="13" height="13">
2542 <path d="M265.694 324.26c-.469.613 5.053 1.422 7.844.083 0 0 3.869-6.716 3.869-10.173 0 0-.309-.703-2.58-.543 0 0-1.3 7.947-2.669 8.838-1.373.891-3.837.09-3.837.09s-1.934.801-2.627 1.705z" fill="#fff" />
2543 </mask>
2544 <g mask="url(#h)">
2545 <path d="M265.693 324.26c-.469.614 5.052 1.422 7.842.083 0 0 3.869-6.716 3.869-10.172 0 0-.307-.704-2.58-.543 0 0-1.299 7.946-2.669 8.837-1.372.892-3.836.091-3.836.091s-1.935.801-2.626 1.704z" fill="#242628" />
2546 </g>
2547 <mask id="i" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="242" y="284" width="36" height="41">
2548 <path d="M277.186 293.753c-.045-.078-8.864-1.059-8.864-1.059s.288 2.676-.615 6.358c-.901 3.684-13.853 19.641-13.853 19.641l1.358 4.295c-4.198 1.46-8.328 1.692-12.393.744l8.416-5.039s7.876-26.775 12.701-31.553l8.842-3.051 4.408 9.664z" fill="#fff" />
2549 </mask>
2550 <g mask="url(#i)">
2551 <path d="M277.185 293.754c-.046-.078-8.864-1.059-8.864-1.059s.287 2.675-.615 6.357c-.9 3.685-13.853 19.641-13.853 19.641l1.356 4.295c-4.197 1.46-8.327 1.693-12.391.744l8.415-5.039s7.876-26.774 12.701-31.553l8.842-3.051 4.409 9.665z" fill="url(#j)" />
2552 </g>
2553 <mask id="k" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="264" y="278" width="39" height="17">
2554 <path d="M296.968 278.172s5.083 7.541 5.5 10.98c.416 3.44-1.393 4.839-6.725 5.069-5.334.23-19.568.225-19.568.225l-12.152-10.357s8.855-3.151 11.158-4.043c2.301-.891 21.787-1.874 21.787-1.874z" fill="#fff" />
2555 </mask>
2556 <g mask="url(#k)">
2557 <path d="M296.969 278.172s5.082 7.541 5.5 10.98c.415 3.439-1.392 4.839-6.724 5.069-5.334.23-19.571.225-19.571.225l-12.149-10.357s8.854-3.152 11.156-4.043c2.302-.891 21.788-1.874 21.788-1.874z" fill="#001C37" />
2558 </g>
2559 <mask id="l" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="240" y="321" width="16" height="5">
2560 <path d="M254.7 321.542s1.46 1.411.474 2.983c0 0-11.582 2.048-14.726 1.119 0 0 .653-1.487 4.193-3.179l-.261.201c-.331.252-.136.781.278.758 2.334-.133 7.164-.561 10.042-1.882z" fill="#fff" />
2561 </mask>
2562 <g mask="url(#l)">
2563 <path d="M254.7 321.542s1.46 1.411.474 2.983c0 0-11.582 2.048-14.726 1.119 0 0 .653-1.487 4.193-3.179l-.261.201c-.331.252-.136.781.278.758 2.334-.133 7.164-.561 10.042-1.882z" fill="#242628" />
2564 </g>
2565 <mask id="m" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="272" y="249" width="31" height="29">
2566 <path d="M283.223 277.173s-5.916-5.622-8.829-9.344a7.887 7.887 0 0 1-1.343-7.111c1.33-4.47 3.694-9.098 3.91-9.819.331-1.107 6.967-1.059 6.967-1.059s13.058 8.455 18.189 22.775l-5.764 1.79s-3.409-5.707-6.777-9.334c0 0 5.829 7.682 6.035 10.825l-12.388 1.277z" fill="#fff" />
2567 </mask>
2568 <g mask="url(#m)">
2569 <path d="M283.222 277.173s-5.915-5.622-8.829-9.344a7.89 7.89 0 0 1-1.343-7.111c1.332-4.47 3.696-9.098 3.91-9.819.331-1.106 6.968-1.059 6.968-1.059s13.057 8.455 18.188 22.775l-5.762 1.79s-3.412-5.707-6.779-9.334c0 0 5.83 7.682 6.035 10.825l-12.388 1.277z" fill="#1FDE91" />
2570 </g>
2571 <mask id="n" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="276" y="243" width="8" height="8">
2572 <path d="M276.157 244.726c.07.228 1.802 5.957 1.937 6.075.136.12 3.799.315 5.523-.858l-2.926-6.11-4.534.893z" fill="#fff" />
2573 </mask>
2574 <g mask="url(#n)">
2575 <path d="M276.158 244.726c.069.228 1.8 5.957 1.936 6.075.136.12 3.8.315 5.523-.859l-2.927-6.11-4.532.894z" fill="url(#o)" />
2576 </g>
2577 <mask id="p" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="272" y="232" width="13" height="14">
2578 <path d="M279.003 232.694s-3.127 1.317-5.252 5.825c-2.128 4.508-.539 6.588 1.455 6.873 1.995.288 4.976.243 8.132-2.668 3.154-2.911-.271-11.592-4.335-10.03z" fill="#fff" />
2579 </mask>
2580 <g mask="url(#p)">
2581 <path d="M279.003 232.694s-3.128 1.317-5.253 5.825c-2.128 4.508-.539 6.588 1.455 6.873 1.997.288 4.976.243 8.133-2.668 3.154-2.911-.272-11.591-4.335-10.03z" fill="url(#q)" />
2582 </g>
2583 <mask id="r" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="275" y="228" width="14" height="17">
2584 <path d="M284.245 244.769c-1.14 0-1.636-1.239-1.636-1.239s1.36-2.123.808-3.259c-.164-.333-.389-.451-.625-.451-.57 0-1.207.686-1.207.686l-.369-.188s2.361-6.595-3.5-6.673l-1.38.961s-1.618-2.75.258-4.62a4.172 4.172 0 0 1 1.122-.781 6.96 6.96 0 0 1 3.079-.693c2.983 0 6.117 1.762 7.218 5.246.311.984.522 1.895.635 2.743.005.123.02.291.055.478.279 2.986-.685 5.147-2.567 6.854-.762.695-1.39.936-1.891.936z" fill="#fff" />
2585 </mask>
2586 <g mask="url(#r)">
2587 <path d="M284.246 244.768c-1.14 0-1.635-1.239-1.635-1.239s1.358-2.122.806-3.259c-.163-.332-.389-.45-.625-.45-.57 0-1.205.686-1.205.686l-.371-.188s2.362-6.596-3.499-6.673l-1.379.961s-1.62-2.751.257-4.621c.287-.285.653-.55 1.122-.781a6.956 6.956 0 0 1 3.079-.693c2.984 0 6.118 1.762 7.219 5.247.311.983.521 1.894.635 2.743.003.122.019.29.055.478.277 2.986-.684 5.146-2.566 6.853-.765.696-1.392.936-1.893.936z" fill="#242628" />
2588 </g>
2589 <mask id="s" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="280" y="239" width="4" height="5">
2590 <path d="M280.594 241.37s1.192-3.397 2.782-2.045c1.591 1.354-1.292 3.138-2.421 3.717l-.361-1.672z" fill="#fff" />
2591 </mask>
2592 <g mask="url(#s)">
2593 <path d="M280.593 241.37s1.191-3.397 2.782-2.045c1.591 1.354-1.294 3.139-2.421 3.717l-.361-1.672z" fill="url(#t)" />
2594 </g>
2595 <mask id="u" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="271" y="272" width="33" height="8">
2596 <path d="m271.167 277.591 4.348-4.18 6.015 2.536s5.989.923 9.414 0l3.427-.927 7.746-2.405 1.214 3.429c.309.871 0 1.837-.755 2.371-.67.475-1.455.998-1.957 1.216-1.019.446-25.139-.308-25.139-.308l-4.313-1.732z" fill="#fff" />
2597 </mask>
2598 <g mask="url(#u)">
2599 <path d="m271.167 277.591 4.348-4.18 6.016 2.535s5.987.924 9.412 0l3.428-.926 7.747-2.405 1.215 3.429c.308.871 0 1.837-.757 2.37-.67.476-1.456.999-1.958 1.217-1.016.445-25.138-.308-25.138-.308l-4.313-1.732z" fill="url(#v)" />
2600 </g>
2601 <mask id="w" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="288" y="236" width="15" height="5">
2602 <path d="m302.754 240.716-.206-.02c.178-1.642-.093-2.733-.835-3.341-.502-.413-1.19-.576-1.983-.576-1.528 0-3.45.596-5.219 1.144-1.362.423-2.647.821-3.547.869-.058.002-.116.005-.171.005-.61 0-1.107-.183-1.47-.549-.379-.375-.547-.871-.62-1.269-.015-.157-.035-.315-.055-.478-.005-.117-.003-.195-.003-.205l.208.01c-.002.01-.05 1.136.615 1.8.326.323.773.485 1.327.485.053 0 .103-.002.156-.005.876-.047 2.15-.44 3.498-.858 1.781-.553 3.718-1.152 5.281-1.152.838 0 1.571.173 2.116.619.797.655 1.096 1.809.908 3.521z" fill="#fff" />
2603 </mask>
2604 <g mask="url(#w)">
2605 <path d="m302.753 240.717-.206-.02c.177-1.642-.094-2.734-.836-3.342-.503-.413-1.189-.575-1.981-.575-1.53 0-3.45.595-5.22 1.143-1.364.424-2.647.821-3.549.869-.056.002-.115.005-.169.005-.61 0-1.106-.183-1.47-.548-.38-.376-.549-.871-.622-1.269-.013-.158-.034-.316-.053-.478-.005-.118-.003-.196-.003-.206l.209.01c-.003.01-.051 1.137.613 1.8.327.323.774.486 1.329.486.051 0 .102-.003.155-.005.876-.048 2.151-.441 3.498-.859 1.781-.553 3.717-1.151 5.282-1.151.838 0 1.569.172 2.113.618.798.656 1.098 1.81.91 3.522z" fill="#424242" />
2606 </g>
2607 <mask id="x" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="288" y="236" width="1" height="1">
2608 <path d="M288.703 236.979a3.421 3.421 0 0 1-.056-.478c.021.163.041.321.056.478z" fill="#fff" />
2609 </mask>
2610 <g mask="url(#x)">
2611 <path d="M288.701 236.98a3.415 3.415 0 0 1-.055-.478c.019.163.039.32.055.478z" fill="url(#y)" />
2612 </g>
2613 <mask id="z" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="308" y="249" width="7" height="17">
2614 <path d="M309.154 265.421c-.484-1.985.823-3.662 2.339-5.604 1.089-1.399 2.323-2.984 2.983-4.886.316-.909.248-1.742-.203-2.478-1.408-2.293-6.085-2.961-6.132-2.966l.027-.205c.196.027 4.82.688 6.28 3.063.487.791.563 1.685.224 2.654-.673 1.934-1.917 3.534-3.019 4.943-1.48 1.9-2.757 3.542-2.298 5.429l-.201.05z" fill="#fff" />
2615 </mask>
2616 <g mask="url(#z)">
2617 <path d="M309.153 265.422c-.484-1.985.823-3.662 2.337-5.605 1.09-1.399 2.326-2.983 2.984-4.885.316-.909.25-1.743-.201-2.479-1.409-2.292-6.086-2.961-6.132-2.966l.027-.205c.196.028 4.819.688 6.278 3.064.487.791.564 1.684.226 2.653-.674 1.935-1.919 3.534-3.019 4.943-1.481 1.9-2.758 3.542-2.299 5.43l-.201.05z" fill="#424242" />
2618 </g>
2619 <mask id="A" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="285" y="299" width="4" height="25">
2620 <path d="M288.083 324h-2.291v-24.71h2.291V324z" fill="#fff" />
2621 </mask>
2622 <g mask="url(#A)">
2623 <path d="M288.083 324h-2.29v-24.71h2.29V324z" fill="#242628" />
2624 </g>
2625 <mask id="B" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="276" y="322" width="21" height="3">
2626 <path d="M296.967 324.475h-20.06a1.49 1.49 0 0 1 1.491-1.487h17.079c.823 0 1.49.666 1.49 1.487z" fill="#fff" />
2627 </mask>
2628 <g mask="url(#B)">
2629 <path d="M296.966 324.475h-20.061a1.49 1.49 0 0 1 1.491-1.486h17.08c.822 0 1.49.665 1.49 1.486z" fill="#242628" />
2630 </g>
2631 <mask id="C" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="274" y="293" width="26" height="8">
2632 <path d="M299.947 298.431a2.468 2.468 0 0 1-2.464 2.458h-20.296a2.468 2.468 0 0 1-2.464-2.458v-2.222a2.467 2.467 0 0 1 2.464-2.456h20.296a2.467 2.467 0 0 1 2.464 2.456v2.222z" fill="#fff" />
2633 </mask>
2634 <g mask="url(#C)">
2635 <path d="M299.949 298.431a2.47 2.47 0 0 1-2.464 2.458h-20.296a2.467 2.467 0 0 1-2.463-2.458v-2.223a2.464 2.464 0 0 1 2.463-2.455h20.296a2.466 2.466 0 0 1 2.464 2.455v2.223z" fill="#242628" />
2636 </g>
2637 <mask id="D" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="261" y="277" width="28" height="3">
2638 <path d="M287.621 279.563h-.058l-25.224-.027a1.127 1.127 0 1 1 .003-2.253l25.224.028c.625 0 1.129.505 1.129 1.129a1.132 1.132 0 0 1-1.074 1.123z" fill="#fff" />
2639 </mask>
2640 <g mask="url(#D)">
2641 <path d="M287.624 279.563h-.06l-25.222-.028a1.126 1.126 0 0 1-1.127-1.126c0-.623.508-1.136 1.127-1.126l25.225.027c.624 0 1.13.506 1.13 1.129a1.132 1.132 0 0 1-1.073 1.124z" fill="#242628" />
2642 </g>
2643 <mask id="E" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="237" y="256" width="38" height="24">
2644 <path d="M239.665 256.716h21.006c1.551 0 2.961.893 3.623 2.29l9.71 20.542H247.9a2.431 2.431 0 0 1-2.263-1.534l-7.53-19.008a1.674 1.674 0 0 1 1.558-2.29z" fill="#fff" />
2645 </mask>
2646 <g mask="url(#E)">
2647 <path d="M239.665 256.716h21.006c1.551 0 2.961.893 3.623 2.29l9.71 20.542H247.9a2.431 2.431 0 0 1-2.263-1.534l-7.53-19.008a1.674 1.674 0 0 1 1.558-2.29z" fill="#242628" />
2648 </g>
2649 <mask id="F" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="215" y="279" width="108" height="6">
2650 <path d="M322.877 280.482a.938.938 0 0 0-.221-.806 1.07 1.07 0 0 0-.803-.353H216.599c-.409 0-.79.195-1.011.518a1.062 1.062 0 0 0-.078 1.071l1.142 2.311c.261.528.828.866 1.45.866h103.031c.579 0 1.076-.383 1.187-.916l.557-2.691z" fill="#fff" />
2651 </mask>
2652 <g mask="url(#F)">
2653 <path d="M322.876 280.482a.94.94 0 0 0-.222-.806 1.077 1.077 0 0 0-.804-.353H216.596c-.409 0-.79.196-1.008.519-.219.32-.25.726-.079 1.071l1.14 2.31c.261.528.83.866 1.452.866H321.13c.58 0 1.076-.383 1.188-.916l.558-2.691z" fill="#1FDE91" />
2654 </g>
2655 <mask id="G" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="221" y="281" width="22" height="49">
2656 <path d="M222.498 329.136a.883.883 0 0 1-.364-.078.95.95 0 0 1-.484-1.239l19.373-45.963a.915.915 0 0 1 1.212-.495c.469.207.685.76.484 1.239l-19.373 45.965a.92.92 0 0 1-.848.571z" fill="#fff" />
2657 </mask>
2658 <g mask="url(#G)">
2659 <path d="M222.498 329.136a.883.883 0 0 1-.364-.078.95.95 0 0 1-.484-1.239l19.373-45.963a.915.915 0 0 1 1.212-.495c.469.207.685.76.484 1.239l-19.373 45.965a.92.92 0 0 1-.848.571z" fill="#1FDE91" />
2660 </g>
2661 <mask id="H" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="224" y="281" width="22" height="49">
2662 <path d="M244.322 329.136a.922.922 0 0 1-.85-.571l-19.37-45.965a.95.95 0 0 1 .481-1.239.918.918 0 0 1 1.215.495l19.372 45.963a.95.95 0 0 1-.484 1.239.89.89 0 0 1-.364.078z" fill="#fff" />
2663 </mask>
2664 <g mask="url(#H)">
2665 <path d="M244.322 329.136a.922.922 0 0 1-.85-.571l-19.37-45.965a.95.95 0 0 1 .481-1.239.918.918 0 0 1 1.215.495l19.372 45.963a.95.95 0 0 1-.484 1.239.89.89 0 0 1-.364.078z" fill="#1FDE91" />
2666 </g>
2667 <mask id="I" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="297" y="281" width="23" height="49">
2668 <path d="M298.785 329.136a.907.907 0 0 1-.367-.078.95.95 0 0 1-.482-1.239l19.371-45.963a.918.918 0 0 1 1.214-.495c.469.207.685.76.484 1.239l-19.372 45.965a.924.924 0 0 1-.848.571z" fill="#fff" />
2669 </mask>
2670 <g mask="url(#I)">
2671 <path d="M298.785 329.136a.914.914 0 0 1-.367-.078.95.95 0 0 1-.481-1.239l19.37-45.963a.918.918 0 0 1 1.214-.495c.469.207.686.76.486 1.238l-19.375 45.966a.922.922 0 0 1-.847.571z" fill="#1FDE91" />
2672 </g>
2673 <mask id="J" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="300" y="281" width="22" height="49">
2674 <path d="M320.606 329.136a.924.924 0 0 1-.848-.571L300.386 282.6a.951.951 0 0 1 .484-1.239.917.917 0 0 1 1.214.495l19.37 45.963a.949.949 0 0 1-.481 1.239.907.907 0 0 1-.367.078z" fill="#fff" />
2675 </mask>
2676 <g mask="url(#J)">
2677 <path d="M320.607 329.136a.923.923 0 0 1-.849-.571L300.385 282.6a.951.951 0 0 1 .483-1.239.918.918 0 0 1 1.215.495l19.37 45.963a.948.948 0 0 1-.48 1.239.9.9 0 0 1-.366.078z" fill="#1FDE91" />
2678 </g>
2679 <mask id="K" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="37" y="31" width="272" height="201">
2680 <path d="m37.887 148.035 139.064 83.741 131.13-78.792V31H48.806l-10.92 117.035z" fill="#fff" />
2681 </mask>
2682 <g mask="url(#K)">
2683 <path d="M270.976 66.156H58.964c-3.739 0-6.835 3.235-6.835 6.966v171.639h249.263V96.978l-30.416-30.822z" fill="#fff" />
2684 <path d="m301.274 96.979-27.82.24c-.742.007-1.316-.283-1.841-.8-.524-.519-.577-1.224-.577-1.959V66.37l30.238 30.609z" fill="#81909C" />
2685 </g>
2686 <path d="M335.988 197.782v21.17c0 1.725-1.055 3.124-2.355 3.124h-77.309c-1.078 0-2.02.976-2.282 2.365l-1.389 7.372c-.182.976-1.208 1.059-1.479.115l-1.786-6.225c-.349-1.211-1.225-2.013-2.199-2.013h-3.323c-1.3 0-2.354-1.399-2.354-3.123v-21.762c0-1.715 1.043-3.11 2.334-3.124l89.767-1.022c1.311-.015 2.375 1.387 2.375 3.123z" fill="#fff" />
2687 <path d="M243.993 208.501c0 5.36 4.246 9.708 9.482 9.708 5.237 0 9.482-4.348 9.482-9.708 0-5.362-4.245-9.709-9.482-9.709-5.236 0-9.482 4.347-9.482 9.709z" fill="#1FDE91" />
2688 <mask id="L" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="250" y="201" width="7" height="8">
2689 <path d="M256.911 204.565c0 1.744-1.418 3.627-3.164 3.627-1.749 0-3.167-1.883-3.167-3.627 0-1.744 1.418-2.981 3.167-2.981 1.746 0 3.164 1.237 3.164 2.981z" fill="#fff" />
2690 </mask>
2691 <g mask="url(#L)">
2692 <path d="M256.911 204.565c0 1.744-1.418 3.627-3.164 3.627-1.749 0-3.167-1.883-3.167-3.627 0-1.744 1.418-2.981 3.167-2.981 1.746 0 3.164 1.237 3.164 2.981z" fill="#242628" />
2693 </g>
2694 <mask id="M" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="247" y="209" width="13" height="6">
2695 <path d="M249.817 214.908h7.728c.813 0 1.533-.526 1.782-1.299l.414-1.297a1.86 1.86 0 0 0-1.127-2.318c-3.146-1.148-6.398-1.001-9.728.105a1.859 1.859 0 0 0-1.194 2.253l.321 1.176c.221.814.959 1.38 1.804 1.38z" fill="#fff" />
2696 </mask>
2697 <g mask="url(#M)">
2698 <path d="M249.817 214.908h7.728c.813 0 1.533-.526 1.782-1.299l.414-1.297a1.86 1.86 0 0 0-1.127-2.318c-3.146-1.148-6.398-1.001-9.728.105a1.859 1.859 0 0 0-1.194 2.253l.321 1.176c.221.814.959 1.38 1.804 1.38z" fill="#242628" />
2699 </g>
2700 <mask id="N" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="268" y="209" width="61" height="4">
2701 <path d="M328.603 211.033c0 1.014-.858 1.837-1.917 1.837h-56.427c-1.059 0-1.917-.823-1.917-1.837s.858-1.834 1.917-1.834h56.427c1.059 0 1.917.82 1.917 1.834z" fill="#fff" />
2702 </mask>
2703 <g mask="url(#N)">
2704 <path d="M328.601 211.033c0 1.014-.859 1.837-1.917 1.837h-56.426c-1.061 0-1.917-.823-1.917-1.837 0-1.013.856-1.833 1.917-1.833h56.426c1.058 0 1.917.82 1.917 1.833z" fill="#242628" />
2705 </g>
2706 <mask id="O" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="268" y="215" width="34" height="4">
2707 <path d="M301.329 216.787c0 .922-.748 1.667-1.671 1.667h-29.583a1.668 1.668 0 1 1 0-3.336h29.583a1.67 1.67 0 0 1 1.671 1.669z" fill="#fff" />
2708 </mask>
2709 <g mask="url(#O)">
2710 <path d="M301.328 216.788c0 .921-.748 1.667-1.67 1.667h-29.582a1.667 1.667 0 1 1 0-3.337h29.582c.922 0 1.67.746 1.67 1.67z" fill="#242628" />
2711 </g>
2712 <mask id="P" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="303" y="215" width="26" height="4">
2713 <path d="M328.665 216.787a1.67 1.67 0 0 1-1.673 1.667h-21.35a1.667 1.667 0 1 1 0-3.336h21.35a1.67 1.67 0 0 1 1.673 1.669z" fill="#fff" />
2714 </mask>
2715 <g mask="url(#P)">
2716 <path d="M328.666 216.787a1.67 1.67 0 0 1-1.674 1.667h-21.349a1.667 1.667 0 1 1 0-3.336h21.349a1.67 1.67 0 0 1 1.674 1.669z" fill="#242628" />
2717 </g>
2718 <path d="M301.368 202.131a2.345 2.345 0 0 1-2.347 2.342h-28.487a2.345 2.345 0 0 1-2.349-2.342v-.191a2.345 2.345 0 0 1 2.349-2.342h28.487a2.346 2.346 0 0 1 2.347 2.342v.191zM82.198 81.84h9.38v.938h-9.38zM82.198 90.282h9.38v.938h-9.38zM82.198 82.778h.938v.938h-.938zM82.198 89.344h.938v.938h-.938zM90.64 89.344h.938v.938h-.938z" fill="#1FDE91" />
2719 <path fill="#1FDE91" d="M81.26 82.778h.938v7.504h-.938zM91.578 82.778h.938v7.504h-.938zM83.136 83.716h.938v.938h-.938zM89.702 83.716h.938v.938h-.938zM84.074 84.654h.938v.938h-.938zM88.764 84.654h.938v.938h-.938zM85.012 85.592h.938v.938h-.938zM87.826 85.592h.938v.938h-.938z" />
2720 <path fill="#1FDE91" d="M85.95 86.529h1.876v.938H85.95zM90.64 82.778h.938v.938h-.938z" />
2721 <rect x="81.26" y="151.468" width="182.938" height="14.146" rx="1.286" fill="#fff" />
2722 <rect x="86.26" y="157.058" width="33" height="2" rx="1" fill="#8B8D8F" />
2723 <rect x="80.869" y="151.077" width="183.72" height="14.928" rx="1.677" stroke="#242628" stroke-opacity=".5" stroke-width=".782" />
2724 <rect x="81.26" y="127.644" width="182.938" height="14.146" rx="1.286" fill="#fff" />
2725 <rect x="86.26" y="134.058" width="33" height="2" rx="1" fill="#8B8D8F" />
2726 <rect x="80.869" y="127.253" width="183.72" height="14.928" rx="1.677" stroke="#242628" stroke-opacity=".5" stroke-width=".782" />
2727 <rect x="81.26" y="103.726" width="182.938" height="14.146" rx="1.286" fill="#fff" />
2728 <rect x="86.26" y="110.058" width="33" height="2" rx="1" fill="#8B8D8F" />
2729 <rect x="80.869" y="103.335" width="183.72" height="14.928" rx="1.677" stroke="#242628" stroke-opacity=".5" stroke-width=".782" />
2730 <path d="M104.812 83.524c-1.74 0-2.736 1.26-2.736 3.156 0 1.956 1.128 3.024 2.748 3.024 1.524 0 2.448-.84 2.448-2.244v-.036h-2.652v-1.356h4.08V91h-1.296l-.096-1.008c-.48.672-1.476 1.14-2.664 1.14-2.46 0-4.176-1.788-4.176-4.488 0-2.664 1.74-4.56 4.38-4.56 2.004 0 3.552 1.164 3.804 2.952h-1.62c-.276-1.032-1.164-1.512-2.22-1.512zm7.983 7.632c-1.764 0-3-1.284-3-3.12 0-1.86 1.212-3.144 2.952-3.144 1.776 0 2.904 1.188 2.904 3.036v.444l-4.464.012c.108 1.044.66 1.572 1.632 1.572.804 0 1.332-.312 1.5-.876h1.356c-.252 1.296-1.332 2.076-2.88 2.076zm-.036-5.064c-.864 0-1.392.468-1.536 1.356h2.976c0-.816-.564-1.356-1.44-1.356zM118.78 91h-1.464v-4.704h-1.14v-1.224h1.14v-1.848h1.464v1.848h1.152v1.224h-1.152V91zm6.028-7.116a.889.889 0 0 1-.9-.888c0-.492.396-.876.9-.876.48 0 .876.384.876.876a.884.884 0 0 1-.876.888zM124.076 91v-5.928h1.464V91h-1.464zm4.441 0h-1.464v-5.928h1.356l.12.768c.372-.6 1.092-.948 1.896-.948 1.488 0 2.256.924 2.256 2.46V91h-1.464v-3.3c0-.996-.492-1.476-1.248-1.476-.9 0-1.452.624-1.452 1.584V91zm10.853 0h-1.464v-4.704h-1.14v-1.224h1.14v-1.848h1.464v1.848h1.152v1.224h-1.152V91zm1.655-2.976c0-1.848 1.332-3.12 3.168-3.12s3.168 1.272 3.168 3.12-1.332 3.12-3.168 3.12-3.168-1.272-3.168-3.12zm1.464 0c0 1.08.696 1.812 1.704 1.812s1.704-.732 1.704-1.812-.696-1.812-1.704-1.812-1.704.732-1.704 1.812zm9.912-2.952h1.464V91h-1.356l-.108-.792c-.36.564-1.128.948-1.92.948-1.368 0-2.172-.924-2.172-2.376v-3.708h1.464v3.192c0 1.128.444 1.584 1.26 1.584.924 0 1.368-.54 1.368-1.668v-3.108zm2.511 2.952c0-1.836 1.212-3.132 2.964-3.132 1.62 0 2.724.9 2.88 2.328h-1.464c-.168-.672-.66-1.02-1.356-1.02-.936 0-1.56.708-1.56 1.824s.576 1.812 1.512 1.812c.732 0 1.248-.36 1.404-1.008h1.476c-.18 1.38-1.332 2.328-2.88 2.328-1.8 0-2.976-1.248-2.976-3.132zm8.41 2.976h-1.464v-8.928h1.476v3.768c.372-.576 1.068-.948 1.92-.948 1.464 0 2.232.924 2.232 2.46V91h-1.464v-3.3c0-.996-.492-1.476-1.236-1.476-.924 0-1.464.648-1.464 1.536V91z" fill="#242628" />
2731 <path fill="#1FDE91" d="M158.26 148.058h2.736v54.717h-2.736z" />
2732 <path fill="#1FDE91" d="M160.995 150.794h2.736v2.736h-2.736zM171.939 161.737h2.736v2.736h-2.736zM182.883 172.681h2.736v2.736h-2.736zM166.468 156.265h2.736v2.736h-2.736zM177.411 167.209h2.736v2.736h-2.736zM188.354 178.152h2.736v2.736h-2.736zM169.203 189.096h2.736v2.736h-2.736z" />
2733 <path fill="#1FDE91" d="M166.468 191.831h2.736v2.736h-2.736zM163.731 194.567h2.736v2.736h-2.736zM160.995 197.303h2.736v2.736h-2.736zM180.146 183.624h2.736v8.208h-2.736zM182.883 191.831h2.736v5.472h-2.736zM174.675 191.831h2.736v5.472h-2.736zM171.939 186.36h2.736v5.472h-2.736zM185.618 197.303h2.736v5.472h-2.736zM177.411 197.303h2.736v5.472h-2.736zM188.354 202.775h2.736v5.472h-2.736zM180.146 202.775h2.736v5.472h-2.736zM163.731 153.53h2.736v2.736h-2.736zM174.675 164.473h2.736v2.736h-2.736zM185.618 175.416h2.736v2.736h-2.736zM169.203 159.001h2.736v2.736h-2.736zM180.146 169.945h2.736v2.736h-2.736zM180.146 180.888h13.679v2.736h-13.679zM182.883 208.247h5.472v2.736h-5.472z" />
2734 <defs>
2735 <linearGradient id="g" x1="279.23" y1="322.708" x2="271.012" y2="285.385" gradientUnits="userSpaceOnUse">
2736 <stop stop-color="#FF928E" />
2737 <stop offset="1" stop-color="#FEB3B1" />
2738 </linearGradient>
2739 <linearGradient id="j" x1="292.659" y1="233.667" x2="273.411" y2="280.645" gradientUnits="userSpaceOnUse">
2740 <stop stop-color="#FF928E" />
2741 <stop offset="1" stop-color="#FEB3B1" />
2742 </linearGradient>
2743 <linearGradient id="o" x1="285.033" y1="237.355" x2="293.362" y2="257.803" gradientUnits="userSpaceOnUse">
2744 <stop stop-color="#FF928E" />
2745 <stop offset="1" stop-color="#FEB3B1" />
2746 </linearGradient>
2747 <linearGradient id="q" x1="-716.313" y1="340.056" x2="-703.14" y2="338.684" gradientUnits="userSpaceOnUse">
2748 <stop stop-color="#FF928E" />
2749 <stop offset="1" stop-color="#FEB3B1" />
2750 </linearGradient>
2751 <linearGradient id="t" x1="-715.046" y1="342.488" x2="-701.896" y2="341.119" gradientUnits="userSpaceOnUse">
2752 <stop stop-color="#FF928E" />
2753 <stop offset="1" stop-color="#FEB3B1" />
2754 </linearGradient>
2755 <linearGradient id="v" x1="352.794" y1="272.43" x2="310.594" y2="268.666" gradientUnits="userSpaceOnUse">
2756 <stop stop-color="#FF928E" />
2757 <stop offset="1" stop-color="#FEB3B1" />
2758 </linearGradient>
2759 <linearGradient id="y" x1="222.273" y1="156.367" x2="354.285" y2="289.941" gradientUnits="userSpaceOnUse">
2760 <stop stop-color="#B05B34" />
2761 <stop offset="1" stop-color="#3225BE" />
2762 </linearGradient>
2763 </defs>
2764 </svg>
2765 </div>
2766 </div>
2767 <?php
2768 }
2769 }
2770 }
2771
2772 /**
2773 * Page footer used on all admin pages.
2774 *
2775 * @access public
2776 * @return void
2777 * @since 1.0.0
2778 */
2779 public function admin_page_footer() {
2780 $current_page = get_current_screen()->id;
2781
2782 if ( 'edit-wpzf-form' == $current_page || 'wpzf-form' == $current_page || 'edit-wpzf-submission' == $current_page || 'wpzf-submission' == $current_page || 'wpzf-form_page_wpzf-settings' == $current_page || 'wpzf-form_page_wpzoom-forms-pro-license' == $current_page || 'wpzf-form_page_wpzf-pro-page' == $current_page ) {
2783 ?>
2784 <footer class="wpzoom-new-admin_settings-footer">
2785 <div class="wpzoom-new-admin_settings-footer-wrap">
2786 <h3 class="wpzoom-new-admin_settings-footer-logo">
2787 <a href="https://www.wpzoom.com/<?php echo $this->utm_source; ?>" target="_blank" title="<?php esc_html_e( 'WPZOOM - WordPress themes with modern features and professional support', 'wpzoom-forms' ); ?>">
2788 <?php _e( 'WPZOOM', 'wpzoom-forms' ); ?>
2789 </a>
2790 </h3>
2791
2792 <ul class="wpzoom-new-admin_settings-footer-links">
2793 <li class="wpzoom-new-admin_settings-footer-links-themes">
2794 <a href="https://www.wpzoom.com/themes/<?php echo $this->utm_source; ?>" target="_blank" title="<?php _e( 'Check out our themes', 'wpzoom-forms' ); ?>">
2795 <?php _e( 'Our Themes', 'wpzoom-forms' ); ?>
2796 </a>
2797 </li>
2798
2799 <li class="wpzoom-new-admin_settings-footer-links-themes">
2800 <a href="https://www.wpzoom.com/plugins/<?php echo $this->utm_source; ?>" target="_blank" title="<?php _e( 'Check out our plugins', 'wpzoom-forms' ); ?>">
2801 <?php _e( 'Our Plugins', 'wpzoom-forms' ); ?>
2802 </a>
2803 </li>
2804
2805 <li class="wpzoom-new-admin_settings-footer-links-blog">
2806 <a href="https://www.wpzoom.com/blog/" target="_blank" title="<?php _e( 'See the latest updates on our blog', 'wpzoom-forms' ); ?>">
2807 <?php _e( 'Blog', 'wpzoom-forms' ); ?>
2808 </a>
2809 </li>
2810
2811 <li class="wpzoom-new-admin_settings-footer-links-themes">
2812 <a href="https://www.wpzoom.com/documentation/wpzoom-forms/" target="_blank" title="<?php _e( 'Documentation', 'wpzoom-forms' ); ?>">
2813 <?php _e( 'Documentation', 'wpzoom-forms' ); ?>
2814 </a>
2815 </li>
2816
2817 <li class="wpzoom-new-admin_settings-footer-links-support">
2818 <a href="https://www.wpzoom.com/support/" target="_blank" title="<?php _e( 'Get support', 'wpzoom-forms' ); ?>">
2819 <?php _e( 'Support', 'wpzoom-forms' ); ?>
2820 </a>
2821 </li>
2822 </ul>
2823 </div>
2824 </footer>
2825 <?php
2826 }
2827 }
2828
2829 public function admin_body_class_filter( $classes ) {
2830 $current_page = get_current_screen()->id;
2831
2832 if ( 'edit-wpzf-form' == $current_page || 'wpzf-form' == $current_page || 'edit-wpzf-submission' == $current_page || 'wpzf-submission' == $current_page || 'wpzf-form_page_wpzf-settings' == $current_page || 'wpzf-form_page_wpzoom-forms-pro-license' == $current_page || 'wpzf-form_page_wpzf-pro-page' == $current_page || 'wpzf-form_page_wpzf-import' == $current_page ) {
2833 $classes .= ' wpzoom-new-admin';
2834 }
2835
2836 $forms_count = wp_count_posts( 'wpzf-form' );
2837
2838 if ( 'edit-wpzf-form' == $current_page && $forms_count->publish < 1 && $forms_count->draft < 1 && $forms_count->trash < 1 ) {
2839 $classes .= ' wpzf-no-forms';
2840 }
2841
2842 return $classes;
2843 }
2844
2845 /**
2846 * Returns whether the given input is considered spam by checking it with Akismet.
2847 *
2848 * @since 1.0.4
2849 * @access public
2850 * @param array $input The input to check for spam.
2851 * @return bool Whether it is spam.
2852 */
2853 public function not_spam( $input ) {
2854
2855 // Check if Akismet class exists and has required methods
2856 if( class_exists( 'Akismet' ) && is_callable( array( 'Akismet', 'get_api_key' ) ) && is_callable( array( 'Akismet', 'http_post' ) ) ) {
2857
2858 $request = array(
2859 'comment_type' => 'contact-form',
2860 'comment_author' => isset( $input['name'] ) ? $input['name'] : '',
2861 'comment_author_email' => isset( $input['from'] ) ? $input['from'] : '',
2862 'comment_author_url' => isset( $input['url'] ) ? $input['url'] : '',
2863 'comment_content' => isset( $input['message'] ) ? $input['message'] : '',
2864 'blog' => get_option( 'home' ),
2865 'user_ip' => $this->get_remote_address(),
2866 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2867 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) : null,
2868 'referrer' => wp_get_referer() ? wp_get_referer() : null,
2869 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2870 'permalink' => get_permalink(),
2871 'blog_lang' => get_locale(),
2872 'blog_charset' => get_bloginfo( 'charset' ),
2873 'user_role' => Akismet::get_user_roles( get_current_user_id() ),
2874 'is_test' => false,
2875
2876 );
2877
2878 $response = Akismet::http_post( build_query( $request ), 'comment-check' );
2879
2880 if( ! empty( $response ) && isset( $response[1] ) && 'true' === trim( $response[1] ) ) {
2881 return false;
2882 }
2883
2884 }
2885
2886 return true;
2887 }
2888
2889 /**
2890 * Returns the remote address of the connected peer.
2891 *
2892 * @since 1.0.4
2893 * @access public
2894 * @return string The remote address, or empty string on failure.
2895 */
2896 public function get_remote_address() {
2897 $server_variable_keys = array(
2898 'HTTP_CLIENT_IP',
2899 'HTTP_X_FORWARDED_FOR',
2900 'HTTP_X_FORWARDED',
2901 'HTTP_X_CLUSTER_CLIENT_IP',
2902 'HTTP_FORWARDED_FOR',
2903 'HTTP_FORWARDED',
2904 'REMOTE_ADDR'
2905 );
2906
2907 foreach ( $server_variable_keys as $key ) {
2908 if ( array_key_exists( $key, $_SERVER ) === true ) {
2909 foreach ( array_map( 'trim', explode( ',', $_SERVER[ $key ] ) ) as $ip ) {
2910 if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
2911 return $ip;
2912 }
2913 }
2914 }
2915 }
2916
2917 return '';
2918 }
2919
2920 /**
2921 * Callback that is triggered when a form is submitted on the frontend.
2922 *
2923 * @access public
2924 * @return void
2925 * @since 1.0.0
2926 */
2927 public function action_form_post() {
2928 $success = false;
2929 $url = isset( $_POST['_wp_http_referer'] ) ? sanitize_text_field( wp_unslash( $_POST['_wp_http_referer'] ) ) : home_url();
2930 $form_id = -1;
2931 $captcha_check_passed = true;
2932
2933 if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'wpzf_submit' ) ) {
2934 $form_id = isset( $_POST['form_id'] ) ? intval( $_POST['form_id'] ) : -1;
2935 $blocks = parse_blocks( $form_id > -1 ? get_post_field( 'post_content', $form_id, 'raw' ) : '' );
2936
2937 // Check if spam protection is fully configured and validate only then.
2938 $captcha_config = $this->get_spam_protection_config();
2939 if ( 'recaptcha' === $captcha_config['active_service'] ) {
2940 $captcha_check_passed = false;
2941 $captcha = false;
2942 $recaptchaType = $captcha_config['type'];
2943
2944 if ( isset( $_POST['g-recaptcha-response'] ) ) {
2945 $captcha = trim( sanitize_text_field( $_POST['g-recaptcha-response'] ) );
2946 }
2947
2948 if( 'v3' == $recaptchaType && isset( $_POST['recaptcha_token'] ) ) {
2949 $captcha = trim( sanitize_text_field( $_POST['recaptcha_token'] ) );
2950 }
2951
2952
2953 if ( ! empty( $captcha ) ) {
2954 $secret = false;
2955 if ( 'v3' === $recaptchaType && ! empty( $captcha_config['recaptcha_v3_secret_key'] ) ) {
2956 $secret = trim( $captcha_config['recaptcha_v3_secret_key'] );
2957 } else {
2958 $secret = trim( $captcha_config['recaptcha_v2_secret_key'] );
2959 }
2960
2961 if ( ! empty( $secret ) ) {
2962 $response = file_get_contents(
2963 sprintf(
2964 'https://www.google.com/recaptcha/api/siteverify?secret=%1$s&response=%2$s&remoteip=%3$s',
2965 $secret,
2966 $captcha,
2967 $_SERVER['REMOTE_ADDR']
2968 )
2969 );
2970
2971 if ( false !== $response && ! empty( $response ) ) {
2972 $json = json_decode( $response );
2973
2974 if( 'v3' == $recaptchaType ) {
2975 if ( null !== $json && is_object( $json ) && true === $json->success && $json->score >= 0.5 ) {
2976 $captcha_check_passed = true;
2977 }
2978 }
2979 else {
2980 if ( null !== $json && is_object( $json ) && true === $json->success ) {
2981 $captcha_check_passed = true;
2982 }
2983 }
2984
2985 }
2986 }
2987 }
2988 } elseif ( 'turnstile' === $captcha_config['active_service'] ) {
2989 $captcha_check_passed = false;
2990
2991 $captcha = isset( $_POST['cf-turnstile-response'] ) ? sanitize_text_field( wp_unslash( $_POST['cf-turnstile-response'] ) ) : '';
2992 if ( ! empty( $captcha ) ) {
2993 $secret = trim( $captcha_config['turnstile_secret_key'] );
2994 $ip = $_SERVER['REMOTE_ADDR'];
2995
2996 $url_path = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
2997 $data = array(
2998 'secret' => $secret,
2999 'response' => $captcha,
3000 'remoteip' => $ip
3001 );
3002
3003 $options = array(
3004 'http' => array(
3005 'method' => 'POST',
3006 'content' => http_build_query($data)
3007 )
3008 );
3009 $stream = stream_context_create($options);
3010 $result = file_get_contents($url_path, false, $stream);
3011 $result = json_decode($result, true);
3012
3013 if ( intval( $result['success'] ) !== 1 ) {
3014 $captcha_check_passed = false;
3015 } else {
3016 $captcha_check_passed = true;
3017 }
3018 }
3019 }
3020
3021 if ( $captcha_check_passed && count( $blocks ) > 0 ) {
3022 $clean_site_name = sanitize_text_field( get_bloginfo( 'name' ) );
3023 $input_blocks = $this->get_input_blocks( $blocks );
3024 $form_method = get_post_meta( $form_id, '_form_method', true ) ?: 'email';
3025 $form_email = get_post_meta( $form_id, '_form_email', true );
3026 $form_subject = get_post_meta( $form_id, '_form_subject', true );
3027 $fallback_email = trim( get_option( 'admin_email' ) );
3028 $sendto = sanitize_email( false !== $form_email && ! empty( $form_email ) && filter_var( $form_email, FILTER_VALIDATE_EMAIL ) ? $form_email : $fallback_email );
3029
3030 if ( 'email' == $form_method || 'combined' == $form_method ) {
3031 $email_body = '<html>
3032 <head>
3033 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
3034 <meta http-equiv="X-UA-Compatible" content="IE=edge">
3035 <meta name="viewport" content="width=device-width">
3036
3037 <style type="text/css">
3038 body {
3039 -ms-text-size-adjust: 100%;height: 100%; line-height: 1.6;
3040 font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
3041 }
3042 a { color: #4477bd; }
3043 a:hover {
3044 color: #e2911a !important;
3045 }
3046 a:active {
3047 color: #0d3d62 !important;
3048 }
3049 p{
3050 margin:10px 0;
3051 padding:0;
3052 font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
3053 }
3054 table{
3055 border-collapse:collapse;
3056 }
3057 h1,h2,h3,h4,h5,h6{
3058 display:block;
3059 margin:0;
3060 padding:0;
3061 }
3062 img,a img{
3063 border:0;
3064 height:auto;
3065 outline:none;
3066 text-decoration:none;
3067 }
3068 body,#bodyTable,#bodyCell{
3069 height:100%;
3070 margin:0;
3071 padding:0;
3072 }
3073 #outlook a{
3074 padding:0;
3075 }
3076 img{
3077 -ms-interpolation-mode:bicubic;
3078 }
3079 table{
3080 mso-table-lspace:0pt;
3081 mso-table-rspace:0pt;
3082 }
3083 p,a,li,td,blockquote{
3084 mso-line-height-rule:exactly;
3085 }
3086 a[href^=tel],a[href^=sms]{
3087 color:inherit;
3088 cursor:default;
3089 text-decoration:none;
3090 }
3091 p,a,li,td,body,table,blockquote{
3092 -ms-text-size-adjust:100%;
3093 -webkit-text-size-adjust:100%;
3094 }
3095 a[x-apple-data-detectors]{
3096 color:inherit !important;
3097 text-decoration:none !important;
3098 font-size:inherit !important;
3099 font-family:inherit !important;
3100 font-weight:inherit !important;
3101 line-height:inherit !important;
3102 }
3103 @media only screen and (max-width: 480px){
3104 body,table,td,p,a,li,blockquote{
3105 -webkit-text-size-adjust:none !important;
3106 }
3107 }
3108 @media only screen and (max-width: 480px){
3109 body{
3110 width:100% !important;
3111 min-width:100% !important;
3112 }
3113 }
3114 </style>
3115 </head>
3116 <body style="height: 100%;margin: 0;padding: 0;width: 100%;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
3117 <div style="font-family:-apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;box-sizing:border-box;font-size:14px;line-height:1.5;max-width:600px;overflow:visible;display:block;margin:0">
3118 <table width="100%" cellpadding="0" cellspacing="0" style="font-family:-apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;box-sizing:border-box;font-size:14px;line-height:1.5">
3119 <tbody>
3120 <tr style="font-family:-apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;box-sizing:border-box;font-size:14px;line-height:1.5">
3121 <td style="font-family:-apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;box-sizing:border-box;font-size:14px;line-height:1.5;vertical-align:top;color:#222222;padding:25px" valign="top">';
3122 $replyto = '';
3123 $sbj = '';
3124 $raw_content = array(
3125 '_wpzf_fields' => array()
3126 );
3127
3128 foreach ( $_REQUEST as $key => $value ) {
3129 if ( strpos( $key, 'wpzf_' ) === 0 ) {
3130 $id = substr( $key, 5 );
3131 $name = isset( $input_blocks[ $id ] ) ? $input_blocks[ $id ] : __( 'Unnamed Input', 'wpzoom-forms' );
3132 $value = is_array( $value ) ? implode( ', ', $value ) : $value;
3133
3134 if ( 'wpzf_replyto' == $key ) {
3135 $replyto = sanitize_text_field( $value );
3136 continue;
3137 } elseif ( 'wpzf_subject' == $key ) {
3138 $sbj = sanitize_text_field( $value );
3139 continue;
3140 }
3141
3142 $email_body .= '<strong>' . wp_kses_post( wp_unslash( $name ) ) . ':</strong><br/>' . nl2br( wp_kses_post( wp_unslash( $value ) ) ) . '<br/><br/>';
3143 $raw_content['_wpzf_fields'][ $name ] = sanitize_textarea_field( $value );
3144 }
3145 }
3146
3147 $fromaddr = ! empty( $replyto ) && isset( $_REQUEST[ $replyto ] ) ? sanitize_email( $_REQUEST[ $replyto ] ) : $sendto;
3148 $cleanname = sanitize_text_field( get_bloginfo( 'name' ) );
3149 $subjectline = ! empty( $sbj ) && isset( $_REQUEST[ $sbj ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ $sbj ] ) ) : $form_subject;
3150 $subjectline .= sprintf( __( ' - %s', 'wpzoom-forms' ), $cleanname );
3151
3152 $email_body = '<html style="background-color:#f4f3f3;"><body style="background-color:#f4f3f3;padding:2em;"><div style="background-color:#ffffff;width:70%;padding:2em;border-radius:10px;">' . preg_replace( '/<br\/><br\/><hr\/><br\/>$/is', '', $email_body ) . '</div></body></html>';
3153
3154 $headers = sprintf(
3155 "Content-Type: text/html; charset=UTF-8\r\nFrom: %s <%s>\r\nReply-To: %s",
3156 $cleanname,
3157 $fromaddr,
3158 $fromaddr
3159 );
3160
3161 $email_body .= '</td>
3162 </tr>
3163 </tbody></table>
3164 </div>
3165
3166 <div style="font-family:-apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;box-sizing:border-box;font-size:14px;line-height:1.5;max-width:600px;overflow:visible;display:block;margin:0">
3167 <table width="100%" cellpadding="0" cellspacing="0" style="font-family:&quot;Helvetica Neue&quot;,&quot;Helvetica&quot;,Helvetica,Arial,sans-serif;box-sizing:border-box;font-size:14px;line-height:1.5">
3168 <tbody><tr style="font-family:-apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;box-sizing:border-box;font-size:14px;line-height:1.5">
3169 <td style="font-family:-apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;box-sizing:border-box;font-size:14px;line-height:1.5;vertical-align:top;width:100%;clear:both;color:#777;border-top-width:1px;border-top-color:#d0d0d0;border-top-style:solid;padding:25px" valign="top">
3170 <p>Sent from <a href="' . esc_attr( get_bloginfo( 'url' ) ) . '">' . $cleanname . '</a> using the <strong>WPZOOM Forms</strong> plugin.</p>
3171 <br style="font-family:&quot;Helvetica Neue&quot;,&quot;Helvetica&quot;,Helvetica,Arial,sans-serif;box-sizing:border-box;font-size:14px;line-height:1.5">
3172 </td>
3173 </tr>
3174 </tbody></table>
3175 </div>
3176 </body>
3177 </html>';
3178
3179 $details = array(
3180 'from' => $fromaddr,
3181 'message' => $raw_content
3182 );
3183
3184 if ( $this->not_spam( $details ) ) {
3185 $success = wp_mail( $sendto, $subjectline, $email_body, $headers );
3186 }
3187 }
3188 if ( 'db' == $form_method || 'combined' == $form_method ) {
3189 $content = array(
3190 '_wpzf_form_id' => $form_id,
3191 '_wpzf_fields' => array()
3192 );
3193
3194 $replyto = '';
3195 $sbj = '';
3196
3197 foreach ( $_REQUEST as $key => $value ) {
3198 if ( strpos( $key, 'wpzf_' ) === 0 ) {
3199 $id = substr( $key, 5 );
3200 $name = isset( $input_blocks[ $id ] ) ? $input_blocks[ $id ] : __( 'Unnamed Input', 'wpzoom-forms' );
3201 $value = is_array( $value ) ? implode( ', ', $value ) : $value;
3202
3203 if ( 'wpzf_replyto' == $key || 'wpzf_subject' == $key ) {
3204 if ( 'wpzf_replyto' == $key ) {
3205 $replyto = sanitize_text_field( $value );
3206 } elseif ( 'wpzf_subject' == $key ) {
3207 $sbj = sanitize_text_field( $value );
3208 }
3209
3210 continue;
3211 }
3212
3213 $content['_wpzf_fields'][ $name ] = sanitize_textarea_field( $value );
3214 }
3215 }
3216
3217 $details = array(
3218 'from' => '',
3219 'message' => $content
3220 );
3221
3222 if ( ! empty( $replyto ) ) {
3223 $fromaddr = isset( $_REQUEST[ $replyto ] ) ? sanitize_email( $_REQUEST[ $replyto ] ) : $sendto;
3224 $details['from'] = $fromaddr;
3225 }
3226
3227 if ( $this->not_spam( $details ) ) {
3228 $success = false !== $content && 0 < wp_insert_post( array(
3229 'post_type' => 'wpzf-submission',
3230 'post_status' => 'publish',
3231 'comment_status' => 'closed',
3232 'ping_status' => 'closed',
3233 'post_title' => __( 'Submission', 'wpzoom-forms' ),
3234 'post_author' => 1,
3235 'post_category' => array( 1 ),
3236 'post_content' => __( 'Submission', 'wpzoom-forms' ),
3237 'meta_input' => $content
3238 ) );
3239 }
3240 }
3241 }
3242 }
3243
3244 $redirect_url = urldecode(
3245 add_query_arg(
3246 array(
3247 'success' => ( $success ? '1' : '0' ),
3248 'wpzf_submitted_form' => $form_id,
3249 ),
3250 $url
3251 )
3252 );
3253
3254 wp_safe_redirect(
3255 $redirect_url . ( $form_id > -1 ? '#wpzf-' . $form_id . '-notice' : '' )
3256 );
3257
3258 exit;
3259 }
3260
3261 /**
3262 * Filters a hierarchical array of Gutenberg blocks to return just the blocks added by this plugin.
3263 *
3264 * @access public
3265 * @param array $blocks A hierarchical array of Gutenberg blocks.
3266 * @return array
3267 * @since 1.0.0
3268 */
3269 public function get_input_blocks( $blocks ) {
3270 $found = array();
3271
3272 if ( is_array( $blocks ) && count( $blocks ) > 0 ) {
3273 for ( $i = 0; $i < count( $blocks ); $i++ ) {
3274 $block = $blocks[ $i ];
3275
3276 if ( isset( $block['blockName'] ) &&
3277 preg_match( '/^wpzoom\-forms\//i', $block['blockName'] ) &&
3278 ! preg_match( '/(label|submit)\-field$/i', $block['blockName'] ) &&
3279 isset( $block['attrs'] ) ) {
3280 $attrs = $block['attrs'];
3281
3282 if ( array_key_exists( 'id', $attrs ) && array_key_exists( 'name', $attrs ) ) {
3283 $found[ $attrs['id'] ] = $attrs['name'];
3284 }
3285 }
3286
3287 if ( isset( $block['innerBlocks'] ) ) {
3288 $found = array_merge( $found, $this->get_input_blocks( $block['innerBlocks'] ) );
3289 }
3290 }
3291 }
3292
3293 return $found;
3294 }
3295
3296 /**
3297 * Returns the output for the form shortcode.
3298 *
3299 * @access public
3300 * @param array|string $atts Shortcode attributes array or empty string.
3301 * @param string $content The shortcode content, or null if not set.
3302 * @param string $tag The shortcode name.
3303 * @return string The shortcode output.
3304 * @since 1.0.0
3305 */
3306 public function shortcode_output( $atts, $content, $tag ) {
3307 $id = is_array( $atts ) && array_key_exists( 'id', $atts ) ? intval( $atts['id'] ) : -1;
3308 $output = '';
3309
3310 if ( $id > 0 ) {
3311 wp_enqueue_script( 'wpzoom-forms-js-frontend-formblock' );
3312 wp_enqueue_style( 'wpzoom-forms-css-frontend-formblock' );
3313
3314 $output = $this->form_block_render( array( 'formId' => $id ) );
3315 }
3316
3317 return $output;
3318 }
3319
3320 /**
3321 * Register the PRO upsell notice with WPZOOM Notice Center.
3322 *
3323 * @since 1.3.4
3324 * @param array $notices Existing notices from the filter.
3325 * @return array Notices with upsell notice added when applicable.
3326 */
3327 public function register_upsell_notice( $notices ) {
3328 if ( ! is_array( $notices ) ) {
3329 $notices = array();
3330 }
3331
3332 // Don't add if PRO version is active.
3333 if ( defined( 'WPZOOM_FORMS_PRO_VERSION' ) ) {
3334 return $notices;
3335 }
3336
3337 $upsell_url = admin_url( 'edit.php?post_type=wpzf-form&page=wpzf-pro-page' );
3338 $pro_url = 'https://www.wpzoom.com/plugins/wpzoom-forms/?utm_source=wpadmin&utm_medium=wpzoom-forms-free&utm_campaign=upsell-notice';
3339
3340 $content = '<p>' . esc_html__( 'Unlock powerful features to build smarter, more flexible forms:', 'wpzoom-forms' ) . '</p>';
3341 $content .= '<ul>';
3342 $content .= '<li><strong>' . esc_html__( 'AI Form Generator', 'wpzoom-forms' ) . '</strong> &mdash; ' . esc_html__( 'Describe your form in simple words and let AI create it for you!', 'wpzoom-forms' ) . '</li>';
3343 $content .= '<li><strong>' . esc_html__( '30+ Pre-built Templates', 'wpzoom-forms' ) . '</strong> &mdash; ' . esc_html__( 'Healthcare, education, real estate, restaurant, HR, and more categories.', 'wpzoom-forms' ) . '</li>';
3344 $content .= '<li><strong>' . esc_html__( 'Conditional Logic', 'wpzoom-forms' ) . '</strong> &mdash; ' . esc_html__( 'Show or hide fields dynamically based on user selections.', 'wpzoom-forms' ) . '</li>';
3345 $content .= '<li><strong>' . esc_html__( 'Mailchimp Integration', 'wpzoom-forms' ) . '</strong> &mdash; ' . esc_html__( 'Automatically add subscribers to your Mailchimp audiences.', 'wpzoom-forms' ) . '</li>';
3346 $content .= '<li><strong>' . esc_html__( 'Import/Export Forms', 'wpzoom-forms' ) . '</strong> &mdash; ' . esc_html__( 'Easily transfer forms between sites or back up your work.', 'wpzoom-forms' ) . '</li>';
3347 $content .= '<li><strong>' . esc_html__( 'Export Submissions to CSV', 'wpzoom-forms' ) . '</strong> &mdash; ' . esc_html__( 'Download form submissions as CSV files for reporting.', 'wpzoom-forms' ) . '</li>';
3348 $content .= '</ul>';
3349
3350 $notices[] = array(
3351 'id' => 'wpzf_upsell_pro',
3352 'priority' => 10,
3353 'heading' => __( 'Upgrade to WPZOOM Forms PRO', 'wpzoom-forms' ),
3354 'content' => $content,
3355 'icon' => array(
3356 'type' => 'image',
3357 'src' => plugins_url( 'dist/assets/admin/images/icon-forms.svg', __FILE__ ),
3358 ),
3359 'primary_button' => array(
3360 'label' => __( 'Upgrade to PRO', 'wpzoom-forms' ),
3361 'url' => $pro_url,
3362 'new_tab' => true,
3363 ),
3364 'secondary_button' => array(
3365 'label' => __( 'Learn More', 'wpzoom-forms' ),
3366 'url' => $upsell_url,
3367 'new_tab' => false,
3368 ),
3369 'capability' => 'manage_options',
3370 'screens' => array( 'dashboard', 'edit-wpzf-form', 'wpzf-form', 'edit-wpzf-submission', 'wpzf-submission' ),
3371 'source' => 'WPZOOM Forms',
3372 'conditions' => function () {
3373 return ! get_option( 'wpzf_upsell_notice_dismissed' );
3374 },
3375 );
3376
3377 return $notices;
3378 }
3379 }
3380
3381 if( ! function_exists ( 'wpzoom_forms_load_files' ) ) {
3382 function wpzoom_forms_load_files() {
3383
3384 //Load Settings Panel
3385 require_once 'classes/class-wpzoom-forms-settings-fields.php';
3386 require_once 'classes/class-wpzoom-forms-settings-page.php';
3387 require_once 'classes/class-wpzoom-forms-template-manager.php';
3388 require_once 'classes/class-wpzoom-forms-settings-upsell.php';
3389
3390 }
3391 add_action( 'plugin_loaded', 'wpzoom_forms_load_files' );
3392 }
3393
3394 if ( ! function_exists( 'wpzoom_forms_plugin_action_links' ) ) {
3395 /**
3396 * Plugin action links.
3397 *
3398 * Adds action links to the plugin list table.
3399 *
3400 * Fired by `plugin_action_links` filter.
3401 *
3402 * @since 1.3.5
3403 *
3404 * @param array $links An array of plugin action links.
3405 *
3406 * @return array An array of plugin action links.
3407 */
3408 function wpzoom_forms_plugin_action_links( $links ) {
3409 $settings_link = sprintf(
3410 '<a href="%1$s">%2$s</a>',
3411 admin_url( 'edit.php?post_type=wpzf-form&page=' . WPZOOM_FORMS_SETTINGS_PAGE ),
3412 esc_html__( 'Settings', 'wpzoom-forms' )
3413 );
3414
3415 array_unshift( $links, $settings_link );
3416
3417 $links['go_pro'] = sprintf(
3418 '<a href="%1$s" target="_blank" class="wpzoom-forms-gopro" style="color:#2271b1;font-weight:bold;">%2$s &rarr; <span class="wpzoom-premium-badge" style="background-color: #2271b1; color: #fff; margin-left: 5px; font-size: 11px; min-height: 16px; border-radius: 8px; display: inline-block; font-weight: 600; line-height: 1.6; padding: 0 8px;">%3$s</span></a>',
3419 'https://www.wpzoom.com/plugins/wpzoom-forms/?utm_source=wpadmin&utm_medium=plugin&utm_campaign=wpzoom-forms-free&utm_content=plugins-page',
3420 esc_html__( 'UPGRADE', 'wpzoom-forms' ),
3421 esc_html__( 'PRO', 'wpzoom-forms' )
3422 );
3423
3424 return $links;
3425 }
3426 add_filter( 'plugin_action_links_' . WPZOOM_FORMS_PLUGIN_BASE, 'wpzoom_forms_plugin_action_links' );
3427 }
3428
3429 /**
3430 * Check if the Elementor Page Builder is enabled load the widget
3431 */
3432 if ( defined( 'ELEMENTOR_VERSION' ) && is_callable( 'Elementor\Plugin::instance' ) ) {
3433 require_once 'elementor/wpzoom-forms-elementor.php';
3434 }
3435
3436 /* -------------------------------------------------------------------------- */
3437 /* WPZOOM Forms v2 — schema-driven builder, REST API, renderer. */
3438 /* Coexists with the legacy block system: schema is the source of truth, the */
3439 /* legacy renderer remains a fallback during/after migration. */
3440 /* -------------------------------------------------------------------------- */
3441 require_once WPZOOM_FORMS_PATH . 'classes/class-wpzoom-forms-schema.php';
3442 require_once WPZOOM_FORMS_PATH . 'classes/class-wpzoom-forms-migration.php';
3443 require_once WPZOOM_FORMS_PATH . 'classes/class-wpzoom-forms-templates.php';
3444 require_once WPZOOM_FORMS_PATH . 'classes/class-wpzoom-forms-option-lists.php';
3445 require_once WPZOOM_FORMS_PATH . 'classes/class-wpzoom-forms-rest.php';
3446 require_once WPZOOM_FORMS_PATH . 'classes/class-wpzoom-forms-renderer.php';
3447 require_once WPZOOM_FORMS_PATH . 'classes/class-wpzoom-forms-submission-handler.php';
3448 require_once WPZOOM_FORMS_PATH . 'classes/class-wpzoom-forms-builder-page.php';
3449 require_once WPZOOM_FORMS_PATH . 'classes/class-wpzoom-forms-submission-view.php';
3450
3451 /**
3452 * Shared embed helper used by the shortcode and the Elementor widget.
3453 *
3454 * Routes to the v2 renderer for forms saved through the new builder
3455 * (_wpzf_schema postmeta present) and to the legacy block renderer for
3456 * old block-based forms that have no schema yet.
3457 *
3458 * @param int $id Form post ID.
3459 * @return string Rendered HTML.
3460 */
3461 function wpzoom_forms_render_embed( $id ) {
3462 global $wpzoom_forms;
3463 $id = (int) $id;
3464 if ( $id < 1 ) {
3465 return '';
3466 }
3467
3468 if ( get_post_meta( $id, WPZOOM_Forms_Schema::META_KEY, true ) ) {
3469 return WPZOOM_Forms_Renderer::render( $id );
3470 }
3471
3472 // Legacy form — fall back to the block-based renderer.
3473 if ( $wpzoom_forms ) {
3474 wp_enqueue_script( 'wpzoom-forms-js-frontend-formblock' );
3475 wp_enqueue_style( 'wpzoom-forms-css-frontend-formblock' );
3476 return $wpzoom_forms->form_block_render( array( 'formId' => $id ) );
3477 }
3478
3479 return '';
3480 }
3481
3482 add_action( 'init', function() {
3483 global $wpzoom_forms;
3484
3485 // Take over form submission from the legacy handler.
3486 if ( $wpzoom_forms ) {
3487 remove_action( 'admin_post_wpzf_submit', array( $wpzoom_forms, 'action_form_post' ), 10 );
3488 remove_action( 'admin_post_nopriv_wpzf_submit', array( $wpzoom_forms, 'action_form_post' ), 10 );
3489 // The legacy class whitelist-resets $wp_meta_boxes to keep ONLY its own boxes —
3490 // drop that hook so our submission-detail boxes survive.
3491 remove_action( 'in_admin_header', array( $wpzoom_forms, 'remove_meta_boxes' ), 100 );
3492 // The legacy meta-box registration adds boxes we replace with our own.
3493 remove_action( 'add_meta_boxes_wpzf-submission', array( $wpzoom_forms, 'add_meta_boxes' ), 10 );
3494 }
3495
3496 // Disable the legacy template-picker modal — our React builder owns templates now.
3497 if ( class_exists( 'WPZOOM_Forms_Template_Manager' ) ) {
3498 $legacy_tm = WPZOOM_Forms_Template_Manager::instance();
3499 remove_action( 'admin_enqueue_scripts', array( $legacy_tm, 'scripts' ) );
3500 remove_action( 'admin_footer', array( $legacy_tm, 'modal_window' ) );
3501 remove_filter( 'default_content', array( $legacy_tm, 'default_form_content' ), 10 );
3502 }
3503
3504 // Register meta so REST/admin can expose it.
3505 register_post_meta( 'wpzf-form', WPZOOM_Forms_Schema::META_KEY, array(
3506 'type' => 'string',
3507 'single' => true,
3508 'show_in_rest' => true,
3509 'auth_callback' => function() { return current_user_can( 'edit_posts' ); },
3510 ) );
3511
3512 ( new WPZOOM_Forms_REST() )->register();
3513 ( new WPZOOM_Forms_Submission_Handler() )->register();
3514 ( new WPZOOM_Forms_Builder_Page() )->register();
3515 ( new WPZOOM_Forms_Submission_View() )->register();
3516 }, 11 ); // After main class init (priority 9).
3517
3518 /**
3519 * Override the legacy shortcode to route through the shared embed helper,
3520 * which selects v2 or legacy renderer based on whether _wpzf_schema exists.
3521 */
3522 add_action( 'init', function() {
3523 remove_shortcode( 'wpzf_form' );
3524 add_shortcode( 'wpzf_form', function( $atts ) {
3525 $atts = shortcode_atts( array( 'id' => 0 ), $atts, 'wpzf_form' );
3526 return wpzoom_forms_render_embed( (int) $atts['id'] );
3527 } );
3528 }, 12 );
3529
3530 /**
3531 * For the form-block (Gutenberg block that embeds a form by id), substitute
3532 * the render callback to use the new renderer. The block remains registered
3533 * so existing content keeps working. We hook `render_block` (rather than
3534 * patching the block type at registration time) because the main class
3535 * registers the block at init:9, before our filters get a chance to attach.
3536 */
3537 add_filter( 'render_block', function( $block_content, $block ) {
3538 if ( ! isset( $block['blockName'] ) || $block['blockName'] !== 'wpzoom-forms/form-block' ) return $block_content;
3539 if ( is_admin() ) return $block_content;
3540 $id = isset( $block['attrs']['formId'] ) ? (int) $block['attrs']['formId'] : 0;
3541 if ( $id < 1 ) return $block_content;
3542
3543 // Only switch to the v2 renderer when the form has been saved through the new
3544 // builder (_wpzf_schema exists). Otherwise return the original block output
3545 // unchanged so old forms keep all their existing styling and behaviour.
3546 if ( ! get_post_meta( $id, WPZOOM_Forms_Schema::META_KEY, true ) ) {
3547 return $block_content;
3548 }
3549
3550 // The render_callback (form_block_render) already built a <style> tag containing
3551 // per-form color/border overrides from the block attributes (fieldBgColor, btnBgColor, …).
3552 // Preserve it so those settings actually apply to the new renderer's output.
3553 $block_styles = '';
3554 if ( preg_match( '/(<style\b[^>]*>.*?<\/style>)/is', $block_content, $m ) ) {
3555 $block_styles = $m[1];
3556 }
3557
3558 // Preserve the block's alignment (wide/full) on the rendered wrapper. The
3559 // render callback no longer produces this output, so the align class set in
3560 // the editor must be re-applied here or the frontend ignores it.
3561 $align = isset( $block['attrs']['align'] ) ? $block['attrs']['align'] : '';
3562 $root_classes = ( $align && 'none' !== $align ) ? 'align' . $align : '';
3563
3564 return $block_styles . WPZOOM_Forms_Renderer::render( $id, $root_classes );
3565 }, 9, 2 );
3566
3567
3568 /**
3569 * Customize the "All Forms" admin list a bit:
3570 * - rename "Add New" wording, replace target link with our builder.
3571 */
3572 add_action( 'admin_head-edit.php', function() {
3573 $screen = get_current_screen();
3574 if ( ! $screen || $screen->post_type !== 'wpzf-form' ) return;
3575 ?>
3576 <style>
3577 .wp-heading-inline + .page-title-action { background: #2271b1; color: #fff; border-color: #2271b1; }
3578 .wp-heading-inline + .page-title-action:hover { background: #135e96; }
3579 </style>
3580 <script>
3581 jQuery(function($){
3582 var btn = $('.page-title-action').first();
3583 if (btn.length) {
3584 btn.attr('href', '<?php echo esc_js( admin_url( 'admin.php?page=wpzf-form-builder' ) ); ?>');
3585 btn.text(<?php echo wp_json_encode( __( 'Add New Form', 'wpzoom-forms' ) ); ?>);
3586 }
3587 });
3588 </script>
3589 <?php
3590 } );
3591
3592 /**
3593 * Render the legacy "Upgrade to PRO" sidebar banner on the forms list page.
3594 * The legacy template-manager modal used to ship this; we disabled that modal
3595 * when replacing the form editor, so re-emit the banner here so the upsell
3596 * isn't lost.
3597 */
3598 add_action( 'admin_footer-edit.php', function() {
3599 $screen = get_current_screen();
3600 if ( ! $screen || $screen->post_type !== 'wpzf-form' ) return;
3601 if ( ! class_exists( 'WPZOOM_Forms_Settings' ) ) return;
3602
3603 $settings_class = new WPZOOM_Forms_Settings();
3604 $settings_class->upsell_banner(); // echoes the .wpzoom-forms-settings-upsell-container markup
3605 ?>
3606 <script>
3607 jQuery(function($){
3608 var $banner = $('.wpzoom-forms-settings-upsell-container').last();
3609 if ( ! $banner.length ) return;
3610 // Drop the upsell into the right rail next to the posts table.
3611 var $filter = $('#posts-filter');
3612 if ( $filter.length ) {
3613 $banner.css({ 'float':'right', 'margin-left':'20px', 'max-width':'340px' });
3614 $filter.before( $banner );
3615 }
3616 });
3617 </script>
3618 <?php
3619 } );
3620
3621