PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.2
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.2
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 in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.12.2, at admin/admin.php

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