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
FrontendEnqueue.php
233 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Style Customizer v2 — engine-aware frontend enqueue. |
| 4 | * |
| 5 | * Decides, per form, whether the front end renders through the v2 engine or the legacy one |
| 6 | * (exactly one stylesheet per form). A v2 form gets the `evf-style-v2` marker class, the rule |
| 7 | * template ID-scoped to `#evf-{id}` (out-specifying Everest Forms' deeply-nested base CSS), |
| 8 | * the per-form CSS-variable block from {@see Compiler}, and its selected Google font. |
| 9 | * |
| 10 | * @package EverestForms\Addons\StyleCustomizer\V2 |
| 11 | * @since x.x.x |
| 12 | */ |
| 13 | |
| 14 | namespace EverestForms\Addons\StyleCustomizer\V2; |
| 15 | |
| 16 | defined( 'ABSPATH' ) || exit; |
| 17 | |
| 18 | /** |
| 19 | * Frontend enqueue for v2 forms. |
| 20 | */ |
| 21 | final class FrontendEnqueue { |
| 22 | |
| 23 | /** |
| 24 | * Shared stylesheet handle. |
| 25 | */ |
| 26 | const HANDLE = 'evf-style-v2'; |
| 27 | |
| 28 | /** |
| 29 | * Marker class added to a v2 form's wrapper. Shared with the builder panel so its live |
| 30 | * preview bridge can scope the same rule template inside the `?evf_preview` iframe. |
| 31 | */ |
| 32 | const MARKER_CLASS = 'evf-style-v2'; |
| 33 | |
| 34 | /** |
| 35 | * Memoized rule-template text (read once per request from the shared CSS file). |
| 36 | * |
| 37 | * @var string|null |
| 38 | */ |
| 39 | protected static $template = null; |
| 40 | |
| 41 | /** |
| 42 | * Wire the frontend hooks. Called from {@see Engine::boot()} (only when enabled). Runs at |
| 43 | * priority 20, after the legacy Style Customizer's enqueue (10), so it can dequeue it. |
| 44 | */ |
| 45 | public static function register() { |
| 46 | add_action( 'everest_forms_shortcode_scripts', array( __CLASS__, 'enqueue' ), 20 ); |
| 47 | add_filter( 'everest_forms_frontend_container_class', array( __CLASS__, 'container_class' ), 10, 2 ); |
| 48 | } |
| 49 | |
| 50 | /** |
| 51 | * Enqueue the rule template + per-form variables for a v2 form. Mirrors the v1 enqueue |
| 52 | * hook signature (`$atts['id']`). |
| 53 | * |
| 54 | * @param array $atts Shortcode atts (`id`). |
| 55 | */ |
| 56 | public static function enqueue( $atts ) { |
| 57 | $form_id = isset( $atts['id'] ) ? absint( $atts['id'] ) : 0; |
| 58 | if ( ! $form_id ) { |
| 59 | return; |
| 60 | } |
| 61 | |
| 62 | $record = self::v2_record( $form_id ); |
| 63 | if ( null === $record ) { |
| 64 | return; // Legacy form — the v1 enqueue handles it. |
| 65 | } |
| 66 | |
| 67 | // A v2 form must not also load the legacy per-form compiled sheet. |
| 68 | self::suppress_legacy_styles( $form_id ); |
| 69 | |
| 70 | if ( ! wp_style_is( self::HANDLE, 'registered' ) ) { |
| 71 | wp_register_style( self::HANDLE, false, array(), (string) Schema::version() ); |
| 72 | } |
| 73 | wp_enqueue_style( self::HANDLE ); |
| 74 | |
| 75 | // 1) The rule template (selectors reading the vars), ID-scoped to this form. |
| 76 | $css = self::scoped_rule_template( $form_id ); |
| 77 | |
| 78 | // 2) Per-form variable block. |
| 79 | $css .= Compiler::compile( $record, $form_id ); |
| 80 | |
| 81 | // Custom CSS is NOT emitted here — it's WP core's own site-wide Additional CSS |
| 82 | // (matches v1's actual behavior exactly, verified directly against v1), which WP core |
| 83 | // already prints globally on every page via its own wp_custom_css_cb() hook. Scoping it |
| 84 | // to this one form here would be wrong twice over: it'd only reach v2 forms, and it'd |
| 85 | // stop applying to every OTHER form the way it's supposed to. |
| 86 | |
| 87 | if ( '' !== $css ) { |
| 88 | wp_add_inline_style( self::HANDLE, $css ); |
| 89 | } |
| 90 | |
| 91 | self::maybe_enqueue_font( $record, $form_id ); |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * The shared rule template, ID-scoped to a form (`.evf-style-v2#evf-{id}`) — mirrors the |
| 96 | * live-preview bridge's transform so frontend and preview stay in lockstep. |
| 97 | * |
| 98 | * @param int $form_id Form id. |
| 99 | * @return string Rescoped CSS (empty if the template file is unreadable). |
| 100 | */ |
| 101 | protected static function scoped_rule_template( $form_id ) { |
| 102 | $template = self::rule_template(); |
| 103 | if ( '' === $template ) { |
| 104 | return ''; |
| 105 | } |
| 106 | $scope = self::MARKER_CLASS . '#evf-' . (int) $form_id; |
| 107 | // Only replace the marker class when not followed by a name char (mirrors PreviewBridge's regex). |
| 108 | return (string) preg_replace( |
| 109 | '/\.' . preg_quote( self::MARKER_CLASS, '/' ) . '(?![\w-])/', |
| 110 | '.' . $scope, |
| 111 | $template |
| 112 | ); |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Read (and memoize) the shared rule-template file. |
| 117 | * |
| 118 | * @return string |
| 119 | */ |
| 120 | protected static function rule_template() { |
| 121 | if ( null === self::$template ) { |
| 122 | $path = plugin_dir_path( __FILE__ ) . 'assets/css/frontend.css'; |
| 123 | self::$template = is_readable( $path ) ? (string) file_get_contents( $path ) : ''; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- reading a bundled plugin asset, not a remote/user file. |
| 124 | } |
| 125 | return self::$template; |
| 126 | } |
| 127 | |
| 128 | /** |
| 129 | * Enqueue the form's selected Google font, unless it's using the theme font. |
| 130 | * |
| 131 | * @param array $record V2 style record. |
| 132 | * @param int|string $form_id Form id. |
| 133 | */ |
| 134 | protected static function maybe_enqueue_font( $record, $form_id ) { |
| 135 | $tokens = isset( $record['tokens'] ) && is_array( $record['tokens'] ) ? $record['tokens'] : array(); |
| 136 | // The global "Apply Theme Style" toggle forces theme fonts too — see Compiler::compile(). |
| 137 | $apply_theme_style = 'default' !== get_post_meta( $form_id, 'everest_forms_enable_theme_style', true ); |
| 138 | $theme_font = $apply_theme_style || ! empty( $tokens['fonts.theme']['desktop'] ); |
| 139 | $family = isset( $tokens['fonts.family']['desktop'] ) ? trim( (string) $tokens['fonts.family']['desktop'] ) : ''; |
| 140 | |
| 141 | if ( $theme_font || '' === $family ) { |
| 142 | return; |
| 143 | } |
| 144 | if ( function_exists( 'evfsc_enqueue_fonts' ) ) { |
| 145 | evfsc_enqueue_fonts( $family ); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * Add the `evf-style-v2` marker class to a v2 form's wrapper. Also adds |
| 151 | * `evf-choice-{variation}` when the Choices "Style variation" is `outline`/`filled`, and |
| 152 | * `evf-choice-align-{center|right}` when the Choices "Alignment" isn't the default `left`. |
| 153 | * |
| 154 | * @param array $classes Container classes. |
| 155 | * @param array $form_data Form data (`id`). |
| 156 | * @return array |
| 157 | */ |
| 158 | public static function container_class( $classes, $form_data ) { |
| 159 | $form_id = isset( $form_data['id'] ) ? absint( $form_data['id'] ) : 0; |
| 160 | $record = $form_id ? self::v2_record( $form_id ) : null; |
| 161 | if ( null === $record ) { |
| 162 | return $classes; |
| 163 | } |
| 164 | $classes[] = 'evf-style-v2'; |
| 165 | $variation = isset( $record['tokens']['choice.variation']['desktop'] ) ? (string) $record['tokens']['choice.variation']['desktop'] : ''; |
| 166 | if ( in_array( $variation, array( 'outline', 'filled' ), true ) ) { |
| 167 | $classes[] = 'evf-choice-' . $variation; |
| 168 | } |
| 169 | // The Subscription Plan card's name/price row is a fixed `space-between` flex row (see |
| 170 | // evf-payment-subscription-plan-frontend.scss) that plain `text-align` can never reach — |
| 171 | // bridge the align token to a class too, the same way choice.variation already does, so |
| 172 | // only that field's row responds to it (every other choice type already works via |
| 173 | // `--evf-choice-align`'s inherited `text-align`). |
| 174 | $align = isset( $record['tokens']['choice.align']['desktop'] ) ? (string) $record['tokens']['choice.align']['desktop'] : ''; |
| 175 | if ( in_array( $align, array( 'center', 'right' ), true ) ) { |
| 176 | $classes[] = 'evf-choice-align-' . $align; |
| 177 | } |
| 178 | // btn.widthMode is another "meta" token (no CSS var) — 'fill' adds a class, same pattern |
| 179 | // as choice.variation/choice.align above; 'fit' (the default) needs no class at all. |
| 180 | $width_mode = isset( $record['tokens']['btn.widthMode']['desktop'] ) ? (string) $record['tokens']['btn.widthMode']['desktop'] : ''; |
| 181 | if ( 'fill' === $width_mode ) { |
| 182 | $classes[] = 'evf-btn-width-fill'; |
| 183 | } |
| 184 | return $classes; |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * Dequeue + deregister the legacy per-form stylesheet handle for a v2 form. |
| 189 | * |
| 190 | * @param int $form_id Form id. |
| 191 | */ |
| 192 | protected static function suppress_legacy_styles( $form_id ) { |
| 193 | $handle = 'everest-forms-style-' . $form_id; |
| 194 | if ( wp_style_is( $handle, 'enqueued' ) || wp_style_is( $handle, 'registered' ) ) { |
| 195 | wp_dequeue_style( $handle ); |
| 196 | wp_deregister_style( $handle ); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | /** |
| 201 | * The stored style record for a form. |
| 202 | * |
| 203 | * @param int $form_id Form id. |
| 204 | * @return array |
| 205 | */ |
| 206 | protected static function record( $form_id ) { |
| 207 | $all = get_option( 'everest_forms_styles', array() ); |
| 208 | return isset( $all[ $form_id ] ) && is_array( $all[ $form_id ] ) ? $all[ $form_id ] : array(); |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * The stored record, only if it's actually v2-shaped, with the SAME `evf_style_v2_record` |
| 213 | * filter {@see RestController::build_payload()} applies for the panel. Without this, an |
| 214 | * addon's backfill (e.g. Multi-Part's `EVFMP_Style_Customizer::backfill_record()`, which |
| 215 | * derives `pagination.*` tokens from the classic `settings.multi_part` values for a form that |
| 216 | * became v2-shaped — via ANY style save — without ever having its own Pagination section |
| 217 | * explicitly saved) only ever ran for the builder panel's display, never for what actually |
| 218 | * rendered on the frontend: `Compiler::compile()` would resolve those missing tokens to their |
| 219 | * schema DEFAULTS instead, silently dropping the form's real pagination colors/type on the |
| 220 | * live site while the panel kept showing them correctly. |
| 221 | * |
| 222 | * @param int $form_id Form id. |
| 223 | * @return array|null The record, or null if this form isn't v2-shaped (caller defers to v1). |
| 224 | */ |
| 225 | protected static function v2_record( $form_id ) { |
| 226 | $record = self::record( $form_id ); |
| 227 | if ( ! Engine::is_v2_record( $record ) ) { |
| 228 | return null; |
| 229 | } |
| 230 | return apply_filters( 'evf_style_v2_record', $record, $form_id ); |
| 231 | } |
| 232 | } |
| 233 |