PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.14.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.14.0
2.14.0 2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 All 81 releases
← All changes | includes/classes/block-base-abstract.php +191 -29 2.9.1 → 2.14.0 View file →
@@ -19,13 +19,17 @@
19 19 protected $blocks_dir_path = ABLOCKS_BLOCKS_DIR_PATH;
20 20
21 21 protected $parent_block_name = '';
22 22
23 + protected $is_skip_inner_block = false;
24 +
23 25 protected $block_name = '';
24 26
25 27 protected $style_depends = [];
26 28 protected $script_depends = [];
27 29
30 + protected $animation_settings = [];
31 +
28 32 public function __construct( $keep_silent = false ) {
29 33 if ( $this->is_enabled_block() && ! $keep_silent ) {
30 34 add_action( 'init', array( $this, 'register_block' ), 20 );
31 35 add_action( 'ablocks/before_render_' . $this->block_name . '_block_content', array( $this, 'enqueue_block_specific_static_assets' ) );
@@ -42,12 +46,21 @@
42 46 }
43 47
44 48 public function register_block() {
45 49 $block_path = $this->assets_path . 'build/blocks/' . $this->block_name . '/block.json';
46 - // Register the block with the merged attributes and render callback
47 - register_block_type( $block_path, array(
50 + $args = [
48 51 'render_callback' => array( $this, 'render_callback' ),
49 - ) );
52 + ];
53 + if ( isset( $metadata['usesContext'] ) ) {
54 + $args['usesContext'] = $metadata['usesContext'];
55 + }
56 + if ( isset( $metadata['providesContext'] ) ) {
57 + $args['providesContext'] = $metadata['providesContext'];
58 + }
59 + if ( $this->is_skip_inner_block ) {
60 + $args['skip_inner_blocks'] = $this->is_skip_inner_block;
61 + }
62 + register_block_type( $block_path, $args );
50 63 }
51 64
52 65 public function get_attributes() {
53 66 $block_attributes = include $this->blocks_dir_path . $this->block_name . '/attributes.php';
@@ -68,11 +81,12 @@
68 81
69 82 return 'class="' . esc_attr( implode( ' ', $classes ) ) . '"';
70 83 }
71 84 private function get_block_data_settings_attributes( $settings ) {
72 - if ( ! is_array( $settings ) ) {
85 + if ( ! is_array( $settings ) || empty( $settings['animationType'] ) || $settings['animationType'] !== 'none' ) {
73 86 return '';
74 87 }
88 +
75 89 // Sanitize each setting in the array
76 90 $sanitized_settings = array_map( 'esc_attr', $settings );
77 91 // Encode sanitized settings to JSON and escape it for safe output
78 92 return sprintf( 'data-settings="%s"', esc_attr( wp_json_encode( $sanitized_settings ) ) );
@@ -80,25 +94,41 @@
80 94 private function get_dynamic_block_wrap( $attributes, $content, $block_instance ) {
81 95 $block_id = ( isset( $attributes['block_id'] ) ? $attributes['block_id'] : '' );
82 96 $animation = ( isset( $attributes['_animation'] ) ? $attributes['_animation'] : [] );
83 97 $block_class_args = array( 'ablocks-block', 'ablocks-block-' . $block_id, 'ablocks-block--' . $this->block_name );
84 - if (
85 - count( $animation ) &&
86 - ( $animation['animationType'] && $animation['animationType'] !== 'none' ) ||
87 - ( $animation['animationTypeTablet'] && $animation['animationTypeTablet'] !== 'none' ) ||
88 - ( $animation['animationTypeMobile'] && $animation['animationTypeMobile'] !== 'none' )
98 + $has_transform = (bool) ( isset( $attributes['_transform']['isApplied'] ) ? $attributes['_transform']['isApplied'] : false );
99 +
100 + if ( count( $animation ) && (
101 + ( ! empty( $animation['animationType'] ) && $animation['animationType'] !== 'none' ) ||
102 + ( ! empty( $animation['animationTypeTablet'] ) && $animation['animationTypeTablet'] !== 'none' ) ||
103 + ( ! empty( $animation['animationTypeMobile'] ) && $animation['animationTypeMobile'] !== 'none' )
104 + )
89 105 ) {
90 106 array_push( $block_class_args, 'ablocks-invisible' );
91 107 }
108 +
109 + if ( $has_transform ) {
110 + array_push( $block_class_args, 'ablocks-has-block-container' );
111 + }
112 +
92 113 ob_start();
93 114 ?>
94 115 <div <?php echo $this->get_block_class( $block_class_args ) . $this->get_block_data_settings_attributes( $animation ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
116 + <?php
117 + if ( $has_transform ) :
118 + ?>
95 119 <div class="ablocks-block-container">
96 120 <?php
121 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
122 + echo $this->render_block_content( $attributes, $content, $block_instance );
123 + ?>
124 + </div>
125 + <?php
126 + else :
97 127 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
98 128 echo $this->render_block_content( $attributes, $content, $block_instance );
129 + endif;
99 130 ?>
100 - </div>
101 131 </div>
102 132 <?php
103 133 return ob_get_clean();
104 134 }
@@ -110,12 +140,30 @@
110 140
111 141 public function render_callback( $attributes, $content, $block_instance ) {
112 142 $block_name = $block_instance->name;
113 143 do_action( 'ablocks/before_render_' . explode( '/', $block_name )[1] . '_block_content', $block_name );
144 + do_action( 'ablocks/render_callback', $block_name, $attributes );
114 145
146 + // Animation CSS is no longer a global dependency (see get_style_depends).
147 + // In the fallback path (asset-generation off) enqueue it only when this
148 + // block actually animates, so pages without animation never load it.
149 + $this->maybe_enqueue_animate_style( $attributes );
150 +
115 151 // Dynamic block
116 - if ( ! $content ) {
117 - $content = $this->get_dynamic_block_wrap( $attributes, $content, $block_instance );
152 + if ( ! $content || $this->is_skip_inner_block ) {
153 + // When called from the editor's ServerSideRender (REST API), RenderContainer (JS) already
154 + // provides the outer ablocks-block-{blockId} wrapper via useBlockProps. Including it here
155 + // too causes the Advanced-settings CSS selector to match two elements → double border/padding.
156 + // Only that preview render (it always sends context=edit): front-end REST endpoints such as the
157 + // loop filter re-render blocks into the page, where the wrapper and its scoped styles are needed.
158 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
159 + if ( defined( 'REST_REQUEST' ) && REST_REQUEST && 'edit' === ( $_REQUEST['context'] ?? '' ) ) {
160 + ob_start();
161 + echo $this->render_block_content( $attributes, $content, $block_instance );
162 + $content = ob_get_clean();
163 + } else {
164 + $content = $this->get_dynamic_block_wrap( $attributes, $content, $block_instance );
165 + }
118 166 }
119 167
120 168 // Static Block but control render from php
121 169 $content = apply_filters( 'ablocks/get_render_block_content', $content, $attributes, $block_instance );
@@ -123,10 +171,12 @@
123 171 return $content;
124 172 }
125 173
126 174 // Pure Static block
127 - if ( apply_filters( 'ablocks/is_allow_block_inline_assets', ! is_admin() && ! Helper::is_enabled_assets_generation() ) ) {
175 + if ( apply_filters( 'ablocks/is_allow_block_inline_assets', ( ! is_admin() || defined( 'DOING_AJAX' ) && DOING_AJAX ) && ! Helper::is_enabled_assets_generation() ) ) {
176 +
128 177 $build_css = '<style>' . $this->build_css( $attributes ) . '</style>';
178 +
129 179 return $build_css . $content;
130 180 }
131 181
132 182 return $content;
@@ -131,8 +181,36 @@
131 181
132 182 return $content;
133 183 }
134 184
185 + /**
186 + * file_exists + filemtime for a per-block asset, memoized per request so
187 + * repeated instances of the same block don't re-stat the same file.
188 + * Returns the mtime (cache-bust version) or false when the file is missing.
189 + */
190 + private static $asset_version_cache = [];
191 + private function cached_asset_version( $path ) {
192 + if ( ! array_key_exists( $path, self::$asset_version_cache ) ) {
193 + self::$asset_version_cache[ $path ] = file_exists( $path ) ? filemtime( $path ) : false;
194 + }
195 + return self::$asset_version_cache[ $path ];
196 + }
197 +
198 + /**
199 + * Enqueue the animate.css library only when a block actually uses an
200 + * animation, and only in the fallback (per-block) asset path — with
201 + * asset-generation on, the combined generator bakes it in on demand instead.
202 + */
203 + private function maybe_enqueue_animate_style( $attributes ) {
204 + if ( is_admin() || Helper::is_enabled_assets_generation() ) {
205 + return;
206 + }
207 + $animation_type = isset( $attributes['_animation']['animationType'] ) ? $attributes['_animation']['animationType'] : '';
208 + if ( ! empty( $animation_type ) && 'none' !== $animation_type ) {
209 + wp_enqueue_style( 'ablocks-animate-style' );
210 + }
211 + }
212 +
135 213 private function enqueue_static_assets( $block_name ) {
136 214 // Library
137 215 if ( count( $this->get_style_depends() ) ) {
138 216 foreach ( $this->get_style_depends() as $style_handler ) {
@@ -139,13 +217,8 @@
139 217 wp_enqueue_style( $style_handler );
140 218 }
141 219 }
142 220
143 - // block static css
144 - if ( file_exists( $this->assets_path . 'build/blocks/' . $block_name . '/style.css' ) ) {
145 - wp_enqueue_style( 'ablocks-' . $block_name . '-block-static-style', $this->assets_url . 'build/blocks/' . $block_name . '/style.css', array(), filemtime( $this->assets_path . 'build/blocks/' . $block_name . '/style.css' ), 'all' );
146 - }
147 -
148 221 // Library
149 222 if ( count( $this->get_script_depends() ) ) {
150 223 foreach ( $this->get_script_depends() as $script_handler ) {
151 224 wp_enqueue_script( $script_handler );
@@ -151,24 +224,32 @@
151 224 wp_enqueue_script( $script_handler );
152 225 }
153 226 }
154 227
155 - if ( file_exists( $this->assets_path . 'build/blocks/' . $block_name . '/view.js' ) ) {
228 + // block static css
229 + $style_version = $this->cached_asset_version( $this->assets_path . 'build/blocks/' . $block_name . '/style.css' );
230 + if ( false !== $style_version ) {
231 + wp_enqueue_style( 'ablocks-' . $block_name . '-block-static-style', $this->assets_url . 'build/blocks/' . $block_name . '/style.css', array(), $style_version, 'all' );
232 + }
233 +
234 + $script_loading_strategy = \ABlocks\Helper::get_script_loading_strategy();
235 + $args = [ 'strategy' => $script_loading_strategy ];
236 +
237 + if ( false !== $this->cached_asset_version( $this->assets_path . 'build/blocks/' . $block_name . '/view.js' ) && ! wp_script_is( 'ablocks-' . $block_name . '-block-static-script', 'enqueued' ) ) {
156 238 $dependencies = include $this->assets_path . 'build/blocks/' . $block_name . '/view.asset.php';
239 + // Depend on the shared data-only handle so ABlocksGlobal is printed
240 + // before this script runs, without each block carrying its own copy.
241 + $deps = array_values( array_unique( array_merge( array( 'ablocks-globals' ), $dependencies['dependencies'] ) ) );
157 242 wp_enqueue_script(
158 243 'ablocks-' . $block_name . '-block-static-script',
159 244 $this->assets_url . 'build/blocks/' . $block_name . '/view.js',
160 - $dependencies['dependencies'],
245 + $deps,
161 246 $dependencies['version'],
162 - true
247 + $args
163 248 );
164 - $Assets = new Assets();
165 - wp_localize_script(
166 - 'ablocks-' . $block_name . '-block-static-script',
167 - 'ABlocksGlobal',
168 - $Assets->get_localize_script_data()
169 - );
249 + Assets::localize_globals_once();
170 250 }
251 +
171 252 }
172 253
173 254 public function enqueue_block_static_assets() {
174 255 if ( ! is_admin() && ! Helper::is_enabled_assets_generation() ) {
@@ -185,13 +266,89 @@
185 266 if ( file_exists( $this->assets_path . 'build/blocks/' . $this->block_name . '/style.css' ) ) {
186 267 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
187 268 $has_static_css = file_get_contents( $this->assets_path . 'build/blocks/' . $this->block_name . '/style.css' );
188 269 if ( $has_static_css ) {
189 - return $has_static_css;
270 + // A block's style.css doubles as its editorStyle, so it can carry
271 + // editor-chrome rules (.block-editor-*, .editor-styles-wrapper …)
272 + // that never match on the front end. Strip them from the frontend
273 + // static CSS (this getter feeds the combined/inline page CSS; the
274 + // editor loads style.css directly via block.json).
275 + return self::strip_editor_only_css( $has_static_css );
190 276 }
191 277 }
192 278 return '';
193 279 }
280 +
281 + /**
282 + * Remove rules whose selector is entirely editor-chrome (every
283 + * comma-separated part references an editor-only class), leaving mixed and
284 + * front-end rules untouched. Brace-aware so @media blocks stay intact.
285 + */
286 + private static $editor_css_cache = [];
287 + public static function strip_editor_only_css( $css ) {
288 + $key = md5( $css );
289 + if ( isset( self::$editor_css_cache[ $key ] ) ) {
290 + return self::$editor_css_cache[ $key ];
291 + }
292 + $markers = [ '.block-editor-', '.editor-styles-wrapper', '.block-list-appender', '.components-base-control', '.block-editor-block-variation-picker', '.block-editor-button-block-appender' ];
293 +
294 + $out = '';
295 + $len = strlen( $css );
296 + $i = 0;
297 + $buf = '';
298 + while ( $i < $len ) {
299 + $ch = $css[ $i ];
300 + $buf .= $ch;
301 + if ( '{' === $ch ) {
302 + $prelude = trim( substr( $buf, 0, -1 ) );
303 + $depth = 1;
304 + $i++;
305 + while ( $i < $len && $depth > 0 ) {
306 + $c = $css[ $i ];
307 + $buf .= $c;
308 + if ( '{' === $c ) {
309 + $depth++;
310 + } elseif ( '}' === $c ) {
311 + $depth--;
312 + }
313 + $i++;
314 + }
315 + $drop = false;
316 + if ( '' !== $prelude && '@' !== $prelude[0] ) {
317 + $parts = array_map( 'trim', explode( ',', $prelude ) );
318 + $drop = true;
319 + foreach ( $parts as $part ) {
320 + // Drop :not(...) negations first — a selector like
321 + // ":not(.block-editor-block-list__block) .foo" is a FRONT-END
322 + // rule (applies everywhere except the editor), NOT editor
323 + // chrome, so the marker inside :not() must not count.
324 + $positive = preg_replace( '/:not\([^)]*\)/', '', $part );
325 + $is_editor = false;
326 + foreach ( $markers as $m ) {
327 + if ( false !== strpos( $positive, $m ) ) {
328 + $is_editor = true;
329 + break;
330 + }
331 + }
332 + if ( ! $is_editor ) {
333 + $drop = false; // a front-end part exists — keep the rule
334 + break;
335 + }
336 + }
337 + }
338 + if ( ! $drop ) {
339 + $out .= $buf;
340 + }
341 + $buf = '';
342 + continue;
343 + }
344 + $i++;
345 + }
346 + $out .= $buf;
347 +
348 + self::$editor_css_cache[ $key ] = $out;
349 + return $out;
350 + }
194 351 public function get_static_js() {
195 352 if ( file_exists( $this->assets_path . 'build/blocks/' . $this->block_name . '/view.js' ) ) {
196 353 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
197 354 $has_static_js = file_get_contents( $this->assets_path . 'build/blocks/' . $this->block_name . '/view.js' );
@@ -201,9 +358,14 @@
201 358 }
202 359 return '';
203 360 }
204 361 public function get_style_depends() {
205 - return apply_filters( 'ablocks/block_style_depends', array_merge( $this->style_depends, array( 'ablocks-frontend-google-fonts', 'ablocks-animate-style', 'ablocks-common-style' ) ) );
362 + // NOTE: 'ablocks-animate-style' is intentionally NOT included here. Animation
363 + // CSS is heavy and most blocks don't animate, so it's added on demand only:
364 + // the combined generator adds it when a block's _animation attribute is set
365 + // (AssetsGenerator::recursive_block_parser), and the fallback render path
366 + // enqueues it per-block via maybe_enqueue_animate_style().
367 + return apply_filters( 'ablocks/block_style_depends', array_merge( $this->style_depends, array( 'ablocks-frontend-google-fonts', 'ablocks-common-style' ) ) );
206 368 }
207 369 public function get_script_depends() {
208 370 return apply_filters( 'ablocks/block_script_depends', array_merge( $this->script_depends, array( 'ablocks-common-script' ) ) );
209 371 }