PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.11.1
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.11.1
2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 0.0.2 0.0.3 All 96 releases
sureforms / admin / admin.php
admin.php
2,245 lines 77.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin Class.
4 *
5 * @package sureforms.
6 */
7
8 namespace SRFM\Admin;
9
10 use Astra_Notices;
11 use SRFM\Inc\AI_Form_Builder\AI_Helper;
12 use SRFM\Inc\Database\Tables\Entries;
13 use SRFM\Inc\Global_Settings\Global_Settings;
14 use SRFM\Inc\Helper;
15 use SRFM\Inc\Onboarding;
16 use SRFM\Inc\Payments\Payment_Helper;
17 use SRFM\Inc\Payments\Stripe\Stripe_Helper;
18 use SRFM\Inc\Smart_Tags;
19 use SRFM\Inc\Traits\Get_Instance;
20
21 if ( ! defined( 'ABSPATH' ) ) {
22 exit; // Exit if accessed directly.
23 }
24
25 if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
26 require_once SRFM_DIR . 'inc/lib/astra-notices/class-bsf-admin-notices.php';
27 }
28 /**
29 * Admin handler class.
30 *
31 * @since 0.0.1
32 */
33 class Admin {
34 use Get_Instance;
35
36 /**
37 * Minimum number of forms or entries required to show the rating notice.
38 *
39 * @since 2.5.2
40 */
41 public const RATING_NOTICE_THRESHOLD = 3;
42
43 /**
44 * Dashboard widget entries data.
45 *
46 * @var array
47 * @since 1.9.1
48 */
49 private $dashboard_widget_data = [];
50
51 /**
52 * Cached result for whether the rating notice should display.
53 *
54 * @var bool|null
55 * @since 2.5.2
56 */
57 private $should_show_rating = null;
58
59 /**
60 * SureForms Page Default permission.
61 *
62 * @var string
63 * @since 1.12.2
64 */
65 private static $sureforms_page_default_capability = 'manage_options';
66
67 /**
68 * Class constructor.
69 *
70 * @return void
71 * @since 0.0.1
72 */
73 public function __construct() {
74 add_action( 'admin_menu', [ $this, 'add_menu_page' ], 9 );
75 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
76 add_action( 'admin_menu', [ $this, 'settings_page' ] );
77 add_action( 'admin_menu', [ $this, 'add_learn_page' ] );
78 add_action( 'admin_menu', [ $this, 'add_new_form' ] );
79 add_action( 'admin_menu', [ $this, 'add_suremail_page' ] );
80 if ( ! Helper::has_pro() ) {
81 add_action( 'admin_menu', [ $this, 'add_quiz_page' ] );
82 add_action( 'admin_menu', [ $this, 'add_survey_reports_page' ] );
83 add_action( 'admin_menu', [ $this, 'add_partial_entries_page' ] );
84 add_action( 'admin_menu', [ $this, 'add_upgrade_to_pro' ] );
85 add_action( 'admin_footer', [ $this, 'add_upgrade_to_pro_target_attr' ] );
86 }
87
88 add_filter( 'plugin_action_links', [ $this, 'add_settings_link' ], 10, 2 );
89 add_action( 'enqueue_block_assets', [ $this, 'enqueue_styles' ] );
90 add_action( 'admin_head', [ $this, 'enqueue_header_styles' ] );
91 add_filter( 'admin_body_class', [ $this, 'admin_template_picker_body_class' ] );
92
93 // this action is used to restrict Spectra's quick action bar on SureForms CPTS.
94 add_action( 'uag_enable_quick_action_sidebar', [ $this, 'restrict_spectra_quick_action_bar' ] );
95
96 add_action( 'current_screen', [ $this, 'enable_gutenberg_for_sureforms' ], 100 );
97 // Register notices early for React pages (before admin_enqueue_scripts).
98 add_action( 'admin_init', [ $this, 'register_pro_compatibility_notices' ], 5 );
99 // Display notices on traditional WordPress admin pages.
100 add_action( 'admin_notices', [ $this, 'srfm_pro_version_compatibility' ] );
101
102 // Enfold theme compatibility to enable block editor for SureForms post type.
103 add_filter( 'avf_use_block_editor_for_post', [ $this, 'enable_block_editor_in_enfold_theme' ] );
104
105 // Add action links to the plugin page.
106 add_filter( 'plugin_action_links_' . SRFM_BASENAME, [ $this, 'add_action_links' ] );
107 // Check if admin notification is enabled and add entries badge.
108 $general_options = get_option( 'srfm_general_settings_options', [] );
109 $admin_notification_on = isset( $general_options['srfm_admin_notification'] ) ? (bool) $general_options['srfm_admin_notification'] : true;
110
111 if ( $admin_notification_on ) {
112 add_action( 'admin_menu', [ $this, 'maybe_add_entries_badge' ], 99 );
113 }
114
115 add_filter( 'wpforms_current_user_can', [ $this, 'disable_wpforms_capabilities' ], 10, 3 );
116
117 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_pointer' ] );
118 // Ajax callbacks for wp-pointer functionality.
119 add_action( 'wp_ajax_should_show_pointer', [ $this, 'pointer_should_show' ] );
120 add_action( 'wp_ajax_sureforms_dismiss_pointer', [ $this, 'pointer_dismissed' ] );
121 add_action( 'wp_ajax_sureforms_accept_cta', [ $this, 'pointer_accepted_cta' ] );
122 add_action( 'wp_ajax_srfm_notice_response', [ $this, 'handle_notice_response' ] );
123
124 // Register dashboard widget only if there are recent entries.
125 add_action( 'admin_init', [ $this, 'maybe_register_dashboard_widget' ] );
126
127 // Save first form creation time stamp.
128 add_action( 'admin_init', [ $this, 'save_first_form_creation_time_stamp' ] );
129 add_action( 'admin_notices', [ $this, 'display_srfm_rating_notice' ] );
130 add_action( 'admin_notices', [ $this, 'display_srfm_getting_started_notice' ] );
131
132 /**
133 * Suppress foreign (third-party) admin notices on SureForms admin screens.
134 *
135 * Some plugins (e.g. Ninja Forms) print large promotional banners on every
136 * admin page via the admin_notices / all_admin_notices / network_admin_notices
137 * hooks. These bleed onto SureForms' own React admin screens and break the UI.
138 * We run at the EARLIEST priority on each notice hook (all third-party
139 * callbacks are registered before these hooks fire, during admin_init /
140 * plugin load) and strip the foreign ones before they are echoed, while
141 * preserving SureForms' own notices. Scoped strictly to SureForms screens.
142 */
143 add_action( 'admin_notices', [ $this, 'suppress_foreign_admin_notices' ], PHP_INT_MIN );
144 add_action( 'all_admin_notices', [ $this, 'suppress_foreign_admin_notices' ], PHP_INT_MIN );
145 add_action( 'network_admin_notices', [ $this, 'suppress_foreign_admin_notices' ], PHP_INT_MIN );
146 }
147
148 /**
149 * Remove third-party admin notices on SureForms admin screens.
150 *
151 * Iterates over the callbacks registered on the admin notice hooks and
152 * removes any that are not owned by SureForms. A callback is considered
153 * owned by SureForms when it belongs to a class in the `SRFM` / `SRFM_PRO`
154 * namespaces or to the bundled `BSF_Admin_Notices` / `Astra_Notices`
155 * notices library. SureForms' own notices are therefore preserved while
156 * foreign promotional banners are suppressed.
157 *
158 * This callback is hooked at `PHP_INT_MIN` so that it runs first on each
159 * notice hook and removes the foreign callbacks before WordPress echoes
160 * them (WP_Hook honours removals made during iteration). It is strictly
161 * scoped to SureForms admin screens via {@see Helper::is_sureforms_admin_page()}
162 * so no other admin page is affected.
163 *
164 * @since 2.10.0
165 * @return void
166 */
167 public function suppress_foreign_admin_notices() {
168 // Bail early if we are not on a SureForms admin screen. This keeps the
169 // suppression strictly scoped and avoids touching any other admin page.
170 // is_sureforms_admin_page() covers the core screens (dashboard, add-new,
171 // settings, entries, the form CPT); we additionally match any admin page
172 // whose `page` slug is SureForms-owned (sureforms_* / srfm_*) so the
173 // suppression also applies to the payments/quiz/survey/learn/SMTP screens.
174 if ( ! Helper::is_sureforms_admin_page() && ! $this->is_sureforms_owned_admin_page() ) {
175 return;
176 }
177
178 global $wp_filter;
179
180 // The hook currently being fired (admin_notices, all_admin_notices or network_admin_notices).
181 $current_hook = current_action();
182
183 if ( empty( $current_hook ) || empty( $wp_filter[ $current_hook ] ) || ! ( $wp_filter[ $current_hook ] instanceof \WP_Hook ) ) {
184 return;
185 }
186
187 foreach ( $wp_filter[ $current_hook ]->callbacks as $priority => $callbacks ) {
188 foreach ( $callbacks as $callback ) {
189 $function = $callback['function'] ?? null;
190
191 // Never remove our own suppression callback.
192 if ( is_array( $function ) && isset( $function[0] ) && $function[0] === $this && 'suppress_foreign_admin_notices' === $function[1] ) {
193 continue;
194 }
195
196 // Preserve SureForms-owned notices, remove everything else.
197 if ( $this->is_sureforms_owned_notice_callback( $function ) ) {
198 continue;
199 }
200
201 remove_action( $current_hook, $function, $priority );
202 }
203 }
204 }
205
206 /**
207 * Get the first form creation time stamp.
208 *
209 * @since 1.10.1
210 * @return int|false
211 */
212 public static function get_first_form_creation_time_stamp() {
213 return Helper::get_srfm_option( 'first_form_created_at', false );
214 }
215
216 /**
217 * Check if the first form has been created.
218 *
219 * @since 1.10.1
220 * @return bool
221 */
222 public static function is_first_form_created() {
223 // Convert the first form creation time stamp to a boolean. If it exists, it will return true, otherwise false.
224 $first_form_creation_time_stamp = self::get_first_form_creation_time_stamp();
225
226 // If the first form creation time stamp is not set, return false.
227 if ( ! $first_form_creation_time_stamp ) {
228 return false; // No forms created yet.
229 }
230
231 // Check if the first form creation time stamp is a valid integer and greater than zero.
232 return is_int( $first_form_creation_time_stamp ) && $first_form_creation_time_stamp > 0;
233 }
234
235 /**
236 * Check and save the first form creation time stamp.
237 * If not already saved.
238 *
239 * @since 1.10.1
240 * @return void
241 */
242 public static function save_first_form_creation_time_stamp() {
243 if ( ! Helper::current_user_can() || self::is_first_form_created() || ! defined( 'SRFM_FORMS_POST_TYPE' ) || ! post_type_exists( SRFM_FORMS_POST_TYPE ) ) {
244 return;
245 }
246
247 // Get the first form creation time from the database that is published.
248 $query = new \WP_Query(
249 [
250 'post_type' => SRFM_FORMS_POST_TYPE,
251 'posts_per_page' => 1,
252 'orderby' => 'date',
253 'order' => 'ASC',
254 'fields' => 'ids',
255 'post_status' => 'publish',
256 ]
257 );
258
259 if ( ! empty( $query->posts ) && isset( $query->posts[0] ) ) {
260 // Get the first post from the query result.
261 $post_id = $query->posts[0];
262 // Get the post creation time in GMT.
263 $creation_time = get_post_field( 'post_date_gmt', $post_id );
264 // Convert the creation time to a timestamp.
265 $timestamp = strtotime( $creation_time );
266
267 if ( ! $timestamp ) {
268 return;
269 }
270
271 Helper::update_srfm_option( 'first_form_created_at', $timestamp );
272 }
273 }
274
275 /**
276 * Check if n days have passed since the first form creation.
277 * This is used to determine if the dynamic nudges should be shown.
278 *
279 * @param int $days Number of days to check.
280 * @since 1.10.1
281 * @return bool
282 */
283 public static function check_first_form_creation_threshold( $days = 3 ) {
284 $first_form_creation_time_stamp = self::get_first_form_creation_time_stamp();
285
286 if ( ! $first_form_creation_time_stamp ) {
287 return false; // No forms created yet.
288 }
289
290 /**
291 * Calculate the number of days since the first form was created.
292 */
293 $days_from_creation = ( strtotime( current_time( 'mysql' ) ) - $first_form_creation_time_stamp ) / DAY_IN_SECONDS;
294
295 // Return a boolean indicating if the number of days since creation is greater than the specified days.
296 return $days_from_creation > $days;
297 }
298
299 /**
300 * Show action on plugin page.
301 *
302 * @param array $links links.
303 * @return array
304 * @since 1.4.2
305 */
306 public function add_action_links( $links ) {
307 if ( ! Helper::has_pro() ) {
308 // Display upsell link if SureForms Pro is not installed.
309 $upsell_link = Helper::get_sureforms_website_url( 'pricing', [ 'utm_medium' => 'plugin-list' ] );
310
311 ob_start();
312 ?>
313 <a href="<?php echo esc_url( $upsell_link ); ?>" target="_blank" rel="noreferrer" class="sureforms-plugins-go-pro">
314 <?php echo esc_html__( 'Get SureForms Pro', 'sureforms' ); ?>
315 </a>
316 <?php
317 $links[] = trim( ob_get_clean() );
318 }
319
320 return $links;
321 }
322
323 /**
324 * Enable block editor in Enfold theme for SureForms post type.
325 *
326 * @param bool $use_block_editor Whether to use block editor.
327 * @since 1.3.1
328 */
329 public function enable_block_editor_in_enfold_theme( $use_block_editor ) {
330 // if SureForms form post type then return true.
331 if ( SRFM_FORMS_POST_TYPE === get_current_screen()->post_type ) {
332 return true;
333 }
334 return $use_block_editor;
335 }
336
337 /**
338 * Enable Gutenberg for SureForms associated post types.
339 *
340 * @since 0.0.10
341 */
342 public function enable_gutenberg_for_sureforms() {
343 /**
344 * Check if the classic editor is enabled from Classic Editor plugin settings or Divi settings.
345 */
346 if ( 'block' === get_option( 'classic-editor-replace' ) || 'on' === get_option( 'et_enable_classic_editor' ) ) {
347 return;
348 }
349
350 $srfm_post_types = apply_filters( 'srfm_enable_gutenberg_post_types', [ SRFM_FORMS_POST_TYPE ] );
351
352 if ( in_array( get_current_screen()->post_type, $srfm_post_types, true ) ) {
353 add_filter( 'use_block_editor_for_post_type', '__return_true', 110 );
354 add_filter( 'gutenberg_can_edit_post_type', '__return_true', 110 );
355 }
356 }
357
358 /**
359 * Sureforms editor header styles.
360 *
361 * @since 0.0.1
362 */
363 public function enqueue_header_styles() {
364 $current_screen = get_current_screen();
365 $file_prefix = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? '' : '.min';
366 $dir_name = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? 'unminified' : 'minified';
367
368 $css_uri = SRFM_URL . 'assets/css/' . $dir_name . '/';
369
370 /* RTL */
371 if ( is_rtl() ) {
372 $file_prefix .= '-rtl';
373 }
374
375 if ( 'sureforms_form' === $current_screen->id ) {
376 wp_enqueue_style( SRFM_SLUG . '-editor-header-styles', $css_uri . 'header-styles' . $file_prefix . '.css', [], SRFM_VER );
377 }
378 }
379
380 /**
381 * Add menu page.
382 *
383 * @return void
384 * @since 0.0.1
385 */
386 public function add_menu_page() {
387 $menu_slug = 'sureforms_menu';
388
389 $logo = file_get_contents( plugin_dir_path( SRFM_FILE ) . 'images/icon.svg' );
390 add_menu_page(
391 __( 'SureForms', 'sureforms' ),
392 __( 'SureForms', 'sureforms' ),
393 self::$sureforms_page_default_capability,
394 $menu_slug,
395 static function () {
396 },
397 'data:image/svg+xml;base64,' . base64_encode( $logo ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
398 30
399 );
400
401 // Add the Dashboard Submenu.
402 add_submenu_page(
403 $menu_slug,
404 __( 'Dashboard', 'sureforms' ),
405 __( 'Dashboard', 'sureforms' ),
406 self::$sureforms_page_default_capability,
407 $menu_slug,
408 [ $this, 'render_dashboard' ]
409 );
410 }
411
412 /**
413 * Add Settings page.
414 *
415 * @return void
416 * @since 0.0.1
417 */
418 public function settings_page() {
419 $callback = [ $this, 'settings_page_callback' ];
420 add_submenu_page(
421 'sureforms_menu',
422 __( 'Settings', 'sureforms' ),
423 __( 'Settings', 'sureforms' ),
424 self::$sureforms_page_default_capability,
425 'sureforms_form_settings',
426 $callback
427 );
428
429 // Get the current submenu page.
430 $submenu_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- $_GET['page'] does not provide nonce.
431
432 if ( ! isset( $_GET['tab'] ) && 'sureforms_form_settings' === $submenu_page ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- $_GET['page'] does not provide nonce.
433 wp_safe_redirect( admin_url( 'admin.php?page=sureforms_form_settings&tab=general-settings' ) );
434 exit;
435 }
436 }
437
438 /**
439 * Open to Upgrade to Pro submenu link in new tab.
440 *
441 * @return void
442 * @since 1.6.1
443 */
444 public function add_upgrade_to_pro_target_attr() {
445 ?>
446 <script type="text/javascript">
447 document.addEventListener('DOMContentLoaded', function () {
448 // Upgrade link handler.
449 // IMPORTANT: If this URL changes, also update it in the `add_upgrade_to_pro` function.
450 const upgradeLink = document.querySelector('a[href*="https://sureforms.com/upgrade"]');
451 if (upgradeLink) {
452 upgradeLink.addEventListener('click', e => {
453 e.preventDefault();
454 window.open(upgradeLink.href, '_blank');
455 });
456 }
457 });
458 </script>
459 <?php
460 }
461
462 /**
463 * Add Upgrade to pro menu item.
464 *
465 * @return void
466 * @since 1.6.1
467 */
468 public function add_upgrade_to_pro() {
469 // The url used here is used as a selector for css to style the upgrade to pro submenu.
470 // If you are changing this url, please make sure to update the css as well.
471 $upgrade_url = Helper::get_sureforms_website_url( 'upgrade', [ 'utm_medium' => 'submenu_link_upgrade' ] );
472
473 add_submenu_page(
474 'sureforms_menu',
475 __( 'Upgrade', 'sureforms' ),
476 __( 'Upgrade', 'sureforms' ),
477 self::$sureforms_page_default_capability,
478 $upgrade_url
479 );
480 }
481
482 /**
483 * Add Quiz empty state submenu page for free users.
484 *
485 * @return void
486 * @since 2.7.0
487 */
488 public function add_quiz_page() {
489 add_submenu_page(
490 'sureforms_menu',
491 __( 'Quiz Entries', 'sureforms' ),
492 __( 'Quizzes', 'sureforms' ) .
493 ' <span style="color:#4ADE80;font-size:9px;font-weight:600;">' .
494 esc_html__( 'New', 'sureforms' ) .
495 '</span>',
496 self::$sureforms_page_default_capability,
497 'sureforms_quiz_entries',
498 [ $this, 'render_quiz_empty_state' ],
499 5
500 );
501 }
502
503 /**
504 * Quiz empty state page callback.
505 *
506 * @return void
507 * @since 2.7.0
508 */
509 public function render_quiz_empty_state() {
510 ?>
511 <div id="srfm-quiz-entries-root" class="srfm-admin-wrapper"></div>
512 <?php
513 }
514
515 /**
516 * Add Survey Reports promotional submenu page for free users.
517 *
518 * @return void
519 * @since 2.8.0
520 */
521 public function add_survey_reports_page() {
522 add_submenu_page(
523 'sureforms_menu',
524 __( 'Survey Reports', 'sureforms' ),
525 __( 'Survey Reports', 'sureforms' ) .
526 ' <span style="color:#4ADE80;font-size:9px;font-weight:600;">' .
527 esc_html__( 'New', 'sureforms' ) .
528 '</span>',
529 self::$sureforms_page_default_capability,
530 'sureforms_survey_reports',
531 [ $this, 'render_survey_empty_state' ],
532 6
533 );
534 }
535
536 /**
537 * Survey empty state page callback.
538 *
539 * @return void
540 * @since 2.8.0
541 */
542 public function render_survey_empty_state() {
543 ?>
544 <div id="srfm-survey-empty-state-root" class="srfm-admin-wrapper"></div>
545 <?php
546 }
547
548 /**
549 * Add Partial Entries promotional submenu page for free users.
550 *
551 * @return void
552 * @since 2.9.0
553 */
554 public function add_partial_entries_page() {
555 add_submenu_page(
556 'sureforms_menu',
557 __( 'Partial Entries', 'sureforms' ),
558 __( 'Partial Entries', 'sureforms' ) .
559 ' <span style="color:#4ADE80;font-size:9px;font-weight:600;">' .
560 esc_html__( 'New', 'sureforms' ) .
561 '</span>',
562 self::$sureforms_page_default_capability,
563 'sureforms_partial_entries',
564 [ $this, 'render_partial_entries_empty_state' ],
565 7
566 );
567 }
568
569 /**
570 * Partial Entries empty state page callback.
571 *
572 * @return void
573 * @since 2.9.0
574 */
575 public function render_partial_entries_empty_state() {
576 ?>
577 <div id="srfm-partial-entries-empty-state-root" class="srfm-admin-wrapper"></div>
578 <?php
579 }
580
581 /**
582 * Add SMTP promotional submenu page.
583 *
584 * @return void
585 * @since 1.7.1
586 */
587 public function add_suremail_page() {
588 add_submenu_page(
589 'sureforms_menu',
590 __( 'SMTP', 'sureforms' ),
591 __( 'SMTP', 'sureforms' ),
592 self::$sureforms_page_default_capability,
593 'sureforms_smtp',
594 [ $this, 'suremail_page_callback' ]
595 );
596
597 // Get the current submenu page.
598 $submenu_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- $_GET['page'] does not provide nonce.
599
600 // Check if SureMail is installed and active.
601 if ( 'sureforms_smtp' === $submenu_page && file_exists( WP_PLUGIN_DIR . '/suremails/suremails.php' ) && is_plugin_active( 'suremails/suremails.php' ) ) {
602 // Plugin is installed and active - redirect to SureMail dashboard.
603 wp_safe_redirect( admin_url( 'options-general.php?page=suremail#/dashboard' ) );
604 exit;
605 }
606 }
607
608 /**
609 * SMTP promotional page callback.
610 *
611 * @return void
612 * @since 1.7.1
613 */
614 public function suremail_page_callback() {
615 ?>
616 <div id="srfm-suremail-container" class="srfm-admin-wrapper"></div>
617 <?php
618 }
619
620 /**
621 * Render Admin Dashboard.
622 *
623 * @return void
624 * @since 0.0.1
625 */
626 public function render_dashboard() {
627 ?>
628 <div id="srfm-dashboard-container" class="srfm-admin-wrapper"></div>
629 <?php
630 }
631
632 /**
633 * Settings page callback.
634 *
635 * @return void
636 * @since 0.0.1
637 */
638 public function settings_page_callback() {
639 ?>
640 <div id="srfm-settings-container" class="srfm-admin-wrapper"></div>
641 <?php
642 }
643
644 /**
645 * Add Learn submenu page.
646 *
647 * @return void
648 * @since 2.5.2
649 */
650 public function add_learn_page() {
651 add_submenu_page(
652 'sureforms_menu',
653 __( 'Learn', 'sureforms' ),
654 __( 'Learn', 'sureforms' ),
655 self::$sureforms_page_default_capability,
656 'sureforms_learn',
657 [ $this, 'render_learn' ]
658 );
659 }
660
661 /**
662 * Learn page callback.
663 *
664 * @return void
665 * @since 2.5.2
666 */
667 public function render_learn() {
668 ?>
669 <div id="srfm-learn-root" class="srfm-admin-wrapper"></div>
670 <?php
671 }
672
673 /**
674 * Add new form menu item.
675 *
676 * @return void
677 * @since 0.0.1
678 */
679 public function add_new_form() {
680 add_submenu_page(
681 'sureforms_menu',
682 __( 'Forms', 'sureforms' ),
683 __( 'Forms', 'sureforms' ),
684 self::$sureforms_page_default_capability,
685 'sureforms_forms',
686 [ $this, 'render_forms' ],
687 1
688 );
689 add_submenu_page(
690 'sureforms_menu',
691 __( 'New Form', 'sureforms' ),
692 __( 'New Form', 'sureforms' ),
693 self::$sureforms_page_default_capability,
694 'add-new-form',
695 [ $this, 'add_new_form_callback' ],
696 2
697 );
698 $entries_hook = add_submenu_page(
699 'sureforms_menu',
700 __( 'Entries', 'sureforms' ),
701 __( 'Entries', 'sureforms' ),
702 self::$sureforms_page_default_capability,
703 SRFM_ENTRIES,
704 [ $this, 'render_entries' ],
705 3
706 );
707
708 add_submenu_page(
709 'sureforms_menu',
710 __( 'Payments', 'sureforms' ),
711 __( 'Payments', 'sureforms' ),
712 self::$sureforms_page_default_capability,
713 SRFM_PAYMENTS,
714 [ $this, 'render_payments' ],
715 4
716 );
717
718 if ( $entries_hook ) {
719 add_action( 'load-' . $entries_hook, [ $this, 'mark_entries_page_visit' ] );
720 }
721 }
722
723 /**
724 * Payments page callback.
725 *
726 * @return void
727 * @since 2.0.0
728 */
729 public function render_payments() {
730 ?>
731 <div id="srfm-payments-react-container" class="srfm-admin-wrapper"></div>
732 <?php
733 }
734
735 /**
736 * Add new form mentu item callback.
737 *
738 * @return void
739 * @since 0.0.1
740 */
741 public function add_new_form_callback() {
742 ?>
743 <div id="srfm-add-new-form-container" class="srfm-admin-wrapper"></div>
744 <?php
745 }
746
747 /**
748 * Forms page callback.
749 *
750 * @return void
751 * @since 2.0.0
752 */
753 public function render_forms() {
754 ?>
755 <div id="srfm-forms-root" class="srfm-admin-wrapper"></div>
756 <?php
757 }
758
759 /**
760 * Entries page callback.
761 *
762 * @since 0.0.13
763 * @since 2.0.0 - Updated the entries UI and the function definition.
764 * @return void
765 */
766 public function render_entries() {
767 echo '<div id="srfm-entries-root"></div>';
768 }
769
770 /**
771 * Add notification badge to SureForms menu when there are new entries.
772 *
773 * @since 1.7.3
774 * @return void
775 */
776 public function maybe_add_entries_badge() {
777 if ( ! Helper::current_user_can() ) {
778 return;
779 }
780
781 // If currently viewing the entries listing page, mark it as visited and skip the badge.
782 if ( isset( $_GET['page'] ) && SRFM_ENTRIES === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only checking the page slug.
783 $this->mark_entries_page_visit();
784 return;
785 }
786
787 $srfm_options = get_option( 'srfm_options', [] );
788 $last_visit = isset( $srfm_options['entries_last_visited'] ) ? absint( $srfm_options['entries_last_visited'] ) : 0;
789 $new_entries = Entries::get_entries_count_after( $last_visit );
790
791 if ( $new_entries <= 0 ) {
792 return;
793 }
794
795 global $menu;
796 foreach ( $menu as $index => $item ) {
797 if ( isset( $item[2] ) && 'sureforms_menu' === $item[2] ) {
798 ob_start();
799 ?>
800 <span class="srfm-update-dot"></span>
801 <?php
802 $dot_html = ob_get_clean();
803 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Adding notifications for menu item.
804 $menu[ $index ][0] .= $dot_html;
805 break;
806 }
807 }
808
809 global $submenu;
810 if ( isset( $submenu['sureforms_menu'] ) ) {
811 foreach ( $submenu['sureforms_menu'] as $index => $sub_item ) {
812 if ( isset( $sub_item[2] ) && SRFM_ENTRIES === $sub_item[2] ) {
813 ob_start();
814 ?>
815 <span class="update-plugins count-<?php echo absint( $new_entries ); ?>">
816 <span class="plugin-count"><?php echo absint( $new_entries ); ?></span>
817 </span>
818 <?php
819 $badge_html = ob_get_clean();
820 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Adding notifications for submenu item.
821 $submenu['sureforms_menu'][ $index ][0] .= $badge_html;
822 break;
823 }
824 }
825 }
826 }
827
828 /**
829 * Mark the user's visit to the entries page.
830 *
831 * @since 1.7.3
832 * @return void
833 */
834 public function mark_entries_page_visit() {
835 if ( Helper::current_user_can() ) {
836 $srfm_options = get_option( 'srfm_options', [] );
837 $srfm_options['entries_last_visited'] = time();
838 \SRFM\Inc\Helper::update_admin_settings_option( 'srfm_options', $srfm_options );
839 }
840 }
841
842 /**
843 * Adds a settings link to the plugin action links on the plugins page.
844 *
845 * @param array $links An array of plugin action links.
846 * @param string $file The plugin file path.
847 * @return array The updated array of plugin action links.
848 * @since 0.0.1
849 */
850 public function add_settings_link( $links, $file ) {
851 if ( 'sureforms/sureforms.php' === $file ) {
852 ob_start();
853 ?>
854 <a href="<?php echo esc_url( admin_url( 'admin.php?page=sureforms_form_settings&tab=general-settings' ) ); ?>">
855 <?php echo esc_html__( 'Settings', 'sureforms' ); ?>
856 </a>
857 <?php
858 $settings_link_html = ob_get_clean();
859 $plugin_links = apply_filters(
860 'sureforms_plugin_action_links',
861 [
862 'sureforms_settings' => $settings_link_html,
863 ]
864 );
865 $links = array_merge( $plugin_links, $links );
866 }
867 return $links;
868 }
869
870 /**
871 * Sureforms block editor styles.
872 *
873 * @since 0.0.1
874 */
875 public function enqueue_styles() {
876 $current_screen = get_current_screen();
877 global $wp_version;
878
879 $file_prefix = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? '' : '.min';
880 $dir_name = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? 'unminified' : 'minified';
881
882 $css_uri = SRFM_URL . 'assets/css/' . $dir_name . '/';
883 $vendor_css_uri = SRFM_URL . 'assets/css/minified/deps/';
884
885 /* RTL */
886 if ( is_rtl() ) {
887 $file_prefix .= '-rtl';
888 }
889
890 // Enqueue editor styles for post and page.
891 if ( SRFM_FORMS_POST_TYPE === $current_screen->post_type ) {
892 wp_enqueue_style( SRFM_SLUG . '-editor', $css_uri . 'backend/editor' . $file_prefix . '.css', [], SRFM_VER );
893 wp_enqueue_style( SRFM_SLUG . '-backend-blocks', $css_uri . 'blocks/default/backend' . $file_prefix . '.css', [], SRFM_VER );
894 wp_enqueue_style( SRFM_SLUG . '-intl', $vendor_css_uri . 'intl/intlTelInput-backend.min.css', [], SRFM_VER );
895 wp_enqueue_style( SRFM_SLUG . '-common', $css_uri . 'common' . $file_prefix . '.css', [], SRFM_VER );
896 wp_enqueue_style( SRFM_SLUG . '-reactQuill', $vendor_css_uri . 'quill/quill.snow.css', [], SRFM_VER );
897 wp_enqueue_style( SRFM_SLUG . '-single-form-modal', $css_uri . 'single-form-setting' . $file_prefix . '.css', [], SRFM_VER );
898
899 // if version is equal to or lower than 6.6.2 then add compatibility css.
900 if ( version_compare( $wp_version, '6.6.2', '<=' ) ) {
901 $srfm_inline_css = '.srfm-settings-modal .srfm-setting-modal-container .components-toggle-control .components-base-control__help{
902 margin-left: 4em;
903 }';
904 wp_add_inline_style( SRFM_SLUG . '-single-form-modal', $srfm_inline_css );
905 }
906 }
907
908 wp_enqueue_style( SRFM_SLUG . '-form-selector', $css_uri . 'srfm-form-selector' . $file_prefix . '.css', [], SRFM_VER );
909 wp_enqueue_style( SRFM_SLUG . '-common-editor', SRFM_URL . 'assets/build/common-editor.css', [], SRFM_VER, 'all' );
910 }
911
912 /**
913 * Get Breadcrumbs for current page.
914 *
915 * @since 0.0.1
916 * @return array Breadcrumbs Array.
917 */
918 public function get_breadcrumbs_for_current_page() {
919 global $post, $pagenow;
920 $breadcrumbs = [];
921
922 if ( 'admin.php' === $pagenow && isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We don't need nonce verification here.
923 $page_title = get_admin_page_title();
924 $breadcrumbs[] = [
925 'title' => $page_title,
926 'link' => '',
927 ];
928 } elseif ( $post && in_array( $pagenow, [ 'post.php', 'post-new.php', 'edit.php' ], true ) ) {
929 $post_type_obj = get_post_type_object( get_post_type() );
930 if ( $post_type_obj ) {
931 $post_type_plural = $post_type_obj->labels->name;
932 $breadcrumbs[] = [
933 'title' => $post_type_plural,
934 'link' => admin_url( 'edit.php?post_type=' . $post_type_obj->name ),
935 ];
936
937 if ( 'edit.php' === $pagenow && ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We don't need nonce verification here.
938 $breadcrumbs[ count( $breadcrumbs ) - 1 ]['link'] = '';
939 } else {
940 $breadcrumbs[] = [
941 /* Translators: Post Title. */
942 'title' => sprintf( __( 'Edit %1$s', 'sureforms' ), get_the_title() ),
943 'link' => get_edit_post_link( $post->ID ),
944 ];
945 }
946 }
947 } else {
948 $current_screen = get_current_screen();
949 if ( $current_screen && 'sureforms_form' === $current_screen->post_type ) {
950 $breadcrumbs[] = [
951 'title' => 'Forms',
952 'link' => '',
953 ];
954 } else {
955 $breadcrumbs[] = [
956 'title' => '',
957 'link' => '',
958 ];
959 }
960 }
961
962 return $breadcrumbs;
963 }
964
965 /**
966 * Enqueue Admin Scripts.
967 *
968 * @return void
969 * @since 0.0.1
970 */
971 public function enqueue_scripts() {
972 $current_screen = get_current_screen();
973 global $wp_version;
974
975 $file_prefix = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? '' : '.min';
976 $dir_name = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? 'unminified' : 'minified';
977 $css_uri = SRFM_URL . 'assets/css/' . $dir_name . '/';
978 $is_rtl = is_rtl();
979 $rtl = $is_rtl ? '-rtl' : '';
980
981 /**
982 * List of the handles in which we need to add translation compatibility.
983 */
984 $script_translations_handlers = [];
985 $onboarding_instance = Onboarding::get_instance();
986 $current_user = wp_get_current_user();
987
988 $localization_data = [
989 'site_url' => get_site_url(),
990 'current_user_login' => $current_user->user_login ?? '',
991 'website_lead_details' => [
992 'first_name' => $current_user->first_name ?? '',
993 'last_name' => $current_user->last_name ?? '',
994 'email' => $current_user->user_email ?? '',
995 ],
996 'breadcrumbs' => $this->get_breadcrumbs_for_current_page(),
997 'sureforms_dashboard_url' => admin_url( '/admin.php?page=sureforms_menu' ),
998 'plugin_version' => SRFM_VER,
999 'global_settings_nonce' => Helper::current_user_can() ? wp_create_nonce( 'wp_rest' ) : '',
1000 'is_pro_active' => Helper::has_pro(),
1001 'is_first_form_created' => self::is_first_form_created(),
1002 'check_three_days_threshold' => self::check_first_form_creation_threshold(),
1003 'check_eight_days_threshold' => self::check_first_form_creation_threshold( 8 ),
1004 'pro_plugin_version' => Helper::has_pro() ? SRFM_PRO_VER : '',
1005 'pro_plugin_name' => Helper::has_pro() && defined( 'SRFM_PRO_PRODUCT' ) ? SRFM_PRO_PRODUCT : 'SureForms Pro',
1006 'sureforms_pricing_page' => Helper::get_sureforms_website_url( 'pricing' ),
1007 'field_spacing_vars' => Helper::get_css_vars(),
1008 'is_ver_lower_than_6_7' => version_compare( $wp_version, '6.6.2', '<=' ),
1009 'integrations' => Helper::sureforms_get_integration(),
1010 'rotating_plugin_banner' => Helper::get_rotating_plugin_banner(),
1011 'ajax_url' => admin_url( 'admin-ajax.php' ),
1012 'sf_plugin_manager_nonce' => wp_create_nonce( 'sf_plugin_manager_nonce' ),
1013 'plugin_installer_nonce' => wp_create_nonce( 'updates' ),
1014 'plugin_activating_text' => __( 'Activating...', 'sureforms' ),
1015 'plugin_activated_text' => __( 'Activated', 'sureforms' ),
1016 'plugin_activate_text' => __( 'Activate', 'sureforms' ),
1017 'plugin_installing_text' => __( 'Installing...', 'sureforms' ),
1018 'plugin_installed_text' => __( 'Installed', 'sureforms' ),
1019 'privacy_policy_url' => Helper::get_sureforms_website_url( 'privacy-policy/' ),
1020 'is_rtl' => $is_rtl,
1021 'onboarding_completed' => method_exists( $onboarding_instance, 'get_onboarding_status' ) ? $onboarding_instance->get_onboarding_status() : false,
1022 'migration_banner_dismissed' => method_exists( $onboarding_instance, 'is_migration_banner_dismissed' ) ? $onboarding_instance->is_migration_banner_dismissed() : false,
1023 'migration_settings_url' => admin_url( 'admin.php?page=sureforms_form_settings&tab=migration-settings' ),
1024 'onboarding_redirect' => isset( $_GET['srfm-activation-redirect'] ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required for the activation redirection.
1025 'pointer_nonce' => wp_create_nonce( 'sureforms_pointer_action' ),
1026 'general_settings_url' => admin_url( '/options-general.php' ),
1027 'additional_header_nav_items' => [],
1028 // Smart tags for the Global Defaults email notification fields.
1029 // srfm_block_data is only available in the block editor; these keys
1030 // make the same data accessible on the settings page via srfm_admin.
1031 'smart_tags_array' => Smart_Tags::smart_tag_list(),
1032 'smart_tags_array_email' => Smart_Tags::email_smart_tag_list(),
1033 // Default confirmation message HTML (icon + heading + text) used as
1034 // the initial React state before the settings API response arrives.
1035 'default_confirmation_message' => Global_Settings::get_default_confirmation_message(),
1036 'payments' => apply_filters(
1037 'srfm_admin_localize_payments_data',
1038 [
1039 'stripe_connected' => Stripe_Helper::is_stripe_connected(),
1040 'stripe_mode' => Stripe_Helper::get_stripe_mode(),
1041 'stripe_connect_url' => Stripe_Helper::get_stripe_settings_url(),
1042 'currencies_data' => Payment_Helper::get_all_currencies_data(),
1043 'zero_decimal_currencies' => Payment_Helper::get_zero_decimal_currencies(),
1044 'webhook_url' => Stripe_Helper::get_webhook_url(),
1045 'webhook_test_connected' => Stripe_Helper::is_webhook_configured( 'test', true ),
1046 'webhook_live_connected' => Stripe_Helper::is_webhook_configured( 'live', true ),
1047 'is_transaction_present' => Stripe_Helper::is_transaction_present(),
1048 'payment_currency' => Payment_Helper::get_currency(),
1049 'currency_sign_position' => Payment_Helper::get_currency_sign_position(),
1050 ]
1051 ),
1052 'mcp_adapter_status' => file_exists( WP_PLUGIN_DIR . '/mcp-adapter/mcp-adapter.php' )
1053 ? ( is_plugin_active( 'mcp-adapter/mcp-adapter.php' ) ? 'active' : 'installed' )
1054 : 'not_installed',
1055 'mcp_endpoint_url' => esc_url_raw( rest_url( 'sureforms/v1/mcp' ) ),
1056 ];
1057
1058 $is_screen_sureforms_menu = Helper::validate_request_context( 'sureforms_menu', 'page' );
1059 $is_screen_add_new_form = Helper::validate_request_context( 'add-new-form', 'page' );
1060 $is_screen_sureforms_forms = Helper::validate_request_context( 'sureforms_forms', 'page' );
1061 $is_screen_sureforms_form_settings = Helper::validate_request_context( 'sureforms_form_settings', 'page' );
1062 $is_screen_sureforms_payments = Helper::validate_request_context( 'sureforms_payments', 'page' );
1063 $is_screen_sureforms_entries = Helper::validate_request_context( SRFM_ENTRIES, 'page' );
1064 $is_screen_sureforms_learn = Helper::validate_request_context( 'sureforms_learn', 'page' );
1065 $is_screen_quiz_empty_state = Helper::validate_request_context( 'sureforms_quiz_entries', 'page' );
1066 $is_screen_survey_empty_state = Helper::validate_request_context( 'sureforms_survey_reports', 'page' );
1067 $is_screen_partial_entries_empty_state = Helper::validate_request_context( 'sureforms_partial_entries', 'page' );
1068 $is_post_type_sureforms_form = SRFM_FORMS_POST_TYPE === $current_screen->post_type;
1069
1070 /**
1071 * Check if the current screen is the SureForms Menu and AI Auth Email is present then we will add user type as registered.
1072 * Compatibility with existing UI code that checks for this condition.
1073 */
1074 if ( $is_screen_sureforms_menu ) {
1075 // If email is stored send the user type as registered else non-registered.
1076 $localization_data['srfm_ai_details'] = [
1077 'type' => ! empty( get_option( 'srfm_ai_auth_user_email' ) ) ? 'registered' : 'non-registered',
1078 ];
1079 }
1080
1081 // Add the Quizzes and Survey Reports nav items when pro is not active.
1082 if ( ! Helper::has_pro() ) {
1083 $localization_data['additional_header_nav_items'][] = [
1084 'slug' => 'sureforms_quiz_entries',
1085 'text' => __( 'Quizzes', 'sureforms' ),
1086 'link' => admin_url( 'admin.php?page=sureforms_quiz_entries' ),
1087 ];
1088 $localization_data['additional_header_nav_items'][] = [
1089 'slug' => 'sureforms_survey_reports',
1090 'text' => __( 'Survey Reports', 'sureforms' ),
1091 'link' => admin_url( 'admin.php?page=sureforms_survey_reports' ),
1092 ];
1093 $localization_data['additional_header_nav_items'][] = [
1094 'slug' => 'sureforms_partial_entries',
1095 'text' => __( 'Partial Entries', 'sureforms' ),
1096 'link' => admin_url( 'admin.php?page=sureforms_partial_entries' ),
1097 ];
1098 }
1099
1100 $is_sureforms_screen = $is_screen_sureforms_menu || $is_post_type_sureforms_form || $is_screen_add_new_form || $is_screen_sureforms_forms || $is_screen_sureforms_form_settings || $is_screen_sureforms_entries || $is_screen_sureforms_payments || $is_screen_sureforms_learn || $is_screen_quiz_empty_state || $is_screen_survey_empty_state || $is_screen_partial_entries_empty_state;
1101
1102 /**
1103 * Filter to allow extending the SureForms dashboard screen check.
1104 *
1105 * @since 2.6.0
1106 *
1107 * @param bool $is_sureforms_screen Whether the current screen is a SureForms dashboard screen.
1108 */
1109 $is_sureforms_screen = apply_filters( 'srfm_is_dashboard_screen', $is_sureforms_screen );
1110
1111 if ( $is_sureforms_screen ) {
1112 $asset_handle = '-dashboard';
1113
1114 wp_enqueue_style( SRFM_SLUG . $asset_handle . '-font', 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap', [], SRFM_VER );
1115
1116 $script_asset_path = SRFM_DIR . 'assets/build/dashboard.asset.php';
1117 $script_info = file_exists( $script_asset_path )
1118 ? include $script_asset_path
1119 : [
1120 'dependencies' => [],
1121 'version' => SRFM_VER,
1122 ];
1123 wp_enqueue_script( SRFM_SLUG . $asset_handle, SRFM_URL . 'assets/build/dashboard.js', $script_info['dependencies'], SRFM_VER, true );
1124
1125 wp_localize_script( SRFM_SLUG . $asset_handle, 'scIcons', [ 'path' => SRFM_URL . 'assets/build/icon-assets' ] );
1126
1127 $script_translations_handlers[] = SRFM_SLUG . $asset_handle;
1128
1129 if ( class_exists( 'SRFM_PRO\Admin\Licensing' ) ) {
1130 $license_active = \SRFM_PRO\Admin\Licensing::is_license_active();
1131 $localization_data['is_license_active'] = $license_active;
1132
1133 // Updating current licensing status.
1134 $srfm_pro_license_status = get_option( 'srfm_pro_license_status', '' );
1135 $current_license_status = $license_active ? 'licensed' : 'unlicensed';
1136 if ( $current_license_status !== $srfm_pro_license_status ) {
1137 update_option( 'srfm_pro_license_status', $current_license_status );
1138 }
1139 }
1140
1141 $localization_data['security_settings_url'] = admin_url( '/admin.php?page=sureforms_form_settings&tab=security-settings&subpage=recaptcha' );
1142 $localization_data['integration_settings_url'] = admin_url( '/admin.php?page=sureforms_form_settings&tab=integration-settings' );
1143 wp_localize_script(
1144 SRFM_SLUG . $asset_handle,
1145 SRFM_SLUG . '_admin',
1146 apply_filters(
1147 SRFM_SLUG . '_admin_filter',
1148 $localization_data
1149 )
1150 );
1151 wp_enqueue_style( SRFM_SLUG . '-dashboard', SRFM_URL . 'assets/build/dashboard.css', [], SRFM_VER, 'all' );
1152 }
1153
1154 if ( $is_screen_sureforms_form_settings || $is_screen_sureforms_forms ) {
1155 wp_enqueue_style( SRFM_SLUG . '-settings', $css_uri . 'backend/settings' . $file_prefix . $rtl . '.css', [], SRFM_VER );
1156 }
1157
1158 // Enqueue styles for the entries page.
1159 if ( $is_screen_sureforms_entries ) {
1160 $asset_handle = '-entries';
1161 wp_enqueue_script( SRFM_SLUG . $asset_handle, SRFM_URL . 'assets/build/entries.js', $script_info['dependencies'], SRFM_VER, true );
1162
1163 wp_localize_script(
1164 SRFM_SLUG . $asset_handle,
1165 SRFM_SLUG . '_admin',
1166 apply_filters(
1167 SRFM_SLUG . '_admin_filter',
1168 $localization_data
1169 )
1170 );
1171 $script_translations_handlers[] = SRFM_SLUG . $asset_handle;
1172 }
1173
1174 // Enqueue scripts for the learn page.
1175 if ( $is_screen_sureforms_learn ) {
1176 $asset_handle = '-learn';
1177 wp_enqueue_script( SRFM_SLUG . $asset_handle, SRFM_URL . 'assets/build/learn.js', $script_info['dependencies'], SRFM_VER, true );
1178
1179 wp_localize_script(
1180 SRFM_SLUG . $asset_handle,
1181 SRFM_SLUG . '_admin',
1182 apply_filters(
1183 SRFM_SLUG . '_admin_filter',
1184 $localization_data
1185 )
1186 );
1187 $script_translations_handlers[] = SRFM_SLUG . $asset_handle;
1188 }
1189
1190 // Enqueue scripts for the forms page.
1191 if ( $is_screen_sureforms_forms ) {
1192 $asset_handle = '-forms';
1193
1194 $script_asset_path = SRFM_DIR . 'assets/build/forms.asset.php';
1195 $script_info = file_exists( $script_asset_path )
1196 ? include $script_asset_path
1197 : [
1198 'dependencies' => [],
1199 'version' => SRFM_VER,
1200 ];
1201
1202 wp_enqueue_script( SRFM_SLUG . $asset_handle, SRFM_URL . 'assets/build/forms.js', $script_info['dependencies'], SRFM_VER, true );
1203 wp_localize_script(
1204 SRFM_SLUG . $asset_handle,
1205 SRFM_SLUG . '_admin',
1206 apply_filters(
1207 SRFM_SLUG . '_admin_filter',
1208 $localization_data
1209 )
1210 );
1211 wp_enqueue_style( SRFM_SLUG . $asset_handle, SRFM_URL . 'assets/build/forms.css', [], SRFM_VER, 'all' );
1212
1213 $script_translations_handlers[] = SRFM_SLUG . $asset_handle;
1214 }
1215
1216 // Enqueue scripts for the SureMail promotional page.
1217 $is_screen_sureforms_smtp = Helper::validate_request_context( 'sureforms_smtp', 'page' );
1218 if ( $is_screen_sureforms_smtp ) {
1219 $asset_handle = 'suremail';
1220
1221 $script_asset_path = SRFM_DIR . 'assets/build/' . $asset_handle . '.asset.php';
1222 $script_info = file_exists( $script_asset_path )
1223 ? include $script_asset_path
1224 : [
1225 'dependencies' => [],
1226 'version' => SRFM_VER,
1227 ];
1228
1229 wp_enqueue_script( SRFM_SLUG . '-suremail', SRFM_URL . 'assets/build/' . $asset_handle . '.js', $script_info['dependencies'], SRFM_VER, true );
1230 wp_enqueue_style( SRFM_SLUG . '-suremail', SRFM_URL . 'assets/build/suremail.css', [], SRFM_VER, 'all' );
1231
1232 // Localize script for SureMail page.
1233 $suremail_localization_data = [
1234 'ajax_url' => admin_url( 'admin-ajax.php' ),
1235 'admin_url' => admin_url(),
1236 'suremail_url' => 'https://sureforms.com/suremail/',
1237 'plugin_installer_nonce' => wp_create_nonce( 'updates' ),
1238 'sfPluginManagerNonce' => wp_create_nonce( 'sf_plugin_manager_nonce' ),
1239 'suremail_status' => file_exists( WP_PLUGIN_DIR . '/suremails/suremails.php' )
1240 ? ( is_plugin_active( 'suremails/suremails.php' ) ? 'active' : 'installed' )
1241 : 'not_installed',
1242 ];
1243
1244 wp_localize_script(
1245 SRFM_SLUG . '-suremail',
1246 SRFM_SLUG . '_admin',
1247 apply_filters(
1248 SRFM_SLUG . '_suremail_admin_filter',
1249 $suremail_localization_data
1250 )
1251 );
1252
1253 $script_translations_handlers[] = SRFM_SLUG . '-suremail';
1254 }
1255
1256 // Enqueue scripts for the Quiz empty state page (free users only).
1257 if ( $is_screen_quiz_empty_state && ! Helper::has_pro() ) {
1258 $asset_handle = 'quizEmptyState';
1259
1260 $script_asset_path = SRFM_DIR . 'assets/build/' . $asset_handle . '.asset.php';
1261 $script_info = file_exists( $script_asset_path )
1262 ? include $script_asset_path
1263 : [
1264 'dependencies' => [],
1265 'version' => SRFM_VER,
1266 ];
1267
1268 wp_enqueue_script( SRFM_SLUG . '-quiz-empty-state', SRFM_URL . 'assets/build/' . $asset_handle . '.js', $script_info['dependencies'], SRFM_VER, true );
1269 wp_enqueue_style( SRFM_SLUG . '-quiz-empty-state', SRFM_URL . 'assets/build/' . $asset_handle . '.css', [], SRFM_VER, 'all' );
1270
1271 $script_translations_handlers[] = SRFM_SLUG . '-quiz-empty-state';
1272 }
1273
1274 // Enqueue scripts for the Survey Reports empty state page (free users only).
1275 if ( $is_screen_survey_empty_state && ! Helper::has_pro() ) {
1276 $asset_handle = 'surveyEmptyState';
1277
1278 $script_asset_path = SRFM_DIR . 'assets/build/' . $asset_handle . '.asset.php';
1279 $script_info = file_exists( $script_asset_path )
1280 ? include $script_asset_path
1281 : [
1282 'dependencies' => [],
1283 'version' => SRFM_VER,
1284 ];
1285
1286 wp_enqueue_script( SRFM_SLUG . '-survey-empty-state', SRFM_URL . 'assets/build/' . $asset_handle . '.js', $script_info['dependencies'], SRFM_VER, true );
1287 wp_enqueue_style( SRFM_SLUG . '-survey-empty-state', SRFM_URL . 'assets/build/' . $asset_handle . '.css', [], SRFM_VER, 'all' );
1288
1289 $script_translations_handlers[] = SRFM_SLUG . '-survey-empty-state';
1290 }
1291
1292 // Enqueue scripts for the Partial Entries empty state page (free users only).
1293 if ( $is_screen_partial_entries_empty_state && ! Helper::has_pro() ) {
1294 $asset_handle = 'partialEntriesEmptyState';
1295
1296 $script_asset_path = SRFM_DIR . 'assets/build/' . $asset_handle . '.asset.php';
1297 $script_info = file_exists( $script_asset_path )
1298 ? include $script_asset_path
1299 : [
1300 'dependencies' => [],
1301 'version' => SRFM_VER,
1302 ];
1303
1304 wp_enqueue_script( SRFM_SLUG . '-partial-entries-empty-state', SRFM_URL . 'assets/build/' . $asset_handle . '.js', $script_info['dependencies'], SRFM_VER, true );
1305 wp_enqueue_style( SRFM_SLUG . '-partial-entries-empty-state', SRFM_URL . 'assets/build/' . $asset_handle . '.css', [], SRFM_VER, 'all' );
1306
1307 $script_translations_handlers[] = SRFM_SLUG . '-partial-entries-empty-state';
1308 }
1309
1310 // Admin Submenu Styles.
1311 wp_enqueue_style( SRFM_SLUG . '-admin', $css_uri . 'backend/admin' . $file_prefix . $rtl . '.css', [], SRFM_VER );
1312
1313 if ( $is_screen_sureforms_form_settings ) {
1314 $asset_handle = 'settings';
1315
1316 $script_asset_path = SRFM_DIR . 'assets/build/' . $asset_handle . '.asset.php';
1317 $script_info = file_exists( $script_asset_path )
1318 ? include $script_asset_path
1319 : [
1320 'dependencies' => [],
1321 'version' => SRFM_VER,
1322 ];
1323
1324 wp_enqueue_script( SRFM_SLUG . '-settings', SRFM_URL . 'assets/build/' . $asset_handle . '.js', $script_info['dependencies'], SRFM_VER, true );
1325 wp_localize_script(
1326 SRFM_SLUG . '-settings',
1327 SRFM_SLUG . '_admin',
1328 apply_filters(
1329 SRFM_SLUG . '_admin_filter',
1330 $localization_data
1331 )
1332 );
1333
1334 // Enqueue Tailwind and Quill editor styles for the settings page.
1335 wp_enqueue_style( SRFM_SLUG . '-settings-build', SRFM_URL . 'assets/build/settings.css', [], SRFM_VER, 'all' );
1336 wp_enqueue_style( SRFM_SLUG . '-reactQuill', SRFM_URL . 'assets/css/minified/deps/quill/quill.snow.css', [], SRFM_VER );
1337
1338 $script_translations_handlers[] = SRFM_SLUG . '-settings';
1339 }
1340
1341 if ( $is_screen_add_new_form ) {
1342 wp_enqueue_style( SRFM_SLUG . '-template-picker', $css_uri . 'template-picker' . $file_prefix . $rtl . '.css', [], SRFM_VER );
1343
1344 $sureforms_admin = 'templatePicker';
1345
1346 $script_asset_path = SRFM_DIR . 'assets/build/' . $sureforms_admin . '.asset.php';
1347 $script_info = file_exists( $script_asset_path )
1348 ? include $script_asset_path
1349 : [
1350 'dependencies' => [],
1351 'version' => SRFM_VER,
1352 ];
1353 wp_enqueue_script( SRFM_SLUG . '-template-picker', SRFM_URL . 'assets/build/' . $sureforms_admin . '.js', $script_info['dependencies'], SRFM_VER, true );
1354
1355 wp_localize_script(
1356 SRFM_SLUG . '-template-picker',
1357 SRFM_SLUG . '_admin',
1358 [
1359 'site_url' => get_site_url(),
1360 'plugin_url' => SRFM_URL,
1361 'admin_url' => admin_url( 'admin.php' ),
1362 'new_template_picker_base_url' => admin_url( 'post-new.php?post_type=sureforms_form' ),
1363 'capability' => Helper::current_user_can(),
1364 'template_picker_nonce' => Helper::current_user_can() ? wp_create_nonce( 'wp_rest' ) : '',
1365 'is_pro_active' => Helper::has_pro(),
1366 'srfm_ai_usage_details' => AI_Helper::get_current_usage_details(),
1367 'is_pro_license_active' => AI_Helper::is_pro_license_active(),
1368 'srfm_ai_auth_user_email' => get_option( 'srfm_ai_auth_user_email' ),
1369 'pricing_page_url' => Helper::get_sureforms_website_url( 'pricing' ),
1370 'licensing_nonce' => wp_create_nonce( 'srfm_pro_licensing_nonce' ),
1371 ]
1372 );
1373
1374 $script_translations_handlers[] = SRFM_SLUG . '-template-picker';
1375 }
1376 // Quick action sidebar.
1377 $default_allowed_quick_sidebar_blocks = apply_filters(
1378 'srfm_quick_sidebar_allowed_blocks',
1379 [
1380 'srfm/input',
1381 'srfm/email',
1382 'srfm/textarea',
1383 'srfm/checkbox',
1384 'srfm/number',
1385 'srfm/inline-button',
1386 'srfm/advanced-heading',
1387 'srfm/payment',
1388 ]
1389 );
1390 if ( ! is_array( $default_allowed_quick_sidebar_blocks ) ) {
1391 $default_allowed_quick_sidebar_blocks = [];
1392 }
1393
1394 $srfm_enable_quick_action_sidebar = get_option( 'srfm_enable_quick_action_sidebar' );
1395 if ( ! $srfm_enable_quick_action_sidebar ) {
1396 $srfm_enable_quick_action_sidebar = 'disabled';
1397 }
1398 $quick_sidebar_allowed_blocks = get_option( 'srfm_quick_sidebar_allowed_blocks' );
1399 $quick_sidebar_allowed_blocks = ! empty( $quick_sidebar_allowed_blocks ) && is_array( $quick_sidebar_allowed_blocks ) ? $quick_sidebar_allowed_blocks : $default_allowed_quick_sidebar_blocks;
1400 $srfm_ajax_nonce = wp_create_nonce( 'srfm_ajax_nonce' );
1401
1402 if ( Helper::is_sureforms_admin_page() ) {
1403 wp_enqueue_script( SRFM_SLUG . '-quick-action-siderbar', SRFM_URL . 'assets/build/quickActionSidebar.js', [], SRFM_VER, true );
1404 wp_localize_script(
1405 SRFM_SLUG . '-quick-action-siderbar',
1406 SRFM_SLUG . '_quick_sidebar_blocks',
1407 [
1408 'allowed_blocks' => $quick_sidebar_allowed_blocks,
1409 'srfm_enable_quick_action_sidebar' => $srfm_enable_quick_action_sidebar,
1410 'srfm_ajax_nonce' => $srfm_ajax_nonce,
1411 'srfm_ajax_url' => admin_url( 'admin-ajax.php' ),
1412 ]
1413 );
1414
1415 $script_translations_handlers[] = SRFM_SLUG . '-quick-action-siderbar';
1416 }
1417
1418 /**
1419 * Enqueuing SureTriggers Integration script.
1420 * This script loads suretriggers iframe in Intergations tab.
1421 */
1422 if ( $is_post_type_sureforms_form ) {
1423 wp_enqueue_script( SRFM_SLUG . '-suretriggers-integration', SRFM_SURETRIGGERS_INTEGRATION_BASE_URL . 'js/v2/embed.js', [], SRFM_VER, true );
1424 }
1425
1426 // Check $script_translations_handlers is not empty before calling the function.
1427 if ( ! empty( $script_translations_handlers ) ) {
1428 // Remove duplicates values from the array.
1429 $script_translations_handlers = array_unique( $script_translations_handlers );
1430
1431 foreach ( $script_translations_handlers as $script_handle ) {
1432 Helper::register_script_translations( $script_handle );
1433 }
1434 }
1435 }
1436
1437 /**
1438 * Form Template Picker Admin Body Classes
1439 * WordPress sometimes translates class names in the admin body tag, which can result in
1440 * incorrect or missing class names when rendering the admin pages. This function ensures
1441 * that essential class names are manually added to the body tag to maintain proper functionality.
1442 *
1443 * @since 0.0.1
1444 * @param string $classes Space separated class string.
1445 */
1446 public function admin_template_picker_body_class( $classes = '' ) {
1447 // Define an associative array of class names and their corresponding conditions.
1448 // Each condition checks whether a specific request context matches.
1449 $srfm_classes = [
1450 'sureforms_page_sureforms_entries' => Helper::validate_request_context( SRFM_ENTRIES, 'page' ),
1451 'sureforms_page_sureforms_form_settings' => Helper::validate_request_context( 'sureforms_form_settings', 'page' ),
1452 'srfm-template-picker' => Helper::validate_request_context( 'add-new-form', 'page' ),
1453 ];
1454
1455 $add_srfm_classes = '';
1456
1457 // Loop through the defined classes and conditions.
1458 foreach ( $srfm_classes as $class => $condition ) {
1459 // Check if the condition evaluates to true.
1460 if ( $condition ) {
1461 // Append the class to the existing classes string, followed by a space.
1462 $add_srfm_classes .= empty( $add_srfm_classes ) ? $class : ' ' . $class;
1463 }
1464 }
1465
1466 // Append the new classes to the existing classes string.
1467 if ( ! empty( $add_srfm_classes ) ) {
1468 $classes .= ' ' . $add_srfm_classes;
1469 }
1470
1471 // Return the updated list of classes.
1472 return $classes;
1473 }
1474
1475 /**
1476 * Disable spectra's quick action bar in sureforms CPT.
1477 *
1478 * @param string $status current status of the quick action bar.
1479 * @since 0.0.2
1480 * @return string
1481 */
1482 public function restrict_spectra_quick_action_bar( $status ) {
1483 $screen = get_current_screen();
1484 if ( 'disabled' !== $status && isset( $screen->id ) && 'sureforms_form' === $screen->id ) {
1485 $status = 'disabled';
1486 }
1487
1488 return $status;
1489 }
1490
1491 /**
1492 * Register Pro compatibility notices early for React pages.
1493 *
1494 * This method runs on admin_init (priority 5) to ensure notices are
1495 * registered BEFORE admin_enqueue_scripts, so they're available when
1496 * wp_localize_script runs.
1497 *
1498 * Hooked - admin_init (priority 5)
1499 *
1500 * @return void
1501 * @since 2.5.0
1502 */
1503 public function register_pro_compatibility_notices() {
1504 // Early exit if Pro is not active, user lacks permissions, or Notice_Manager is unavailable.
1505 if ( ! Helper::has_pro() || ! Helper::current_user_can() || ! class_exists( 'SRFM\Admin\Notice_Manager' ) ) {
1506 return;
1507 }
1508
1509 // Register version outdated notice for React pages.
1510 if ( ! version_compare( SRFM_PRO_VER, SRFM_PRO_RECOMMENDED_VER, '>=' ) ) {
1511 $pro_plugin_name = defined( 'SRFM_PRO_PRODUCT' ) ? SRFM_PRO_PRODUCT : 'SureForms Pro';
1512 $react_outdated_message = sprintf(
1513 // translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version.
1514 esc_html__( 'SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version.', 'sureforms' ),
1515 esc_html( SRFM_VER ),
1516 esc_html( $pro_plugin_name ),
1517 esc_html( SRFM_PRO_RECOMMENDED_VER )
1518 );
1519
1520 \SRFM\Admin\Notice_Manager::register_notice(
1521 [
1522 'id' => 'sureforms-pro-version-outdated',
1523 'variant' => 'warning',
1524 'message' => $react_outdated_message,
1525 'actions' => [
1526 [
1527 'label' => esc_html__( 'Update Now', 'sureforms' ),
1528 'url' => admin_url( 'update-core.php' ),
1529 'variant' => 'primary',
1530 ],
1531 ],
1532 'pages' => [ 'all' ],
1533 ]
1534 );
1535 }
1536 }
1537
1538 /**
1539 * Admin Notice Callback if sureforms pro is out of date.
1540 *
1541 * Hooked - admin_notices
1542 *
1543 * @return void
1544 * @since 1.0.4
1545 */
1546 public function srfm_pro_version_compatibility() {
1547 if ( ! Helper::has_pro() ) {
1548 return;
1549 }
1550
1551 if ( empty( get_current_screen() ) ) {
1552 return;
1553 }
1554
1555 if ( ! Helper::current_user_can() ) {
1556 return;
1557 }
1558
1559 $srfm_pro_license_status = get_option( 'srfm_pro_license_status', '' );
1560 /**
1561 * If the license status is not set then get the license status and update the option accordingly.
1562 * This will be executed only once. Subsequently, the option status is updated by the licensing class on license activation or deactivation.
1563 */
1564 if ( empty( $srfm_pro_license_status ) && class_exists( 'SRFM_PRO\Admin\Licensing' ) ) {
1565 $srfm_pro_license_status = \SRFM_PRO\Admin\Licensing::is_license_active() ? 'licensed' : 'unlicensed';
1566 update_option( 'srfm_pro_license_status', $srfm_pro_license_status );
1567 }
1568
1569 $pro_plugin_name = defined( 'SRFM_PRO_PRODUCT' ) ? SRFM_PRO_PRODUCT : 'SureForms Pro';
1570 $message = '';
1571 $url = admin_url( 'admin.php?page=sureforms_form_settings&tab=account-settings' );
1572 if ( 'unlicensed' === $srfm_pro_license_status ) {
1573 ob_start();
1574 ?>
1575 <p>
1576 <?php
1577 printf(
1578 // translators: %1$s: Opening anchor tag with URL, %2$s: Closing anchor tag, %3$s: SureForms Pro Plugin Name.
1579 esc_html__( 'Please %1$sactivate%2$s your copy of %3$s to get new features, access support, receive update notifications, and more.', 'sureforms' ),
1580 '<a href="' . esc_url( $url ) . '">',
1581 '</a>',
1582 '<i>' . esc_html( $pro_plugin_name ) . '</i>'
1583 );
1584 ?>
1585 </p>
1586 <?php
1587 $message = ob_get_clean();
1588 }
1589
1590 if ( ! version_compare( SRFM_PRO_VER, SRFM_PRO_RECOMMENDED_VER, '>=' ) ) {
1591 ob_start();
1592 ?>
1593 <p>
1594 <?php
1595 printf(
1596 // translators: %1$s: SureForms version, %2$s: SureForms Pro Plugin Name, %3$s: SureForms Pro Version, %4$s: Anchor tag open, %5$s: Closing anchor tag.
1597 esc_html__( 'SureForms %1$s requires minimum %2$s %3$s to work properly. Please update to the latest version from %4$shere%5$s.', 'sureforms' ),
1598 esc_html( SRFM_VER ),
1599 esc_html( $pro_plugin_name ),
1600 esc_html( SRFM_PRO_RECOMMENDED_VER ),
1601 '<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">',
1602 '</a>'
1603 );
1604 ?>
1605 </p>
1606 <?php
1607 $message .= ob_get_clean();
1608 }
1609
1610 if ( ! empty( $message ) ) {
1611 // Phpcs ignore comment is required as $message variable is already escaped.
1612 ?>
1613 <div class="notice notice-warning"><?php echo wp_kses_post( $message ); ?></div>
1614 <?php
1615 }
1616 }
1617
1618 /**
1619 * Display a notice to the user about providing a review.
1620 *
1621 * @since 2.5.2
1622 * @return void
1623 */
1624 public function display_srfm_rating_notice() {
1625 // Only show to admins.
1626 if ( ! Helper::current_user_can() ) {
1627 return;
1628 }
1629
1630 // Allow the notice to be disabled.
1631 if ( ! apply_filters( 'srfm_show_rating_notice', true ) ) {
1632 return;
1633 }
1634
1635 Astra_Notices::add_notice(
1636 [
1637 'id' => 'srfm-plugin-review-notice',
1638 'type' => '',
1639 'message' => $this->build_notice_markup(
1640 esc_html__( 'Amazing! SureForms is powering your forms and submissions - let\'s keep growing together!', 'sureforms' ),
1641 esc_html__( 'If SureForms has been helpful, would you mind taking a moment to leave a 5-star review on WordPress.org?', 'sureforms' ),
1642 esc_url( 'https://wordpress.org/support/plugin/sureforms/reviews/?filter=5#new-post' ),
1643 esc_html__( 'Rate SureForms', 'sureforms' ),
1644 esc_html__( 'Maybe later', 'sureforms' ),
1645 esc_html__( 'I already did', 'sureforms' ),
1646 WEEK_IN_SECONDS,
1647 true
1648 ),
1649 'repeat-notice-after' => WEEK_IN_SECONDS,
1650 'show_if' => $this->maybe_display_rating_notice(),
1651 'display-with-other-notices' => true,
1652 ]
1653 );
1654
1655 add_action( 'astra_notice_after_markup_srfm-plugin-review-notice', [ $this, 'enqueue_notice_response_script' ] );
1656 }
1657
1658 /**
1659 * Display a "Getting Started" admin notice for new users who haven't yet
1660 * reached the rating-notice milestone (3+ forms or 3+ entries).
1661 *
1662 * The Astra Notices library handles the 7-day delay via the
1663 * `display-notice-after` parameter.
1664 *
1665 * @since 2.5.2
1666 * @return void
1667 */
1668 public function display_srfm_getting_started_notice() {
1669 // Only show to admins.
1670 if ( ! Helper::current_user_can() ) {
1671 return;
1672 }
1673
1674 // Allow the notice to be disabled programmatically.
1675 if ( ! apply_filters( 'srfm_show_getting_started_notice', true ) ) {
1676 return;
1677 }
1678
1679 Astra_Notices::add_notice(
1680 [
1681 'id' => 'srfm-getting-started-notice',
1682 'type' => '',
1683 'message' => $this->build_notice_markup(
1684 esc_html__( 'SureForms is ready to power your forms — explore what\'s possible!', 'sureforms' ),
1685 esc_html__( 'Manage your forms, track submissions, and discover features like AI Form Builder, payment integrations, and more from the SureForms dashboard.', 'sureforms' ),
1686 esc_url( admin_url( 'admin.php?page=sureforms_menu' ) ),
1687 esc_html__( 'Go to Dashboard', 'sureforms' ),
1688 esc_html__( 'Maybe later', 'sureforms' ),
1689 esc_html__( 'I already know', 'sureforms' ),
1690 WEEK_IN_SECONDS
1691 ),
1692 'repeat-notice-after' => WEEK_IN_SECONDS,
1693 'show_if' => ! $this->maybe_display_rating_notice(),
1694 'display-notice-after' => WEEK_IN_SECONDS,
1695 'display-with-other-notices' => true,
1696 ]
1697 );
1698
1699 add_action( 'astra_notice_after_markup_srfm-getting-started-notice', [ $this, 'enqueue_notice_response_script' ] );
1700 }
1701
1702 /**
1703 * Enqueue the notice response analytics script.
1704 *
1705 * Called via the astra_notice_after_markup_{id} hook so the script
1706 * only loads when a SureForms notice is actually rendered.
1707 *
1708 * @since 2.5.2
1709 * @return void
1710 */
1711 public function enqueue_notice_response_script() {
1712 if ( wp_script_is( 'srfm-notice-response', 'enqueued' ) ) {
1713 return;
1714 }
1715
1716 wp_enqueue_script(
1717 'srfm-notice-response',
1718 SRFM_URL . 'admin/assets/js/notice-response.js',
1719 [],
1720 SRFM_VER,
1721 true
1722 );
1723
1724 wp_localize_script(
1725 'srfm-notice-response',
1726 'srfmNoticeResponse',
1727 [
1728 'ajaxurl' => admin_url( 'admin-ajax.php' ),
1729 'nonce' => wp_create_nonce( 'srfm_notice_response' ),
1730 ]
1731 );
1732 }
1733
1734 /**
1735 * Handle the notice response AJAX request.
1736 *
1737 * Validates the request and records the analytics event
1738 * for the notice button that was clicked.
1739 *
1740 * @since 2.5.2
1741 * @return void
1742 */
1743 public function handle_notice_response() {
1744 if ( ! check_ajax_referer( 'srfm_notice_response', 'nonce', false ) ) {
1745 wp_send_json_error( [ 'message' => __( 'Invalid nonce.', 'sureforms' ) ], 403 );
1746 }
1747
1748 if ( ! Helper::current_user_can() ) {
1749 wp_send_json_error( [ 'message' => __( 'Unauthorized user.', 'sureforms' ) ], 403 );
1750 }
1751
1752 $notice_id = isset( $_POST['notice_id'] ) ? sanitize_text_field( wp_unslash( $_POST['notice_id'] ) ) : '';
1753 $button = isset( $_POST['button'] ) ? sanitize_text_field( wp_unslash( $_POST['button'] ) ) : '';
1754
1755 $valid = [
1756 'srfm-getting-started-notice' => [
1757 'go_to_dashboard' => 'getting_started_notice_cta',
1758 'maybe_later' => 'getting_started_notice_snooze',
1759 'dismissed' => 'getting_started_notice_dismiss',
1760 ],
1761 'srfm-plugin-review-notice' => [
1762 'rate_sureforms' => 'rating_notice_cta',
1763 'maybe_later' => 'rating_notice_snooze',
1764 'dismissed' => 'rating_notice_dismiss',
1765 ],
1766 ];
1767
1768 if ( ! isset( $valid[ $notice_id ][ $button ] ) ) {
1769 wp_send_json_error( [ 'message' => __( 'Invalid parameters.', 'sureforms' ) ], 400 );
1770 }
1771
1772 $event_name = $valid[ $notice_id ][ $button ];
1773 Analytics::events()->track( $event_name, $button );
1774
1775 wp_send_json_success();
1776 }
1777
1778 /**
1779 * Disables the capabilities for WPForms to avoid conflicts when enqueueing
1780 * scripts and styles for WPForms.
1781 *
1782 * This function is intended to prevent any potential conflicts that may arise
1783 * when WPForms scripts and styles are enqueued. By disabling certain capabilities,
1784 * it ensures that WPForms does not interfere with other functionalities.
1785 *
1786 * @param bool $user_can A boolean indicating whether the user has the capability.
1787 * @return bool Returns true if the capabilities are successfully disabled, false otherwise.
1788 * @since 1.4.2
1789 */
1790 public function disable_wpforms_capabilities( $user_can ) {
1791 // Note: Nonce verification is intentionally omitted here as no database operations are performed.
1792 // The values of the $_REQUEST variables are strictly validated, ensuring security without the need for nonce verification.
1793
1794 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1795 $post_id = ! empty( $_REQUEST['post'] ) && ! empty( $_REQUEST['action'] ) ? absint( $_REQUEST['post'] ) : 0;
1796
1797 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1798 $post_type = $post_id ? get_post_type( $post_id ) : sanitize_text_field( wp_unslash( $_REQUEST['post_type'] ?? '' ) );
1799 return SRFM_FORMS_POST_TYPE === $post_type ? false : $user_can;
1800 }
1801
1802 /**
1803 * Enqueueus the admin pointer script and styles.
1804 *
1805 * @return void
1806 * @since 1.8.0
1807 */
1808 public function enqueue_admin_pointer() {
1809 if ( ! $this->is_admin_pointer_visible() ) {
1810 return;
1811 }
1812 wp_enqueue_style( 'wp-pointer' );
1813 wp_enqueue_script( 'wp-pointer' );
1814 wp_enqueue_script(
1815 'sureforms-admin-pointer',
1816 plugins_url( 'admin/assets/js/sureforms-pointer.js', SRFM_FILE ),
1817 [ 'wp-pointer', 'jquery' ],
1818 SRFM_VER,
1819 true
1820 );
1821 wp_localize_script(
1822 'sureforms-admin-pointer',
1823 'sureformsPointerData',
1824 [
1825 'ajaxurl' => admin_url( 'admin-ajax.php' ),
1826 'pointer_nonce' => wp_create_nonce( 'sureforms_pointer_action' ),
1827 ]
1828 );
1829 }
1830
1831 /**
1832 * Ajax handler for pointer popup visibility.
1833 *
1834 * @return void
1835 * @since 1.8.0
1836 */
1837 public function pointer_should_show() {
1838 // Security: Check user capability.
1839 if ( ! Helper::current_user_can() ) {
1840 wp_send_json_error( [ 'message' => __( 'Unauthorized user.', 'sureforms' ) ], 403 );
1841 }
1842 // Security: Nonce check.
1843 if ( empty( $_POST['pointer_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pointer_nonce'] ) ), 'sureforms_pointer_action' ) ) {
1844 wp_send_json_error( [ 'message' => __( 'Invalid nonce.', 'sureforms' ) ], 403 );
1845 }
1846
1847 $content_markup = sprintf(
1848 /* translators: 1: opening span, 2: opening strong (inline), 3: closing strong, 4: closing span, 5: opening strong (block), 6: closing strong */
1849 __( '%1$sGet started by %2$sbuilding your first form%3$s.%4$s%5$sExperience the power of our intuitive AI Form Builder%6$s', 'sureforms' ),
1850 '<span>',
1851 '<strong>',
1852 '</strong>',
1853 '</span><br/>',
1854 '<strong style="font-size:1.1em;">',
1855 '</strong>'
1856 );
1857 wp_send_json(
1858 [
1859 'show' => true,
1860 'title' => esc_html( __( 'SureForms is waiting for you!', 'sureforms' ) ),
1861 'content' => wp_kses_post( $content_markup ),
1862 'button_text' => esc_html( __( 'Build My First Form', 'sureforms' ) ),
1863 'dismiss' => esc_html( __( 'Dismiss', 'sureforms' ) ),
1864 'button_url' => admin_url( 'admin.php?page=add-new-form' ),
1865 ]
1866 );
1867 }
1868
1869 /**
1870 * Ajax callback for pointer popup dismissed action.
1871 *
1872 * @return void
1873 * @since 1.8.0
1874 */
1875 public function pointer_dismissed() {
1876 // Security: Check user capability.
1877 if ( ! Helper::current_user_can() ) {
1878 wp_send_json_error( [ 'message' => __( 'Unauthorized user.', 'sureforms' ) ], 403 );
1879 }
1880 // Security: Nonce check.
1881 if ( empty( $_POST['pointer_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pointer_nonce'] ) ), 'sureforms_pointer_action' ) ) {
1882 wp_send_json_error( [ 'message' => __( 'Invalid nonce.', 'sureforms' ) ], 403 );
1883 }
1884 // Use Helper to update srfm_options key.
1885 Helper::update_srfm_option( 'pointer_popup_dismissed', time() );
1886
1887 wp_send_json_success();
1888 }
1889
1890 /**
1891 * Ajax pointer accepted CTA callback.
1892 *
1893 * @return void
1894 * @since 1.8.0
1895 */
1896 public function pointer_accepted_cta() {
1897 // Security: Check user capability.
1898 if ( ! Helper::current_user_can() ) {
1899 wp_send_json_error( [ 'message' => __( 'Unauthorized user.', 'sureforms' ) ], 403 );
1900 }
1901 // Security: Nonce check.
1902 if ( empty( $_POST['pointer_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pointer_nonce'] ) ), 'sureforms_pointer_action' ) ) {
1903 wp_send_json_error( [ 'message' => __( 'Invalid nonce.', 'sureforms' ) ], 403 );
1904 }
1905 // Use Helper to update srfm_options key.
1906 Helper::update_srfm_option( 'pointer_popup_accepted', time() );
1907
1908 wp_send_json_success();
1909 }
1910
1911 /**
1912 * Maybe register the dashboard widget based on entries.
1913 *
1914 * @return void
1915 * @since 1.9.1
1916 */
1917 public function maybe_register_dashboard_widget() {
1918
1919 // Only for users with manage_options capability.
1920 if ( ! Helper::current_user_can() ) {
1921 return;
1922 }
1923
1924 // Quick check if there are any entries in the last 7 days.
1925 $seven_days_ago = strtotime( '-7 days' );
1926 $total_entries = Entries::get_entries_count_after( $seven_days_ago );
1927
1928 // Only add the dashboard setup hook if there are entries.
1929 if ( $total_entries > 0 ) {
1930 // Get forms with entries (limit 4 for dashboard widget).
1931 $this->dashboard_widget_data = Helper::get_forms_with_entry_counts( $seven_days_ago, 4 );
1932
1933 // Only show dashboard widget if there are forms with entries.
1934 if ( ! empty( $this->dashboard_widget_data ) ) {
1935 add_action( 'wp_dashboard_setup', [ $this, 'register_dashboard_widget' ] );
1936 }
1937 }
1938 }
1939
1940 /**
1941 * Register the dashboard widget.
1942 *
1943 * @return void
1944 * @since 1.9.1
1945 */
1946 public function register_dashboard_widget() {
1947 // Add the widget with high priority to position it at the top.
1948 wp_add_dashboard_widget(
1949 'sureforms_recent_entries',
1950 __( 'SureForms', 'sureforms' ),
1951 [ $this, 'render_dashboard_widget' ],
1952 null,
1953 null,
1954 'normal',
1955 'high'
1956 );
1957 }
1958
1959 /**
1960 * Render the dashboard widget content.
1961 *
1962 * @return void
1963 * @since 1.9.1
1964 */
1965 public function render_dashboard_widget() {
1966 // Use the pre-fetched data to avoid duplicate queries.
1967 $entries_data = $this->dashboard_widget_data;
1968
1969 // Display the widget content.
1970 ?>
1971 <div class="srfm-dashboard-widget">
1972 <div class="srfm-widget-header">
1973 <h3 class="srfm-widget-title">
1974 <?php esc_html_e( 'Recent Entries', 'sureforms' ); ?>
1975 <span class="srfm-widget-subtitle"><?php esc_html_e( '( Last 7 days )', 'sureforms' ); ?></span>
1976 </h3>
1977 <a href="<?php echo esc_url( admin_url( 'admin.php?page=sureforms_entries' ) ); ?>" class="srfm-widget-view-link">
1978 <?php esc_html_e( 'View', 'sureforms' ); ?>
1979 </a>
1980 </div>
1981
1982 <div class="srfm-table-wrapper">
1983 <table class="srfm-entries-table">
1984 <thead>
1985 <tr>
1986 <th><?php esc_html_e( 'Form Name', 'sureforms' ); ?></th>
1987 <th><?php esc_html_e( 'Entries', 'sureforms' ); ?></th>
1988 </tr>
1989 </thead>
1990 <tbody>
1991 <?php foreach ( $entries_data as $form_data ) { ?>
1992 <tr>
1993 <td class="form-name"><?php echo esc_html( $form_data['title'] ); ?></td>
1994 <td class="entry-count"><?php echo esc_html( $form_data['count'] ); ?></td>
1995 </tr>
1996 <?php } ?>
1997 </tbody>
1998 </table>
1999 </div>
2000
2001 <?php
2002 // Render footer if applicable.
2003 $this->render_dashboard_widget_footer( $entries_data );
2004 ?>
2005 </div>
2006 <?php
2007 }
2008
2009 /**
2010 * Determine whether a notice callback is owned by SureForms.
2011 *
2012 * Recognises object methods on classes in the `SRFM` / `SRFM_PRO` namespaces
2013 * as well as the bundled notices libraries (`BSF_Admin_Notices` and the
2014 * legacy `Astra_Notices` alias). Everything else is treated as foreign.
2015 *
2016 * @param callable|array|string|null $function The registered callback function.
2017 * @since 2.10.0
2018 * @return bool True when the callback belongs to SureForms, false otherwise.
2019 */
2020 private function is_sureforms_owned_notice_callback( $function ) {
2021 $class_name = '';
2022
2023 if ( is_array( $function ) && isset( $function[0] ) ) {
2024 // Object or static method callback represented as an array. The first
2025 // element is either the object instance or the fully qualified class name.
2026 $class_name = is_object( $function[0] ) ? get_class( $function[0] ) : (string) $function[0];
2027 } elseif ( is_string( $function ) && false !== strpos( $function, '::' ) ) {
2028 // Static method passed as "Class::method".
2029 $class_name = strstr( $function, '::', true );
2030 }
2031
2032 if ( '' === $class_name ) {
2033 // Plain function callbacks are never owned by SureForms.
2034 return false;
2035 }
2036
2037 // SureForms (free and pro) namespaced classes. Pro's real namespace is
2038 // `SRFM_Pro\` (case-sensitive) — not the all-caps `SRFM_PRO_` constant
2039 // prefix — so match it case-insensitively to be safe.
2040 if ( 0 === strpos( $class_name, 'SRFM\\' ) || 0 === stripos( $class_name, 'SRFM_Pro\\' ) ) {
2041 return true;
2042 }
2043
2044 // Bundled notices library shipped with SureForms.
2045 return in_array( $class_name, [ 'BSF_Admin_Notices', 'Astra_Notices' ], true );
2046 }
2047
2048 /**
2049 * Whether the current admin page is a SureForms-owned screen identified by a
2050 * `sureforms_*` / `srfm_*` `page` query slug. Complements
2051 * {@see Helper::is_sureforms_admin_page()} so foreign-notice suppression also
2052 * covers the payments / quiz / survey / learn / SMTP / partial-entries screens
2053 * that the core helper does not enumerate. Read-only screen check.
2054 *
2055 * @since 2.10.0
2056 * @return bool
2057 */
2058 private function is_sureforms_owned_admin_page() {
2059 if ( ! is_admin() || empty( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen detection, no state change.
2060 return false;
2061 }
2062 $page = sanitize_key( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen detection, no state change.
2063 return 0 === strpos( $page, 'sureforms' ) || 0 === strpos( $page, 'srfm' );
2064 }
2065
2066 /**
2067 * Build the shared HTML markup for admin notices.
2068 *
2069 * @since 2.5.2
2070 *
2071 * All text parameters must be pre-escaped by the caller (e.g. via esc_html__()).
2072 * URL parameters must be pre-escaped via esc_url().
2073 *
2074 * @param string $heading The notice heading text (pre-escaped).
2075 * @param string $message The notice body text (pre-escaped).
2076 * @param string $cta_url The primary CTA URL (pre-escaped).
2077 * @param string $cta_text The primary CTA button text (pre-escaped).
2078 * @param string $snooze_text The snooze button text (pre-escaped).
2079 * @param string $dismiss_text The dismiss button text (pre-escaped).
2080 * @param int $snooze_duration Snooze duration in seconds for the data-repeat-notice-after attribute.
2081 * @param bool $external_cta Whether the CTA opens in a new tab and also dismisses the notice
2082 * via the astra-notice-close class. Default false.
2083 * @return string The notice HTML markup.
2084 */
2085 private function build_notice_markup( $heading, $message, $cta_url, $cta_text, $snooze_text, $dismiss_text, $snooze_duration, $external_cta = false ) {
2086 $image_path = esc_url( SRFM_URL . 'admin/assets/sureforms-logo.png' );
2087 $cta_class = $external_cta ? 'astra-notice-close button-primary' : 'button-primary';
2088 $cta_attrs = $external_cta ? ' target="_blank" rel="noopener noreferrer"' : '';
2089
2090 return sprintf(
2091 '<div class="notice-image">
2092 <img src="%1$s" class="custom-logo" alt="SureForms" itemprop="logo">
2093 </div>
2094 <div class="notice-content">
2095 <div class="notice-heading">
2096 %2$s
2097 </div>
2098 %3$s<br />
2099 <div class="astra-review-notice-container">
2100 <a href="%4$s" class="%5$s"%6$s>
2101 %7$s
2102 </a>
2103 <span class="dashicons dashicons-clock" aria-hidden="true"></span>
2104 <a href="#" data-repeat-notice-after="%8$s" class="astra-notice-close">
2105 %9$s
2106 </a>
2107 <span class="dashicons dashicons-smiley" aria-hidden="true"></span>
2108 <a href="#" class="astra-notice-close">
2109 %10$s
2110 </a>
2111 </div>
2112 </div>',
2113 $image_path,
2114 $heading,
2115 $message,
2116 $cta_url,
2117 esc_attr( $cta_class ),
2118 $cta_attrs,
2119 $cta_text,
2120 $snooze_duration,
2121 $snooze_text,
2122 $dismiss_text
2123 );
2124 }
2125
2126 /**
2127 * Callback for displaying the rating notice conditionally.
2128 *
2129 * Returns true if the user has 3 or more published forms or 3 or more form entries.
2130 *
2131 * @since 2.5.2
2132 * @return bool
2133 */
2134 private function maybe_display_rating_notice() {
2135 if ( null === $this->should_show_rating ) {
2136 $entries_count = Entries::get_total_entries_by_status( 'all' );
2137 $form_count = wp_count_posts( SRFM_FORMS_POST_TYPE );
2138 $this->should_show_rating = $entries_count >= self::RATING_NOTICE_THRESHOLD || Helper::get_integer_value( $form_count->publish ?? 0 ) >= self::RATING_NOTICE_THRESHOLD;
2139 }
2140
2141 return $this->should_show_rating;
2142 }
2143
2144 /**
2145 * Get random premium feature text.
2146 *
2147 * @return string Random feature text.
2148 * @since 1.9.1
2149 */
2150 private function get_random_premium_feature_text() {
2151 $features = [
2152 __( 'Use Conditional Logic to show only what matters', 'sureforms' ),
2153 __( 'Split your form into steps to keep it easy', 'sureforms' ),
2154 __( 'Let people upload files directly to your form', 'sureforms' ),
2155 __( 'Turn responses into downloadable PDFs automatically', 'sureforms' ),
2156 __( 'Let users sign with a simple signature field', 'sureforms' ),
2157 __( 'Connect your form to other tools using webhooks', 'sureforms' ),
2158 __( 'Use Conversational Forms for a chat-like experience', 'sureforms' ),
2159 __( 'Let users register or log in through your form', 'sureforms' ),
2160 __( 'Build forms that create WordPress user accounts', 'sureforms' ),
2161 __( 'Add calculations to auto-total scores or prices', 'sureforms' ),
2162 ];
2163
2164 // Get a random feature.
2165 $random_key = array_rand( $features );
2166 return $features[ $random_key ];
2167 }
2168
2169 /**
2170 * Render the dashboard widget footer for upsell.
2171 *
2172 * @param array $entries_data The entries data array.
2173 * @return void
2174 * @since 1.9.1
2175 */
2176 private function render_dashboard_widget_footer( $entries_data ) {
2177 // Only show footer if Pro is not active.
2178 if ( Helper::has_pro() ) {
2179 return;
2180 }
2181
2182 // Count total entries in last 7 days.
2183 $total_entries = 0;
2184 foreach ( $entries_data as $form_data ) {
2185 $total_entries += $form_data['count'];
2186 }
2187
2188 // Count total published forms.
2189 $published_forms_count = wp_count_posts( SRFM_FORMS_POST_TYPE )->publish;
2190
2191 // Show footer only if 3+ entries received OR 3+ forms published.
2192 if ( $total_entries >= 3 || $published_forms_count >= 3 ) {
2193 ?>
2194 <div class="srfm-widget-footer">
2195 <div class="srfm-upgrade-content">
2196 <svg class="srfm-logo-icon" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2197 <rect width="20" height="20" fill="#D54407"/>
2198 <path d="M5.7139 4.2854H14.2853V7.1425H7.1424L5.7139 8.5711V7.1425V4.2854Z" fill="white"/>
2199 <path d="M5.7139 4.2854H14.2853V7.1425H7.1424L5.7139 8.5711V7.1425V4.2854Z" fill="white"/>
2200 <path d="M5.7148 8.5713H12.8577V11.4284H7.1434L5.7148 12.857V11.4284V8.5713Z" fill="white"/>
2201 <path d="M5.7148 8.5713H12.8577V11.4284H7.1434L5.7148 12.857V11.4284V8.5713Z" fill="white"/>
2202 <path d="M5.7148 12.8569H10.0006V15.7141H5.7148V12.8569Z" fill="white"/>
2203 <path d="M5.7148 12.8569H10.0006V15.7141H5.7148V12.8569Z" fill="white"/>
2204 </svg>
2205 <span><?php echo esc_html( $this->get_random_premium_feature_text() ); ?></span>
2206 </div>
2207 <?php
2208 $upgrade_url = Helper::get_sureforms_website_url( 'pricing', [ 'utm_medium' => 'dashboard-widget' ] );
2209 ?>
2210 <a href="<?php echo esc_url( $upgrade_url ); ?>" class="srfm-upgrade-link" target="_blank">
2211 <?php esc_html_e( 'Upgrade', 'sureforms' ); ?>
2212 </a>
2213 </div>
2214 <?php
2215 }
2216 }
2217
2218 /**
2219 * Determine if the admin pointer should be visible on this page.
2220 *
2221 * @since 1.8.0
2222 * @return bool
2223 */
2224 private function is_admin_pointer_visible() {
2225 global $pagenow;
2226 $allowed_pages = [ 'index.php', 'options-general.php' ];
2227
2228 // Do not show if pointer dismissed, accepted, or more than 1 form exists.
2229 if (
2230 ! empty( Helper::get_srfm_option( 'pointer_popup_dismissed' ) )
2231 || ! empty( Helper::get_srfm_option( 'pointer_popup_accepted' ) )
2232 || (int) ( wp_count_posts( SRFM_FORMS_POST_TYPE )->publish ?? 0 ) > 1
2233 ) {
2234 return false;
2235 }
2236
2237 if ( in_array( $pagenow, $allowed_pages, true ) ) {
2238 return true;
2239 }
2240
2241 return false;
2242 }
2243
2244 }
2245