PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.6.0
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.6.0
3.6.0 3.5.3 3.5.2 3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / addons / StyleCustomizer / V2 / BuilderPanel.php
everest-forms / addons / StyleCustomizer / V2 Last commit date
assets 1 week ago BuilderPanel.php 1 week ago Compiler.php 1 week ago Engine.php 1 week ago FrontendEnqueue.php 1 week ago Migrator.php 1 week ago Palettes.php 1 week ago PreviewDraft.php 1 week ago RestController.php 1 week ago Sanitizer.php 1 week ago Schema.php 1 week ago Templates.php 1 week ago
BuilderPanel.php
193 lines
1 <?php
2 /**
3 * Style Customizer v2 — builder "Style" tab.
4 *
5 * Registers a "Style" tab in the form builder, mounts the React panel into it, and enqueues
6 * the panel bundle. Wired only when `Engine::enabled()`.
7 *
8 * @package EverestForms\Addons\StyleCustomizer\V2
9 * @since x.x.x
10 */
11
12 namespace EverestForms\Addons\StyleCustomizer\V2;
13
14 defined( 'ABSPATH' ) || exit;
15
16 /**
17 * Builder tab + panel enqueue.
18 */
19 final class BuilderPanel {
20
21 /**
22 * Builder tab slug (drives the `&tab=style` URL and the `everest-forms-panel-style` wrapper).
23 */
24 const TAB = 'style';
25
26 /**
27 * Builder screen id.
28 */
29 const SCREEN = 'everest-forms_page_evf-builder';
30
31 /**
32 * Wire the builder hooks. Called from {@see Engine::boot()}.
33 */
34 public static function register() {
35 add_filter( 'everest_forms_builder_tabs_array', array( __CLASS__, 'add_tab' ), 30 );
36 add_action( 'everest_forms_builder_sidebar_' . self::TAB, array( __CLASS__, 'render_sidebar' ) );
37 add_action( 'everest_forms_builder_content_' . self::TAB, array( __CLASS__, 'render_content' ) );
38 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ) );
39 add_action( 'admin_head', array( __CLASS__, 'tab_icon_styles' ) );
40 }
41
42 /**
43 * Add the "Style" tab to the builder nav, positioned right after "Fields".
44 *
45 * @param array $tabs Tabs (slug => [label, sidebar]).
46 * @return array
47 */
48 public static function add_tab( $tabs ) {
49 $entry = array(
50 self::TAB => array(
51 'label' => __( 'Style', 'everest-forms' ),
52 'sidebar' => true,
53 ),
54 );
55
56 if ( ! isset( $tabs['fields'] ) ) {
57 return array_merge( $tabs, $entry );
58 }
59
60 $out = array();
61 foreach ( $tabs as $slug => $tab ) {
62 $out[ $slug ] = $tab;
63 if ( 'fields' === $slug ) {
64 $out = array_merge( $out, $entry );
65 }
66 }
67 return $out;
68 }
69
70 /**
71 * Render the controls mount point inside the builder's native sidebar.
72 */
73 public static function render_sidebar() {
74 echo '<div id="evf-scv2-controls" class="evf-scv2-controls"></div>';
75 }
76
77 /**
78 * Render the live-preview mount point inside the tab's content panel.
79 */
80 public static function render_content() {
81 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
82 $form_id = isset( $_GET['form_id'] ) ? absint( wp_unslash( $_GET['form_id'] ) ) : 0;
83 printf( '<div id="evf-scv2-preview" class="evf-scv2-preview" data-form-id="%d"></div>', (int) $form_id );
84 }
85
86 /**
87 * Paint the "Style" builder tab's icon with a masked SVG (no icon-font glyph exists for it).
88 */
89 public static function tab_icon_styles() {
90 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
91 if ( ! $screen || self::SCREEN !== $screen->id ) {
92 return;
93 }
94 // Hardcoded, fully URL-encoded SVG constant with no external input.
95 $svg = "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M3%2021l3-1%2011-11a2.8%202.8%200%200%200-4-4L2%2016l-1%205'/%3E%3Cpath%20d='m15%205%204%204'/%3E%3C/svg%3E";
96 $mask = 'url("' . $svg . '") no-repeat center / contain';
97
98 echo '<style id="evf-scv2-tab-icon">'
99 . '.everest-forms nav.evf-nav-tab-wrapper a.evf-panel-style-button .evf-nav-icon.style::before{'
100 . 'content:"";display:inline-block;width:16px;height:16px;background-color:currentColor;'
101 . '-webkit-mask:' . $mask . ';mask:' . $mask . ';}'
102 . '</style>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static developer-controlled CSS constant.
103 }
104
105 /**
106 * Mtime of a bundled V2 asset (cache-bust component), or '0' if unreadable.
107 *
108 * @param string $relative Path relative to the V2 directory.
109 * @return string
110 */
111 protected static function asset_mtime( $relative ) {
112 $path = plugin_dir_path( __FILE__ ) . ltrim( $relative, '/' );
113 return file_exists( $path ) ? (string) filemtime( $path ) : '0';
114 }
115
116 /**
117 * Enqueue the panel bundle on the builder screen.
118 *
119 * @param string $hook Current admin page hook (unused; we check the screen id).
120 */
121 public static function enqueue( $hook ) {
122 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
123 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
124 if ( ! $screen || self::SCREEN !== $screen->id || ! isset( $_GET['form_id'] ) ) {
125 return;
126 }
127 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
128 $form_id = absint( wp_unslash( $_GET['form_id'] ) );
129
130 wp_enqueue_media();
131
132 // Cache-bust by the built file's mtime; fall back to the plugin version.
133 $bundle_path = evf()->plugin_path() . '/dist/styleCustomizerV2.min.js';
134 $bundle_ver = file_exists( $bundle_path ) ? (string) filemtime( $bundle_path ) : ( defined( 'EVF_VERSION' ) ? EVF_VERSION : false );
135
136 wp_enqueue_script(
137 'evf-style-v2-panel',
138 evf()->plugin_url() . '/dist/styleCustomizerV2.min.js',
139 array( 'wp-element', 'wp-api-fetch', 'wp-i18n', 'react', 'react-dom' ),
140 $bundle_ver,
141 true
142 );
143
144 if ( function_exists( 'wp_set_script_translations' ) ) {
145 wp_set_script_translations( 'evf-style-v2-panel', 'everest-forms' );
146 }
147
148 // Per-page-load token scoping the live-preview draft to this builder session.
149 $preview_session = wp_generate_password( 12, false );
150
151 wp_localize_script(
152 'evf-style-v2-panel',
153 'evfStyleV2',
154 array(
155 'restBase' => '/everest-forms/v1/styles',
156 'formId' => $form_id,
157 'formTitle' => get_the_title( $form_id ),
158 // Scheme forced to match the admin request to avoid a mixed-content iframe block.
159 'previewUrl' => set_url_scheme(
160 add_query_arg(
161 array(
162 'form_id' => $form_id,
163 'evf_preview' => 'true',
164 PreviewDraft::PREVIEW_FLAG => '1',
165 PreviewDraft::SESSION_ARG => $preview_session,
166 ),
167 home_url( '/' )
168 ),
169 is_ssl() ? 'https' : 'http'
170 ),
171 // Rule template URL, version-stamped to avoid a stale browser cache.
172 'frontendCssUrl' => add_query_arg( 'ver', (string) Schema::version() . '.' . self::asset_mtime( 'assets/css/frontend.css' ), evf()->plugin_url() . '/addons/StyleCustomizer/V2/assets/css/frontend.css' ),
173 'wrapperId' => 'evf-' . $form_id,
174 'markerClass' => FrontendEnqueue::MARKER_CLASS,
175 'previewSession' => $preview_session,
176 // AI launcher visibility: present whenever the integration class exists — so on a
177 // local/staging site it still renders, greyed-out and non-functional, exactly like
178 // the Fields-tab AI Form Assistant (rather than vanishing). `aiDisabled` carries the
179 // local-site check the button uses to grey itself out and explain why on hover.
180 'aiEnabled' => class_exists( 'EVF_AI_Registration' ),
181 'aiDisabled' => class_exists( 'EVF_AI_Registration' ) && \EVF_AI_Registration::is_local_site(),
182 // Discovery-hint dismissal — per-user (not per-browser), shared AJAX action/nonce
183 // with the Fields-tab AI assistant (see EVF_AI_Ajax::dismiss_hint()).
184 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
185 'aiNonce' => wp_create_nonce( 'evf_ai_nonce' ),
186 'aiHintDismissed' => (bool) get_user_meta( get_current_user_id(), 'evf_ai_style_hint_dismissed', true ),
187 // Full initial REST payload, computed here so the panel initializes without a follow-up fetch.
188 'payload' => RestController::build_payload( $form_id ),
189 )
190 );
191 }
192 }
193