PluginProbe
Twentig Supercharged Block Editor – Blocks, Patterns, Starter Sites, Portfolio / trunk
Twentig Supercharged Block Editor – Blocks, Patterns, Starter Sites, Portfolio vtrunk
2.0.5 2.0.4 2.0.3 2.0.2 trunk 0.0.1 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.9.1 0.9.2 1.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 All 88 releases
twentig / inc / blocks / gallery.php

gallery.php in Twentig Supercharged Block Editor – Blocks, Patterns, Starter Sites, Portfolio trunk, at inc/blocks/gallery.php

298 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server-side customizations for the `core/gallery` block.
4 *
5 * @package twentig
6 */
7
8 defined( 'ABSPATH' ) || exit;
9
10 /**
11 * Sets the lightbox scale attribute to "contain" for images inside a gallery
12 * that has an aspect ratio with image crop disabled.
13 *
14 * @param string $block_content The block content.
15 * @param array $block The full block, including name and attributes.
16 * @return string The unmodified block content.
17 */
18 function twentig_gallery_image_scale_attr( $block_content, $block ) {
19 $attributes = $block['attrs'] ?? array();
20 $scale_contain = isset( $attributes['aspectRatio'] ) && false === ( $attributes['imageCrop'] ?? true );
21 if ( ! $scale_contain ) {
22 return $block_content;
23 }
24
25 $processor = new WP_HTML_Tag_Processor( $block_content );
26 $metadata = array();
27 while ( $processor->next_tag( array( 'tag_name' => 'figure', 'class_name' => 'wp-block-image' ) ) ) {
28 $context = $processor->get_attribute( 'data-wp-context' );
29 if ( $context ) {
30 $data = json_decode( html_entity_decode( $context ), true );
31 if ( ! empty( $data['imageId'] ) ) {
32 $metadata[ $data['imageId'] ] = array( 'scaleAttr' => 'contain' );
33 }
34 }
35 }
36 if ( $metadata ) {
37 wp_interactivity_state( 'core/image', array( 'metadata' => $metadata ) );
38 }
39 return $block_content;
40 }
41 add_filter( 'render_block_core/gallery', 'twentig_gallery_image_scale_attr', 10, 2 );
42
43 /**
44 * Returns Twentig classes for a Gallery block.
45 *
46 * Dynamic galleries do not save a wrapper element, so these classes must be
47 * added to the wrapper generated by Core on the server.
48 *
49 * @param array $attributes Gallery block attributes.
50 * @return string[] Gallery class names.
51 */
52 function twentig_get_gallery_classes( $attributes ) {
53 $layout = $attributes['twLayout'] ?? '';
54 $image_crop = $attributes['imageCrop'] ?? true;
55 $classes = array();
56
57 if ( $layout ) {
58 $classes[] = 'tw-gallery-' . sanitize_html_class( $layout );
59 }
60
61 if ( ! empty( $attributes['twFixedWidthCols'] ) ) {
62 $classes[] = 'tw-fixed-cols';
63 }
64
65 if ( ! empty( $attributes['twImageRatio'] ) && 'justified' !== $layout ) {
66 $classes[] = 'tw-img-ratio-' . sanitize_html_class( $attributes['twImageRatio'] );
67 }
68
69 if ( ! empty( $attributes['twGutter'] ) ) {
70 $classes[] = 'tw-gutter-' . sanitize_html_class( $attributes['twGutter'] );
71 }
72
73 if ( ! empty( $attributes['twColumnWidth'] ) ) {
74 $classes[] = 'tw-cols-' . sanitize_html_class( $attributes['twColumnWidth'] );
75 }
76
77 if (
78 ! empty( $attributes['twVerticalAlignment'] ) &&
79 'justified' !== $layout &&
80 false === $image_crop
81 ) {
82 $classes[] = 'tw-valign-' . sanitize_html_class( $attributes['twVerticalAlignment'] );
83 }
84
85 return $classes;
86 }
87
88 /**
89 * Filters the gallery block output.
90 *
91 * @param string $block_content Rendered block content.
92 * @param array $block Block object.
93 * @return string Filtered block content.
94 */
95 function twentig_filter_gallery_block( $block_content, $block ) {
96
97 $attributes = $block['attrs'] ?? array();
98 $layout = $attributes['twLayout'] ?? null;
99 $animation = $attributes['twAnimation'] ?? '';
100 $is_dynamic = ! empty( $attributes['dynamicContent'] );
101 $classes = $is_dynamic ? twentig_get_gallery_classes( $attributes ) : array();
102
103 if ( ! $layout && ! $animation && ! $classes ) {
104 return $block_content;
105 }
106
107 $tag_processor = new WP_HTML_Tag_Processor( $block_content );
108
109 if ( ! $tag_processor->next_tag() ) {
110 return $block_content;
111 }
112
113 foreach ( $classes as $class_name ) {
114 $tag_processor->add_class( $class_name );
115 }
116
117 if ( $classes ) {
118 $block_content = $tag_processor->get_updated_html();
119 }
120
121 if ( $animation && 'carousel' !== $layout ) {
122 $duration = $attributes['twAnimationDuration'] ?? '';
123 $delay = $attributes['twAnimationDelay'] ?? 0;
124
125 $tag_processor->set_bookmark( 'tw-gallery' );
126 $tag_processor->remove_class( 'tw-block-animation' );
127
128 while ( $tag_processor->next_tag( 'figure' ) ) {
129 if ( ! $tag_processor->has_class( 'tw-block-animation' ) ) {
130 $tag_processor->add_class( 'tw-block-animation' );
131 $tag_processor->add_class( 'tw-animation-' . sanitize_html_class( $animation ) );
132
133 if ( $duration ) {
134 $tag_processor->add_class( 'tw-duration-' . sanitize_html_class( $duration ) );
135 }
136
137 if ( $delay ) {
138 $style_attr = $tag_processor->get_attribute( 'style' );
139 $style = '--tw-animation-delay:' . esc_attr( $delay ) . 's;' . $style_attr;
140 $tag_processor->set_attribute( 'style', $style );
141 }
142 }
143 }
144 $tag_processor->seek( 'tw-gallery' );
145 $block_content = $tag_processor->get_updated_html();
146 }
147
148 if ( 'justified' === $layout ) {
149 $rowHeight = (int) ( $attributes['twRowHeight'] ?? 250 );
150 $crop = $attributes['imageCrop'] ?? true;
151 $tag_processor->remove_class( 'columns-default' );
152
153 $style_attr = $tag_processor->get_attribute( 'style' );
154 $style = '--tw-row-height:' . esc_attr( $rowHeight ) . 'px;' . $style_attr;
155 $tag_processor->set_attribute( 'style', $style );
156
157 if ( $crop ) {
158 while ( $tag_processor->next_tag( 'figure' ) ) {
159 $tag_processor->set_bookmark( 'figure' );
160 $tag_processor->next_tag( 'img' );
161
162 $ratio = '';
163 $attachment_id = absint( $tag_processor->get_attribute( 'data-id' ) );
164 $tag_processor->set_attribute( 'decoding', 'auto' );
165
166 if ( ! $attachment_id ) {
167 $class = $tag_processor->get_attribute( 'class' );
168 if ( ! empty( $class ) && preg_match( '/wp-image-([0-9]+)/i', $class, $class_id ) ) {
169 $attachment_id = absint( $class_id[1] );
170 }
171 }
172
173 if ( $attachment_id ) {
174 $metadata = wp_get_attachment_metadata( $attachment_id );
175 if ( is_array( $metadata ) && isset( $metadata['width'], $metadata['height'] ) && (int) $metadata['height'] !== 0 ) {
176 $ratio = round( (int) $metadata['width'] / (int) $metadata['height'], 6 );
177 }
178 } else {
179 $image_src = $tag_processor->get_attribute( 'src' );
180 if ( $image_src ) {
181 $query = wp_parse_url( str_replace( '&amp;', '&', $image_src ), PHP_URL_QUERY );
182 if ( $query ) {
183 $query_params = wp_parse_args( $query );
184 if ( isset( $query_params['w'], $query_params['h'] ) && is_numeric( $query_params['w'] ) && is_numeric( $query_params['h'] ) && (int) $query_params['h'] !== 0 ) {
185 $ratio = round( (int) $query_params['w'] / (int) $query_params['h'], 6 );
186 }
187 }
188 }
189 }
190
191 if ( $ratio ) {
192 $tag_processor->seek( 'figure' );
193 $style_attr = $tag_processor->get_attribute( 'style' );
194 $style = '--tw-ratio:' . esc_attr( $ratio ) . ';' . $style_attr;
195 $tag_processor->set_attribute( 'style', $style );
196 }
197 }
198 }
199
200 $block_content = $tag_processor->get_updated_html();
201
202 } elseif ( 'carousel' === $layout ) {
203
204 wp_enqueue_script_module(
205 'tw-block-gallery',
206 TWENTIG_ASSETS_URI . '/blocks/gallery/view.js',
207 array( '@wordpress/interactivity' ),
208 TWENTIG_VERSION
209 );
210
211 $slides_count = 0;
212 $slides_processor = new WP_HTML_Tag_Processor( $block_content );
213 while ( $slides_processor->next_tag( array( 'tag_name' => 'figure', 'class_name' => 'wp-block-image' ) ) ) {
214 ++$slides_count;
215 }
216
217 $settings = $attributes['twCarousel'] ?? array();
218 $arrows_position = $settings['arrowsPosition'] ?? 'overlay';
219 $markers_position = $settings['markersPosition'] ?? 'below';
220 $columns = (int) ( $attributes['columns'] ?? 3 );
221 $view = $settings['view'] ?? '';
222 $show_edges = 'adjacent-images' === $view;
223
224 if ( 1 !== $columns || str_contains( $arrows_position, 'below' ) ) {
225 $markers_position = 'none';
226 }
227
228 $tag_processor->set_attribute( 'role', 'region' );
229 $tag_processor->set_attribute( 'aria-roledescription', __( 'carousel', 'twentig' ) );
230 $tag_processor->set_attribute( 'aria-label', __( 'Image gallery', 'twentig' ) );
231 $tag_processor->set_attribute( 'data-wp-interactive', 'twentig/carousel' );
232 $tag_processor->set_attribute( 'data-wp-init', 'callbacks.initCarousel' );
233 $tag_processor->set_attribute(
234 'data-wp-context',
235 wp_json_encode(
236 array(
237 'showEdges' => $show_edges,
238 'currentIndex' => 0,
239 'maxIndex' => $slides_count - 1,
240 ),
241 JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
242 )
243 );
244
245 if ( $view ) {
246 $tag_processor->add_class( 'tw-' . sanitize_html_class( $view ) );
247 }
248
249 $block_content = $tag_processor->get_updated_html();
250
251 $controls_html = '';
252
253 if ( 'none' !== $arrows_position ) {
254 $controls_html .= '<div class="tw-carousel-arrows is-position-' . esc_attr( $arrows_position ) . '">';
255 $controls_html .= '<button type="button" class="tw-carousel-arrow tw-carousel-arrow-left" aria-label="' . esc_attr__( 'Previous slide', 'twentig' ) . '" data-wp-on--click="actions.goPrevious" data-wp-bind--disabled="state.isAtStart"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><path d="m13.6 18-6-6 6-6L15 7.4 10.4 12l4.6 4.6-1.4 1.4Z"></path></svg></button>';
256 $controls_html .= '<button type="button" class="tw-carousel-arrow tw-carousel-arrow-right" aria-label="' . esc_attr__( 'Next slide', 'twentig' ) . '" data-wp-on--click="actions.goNext" data-wp-bind--disabled="state.isAtEnd"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><path d="M13.6 12 9 7.4 10.4 6l6 6-6 6L9 16.6l4.6-4.6Z"></path></svg></button>';
257 $controls_html .= '</div>';
258 }
259
260 if ( 'none' !== $markers_position ) {
261 $controls_html .= '<ul class="tw-gallery-carousel-nav is-position-' . esc_attr( $markers_position ) . '">';
262
263 for ( $i = 1; $i <= $slides_count; $i++ ) {
264 /* translators: %s: image number */
265 $button_text = sprintf( __( 'Image %s', 'twentig' ), $i );
266 $context = array(
267 'index' => $i - 1,
268 );
269
270 $controls_html .= sprintf(
271 '<li><button type="button" aria-label="%2$s" data-wp-on--click="actions.goToIndex" data-wp-class--selected="state.isMarkerActive" data-wp-bind--aria-current="state.isMarkerActive" data-wp-context="%1$s"></button></li>',
272 htmlspecialchars( wp_json_encode( $context ), ENT_QUOTES, 'UTF-8' ),
273 esc_attr( $button_text )
274 );
275 }
276 $controls_html .= '</ul>';
277 }
278
279
280
281 $replace_regex = sprintf(
282 '/(^\s*<%1$s\b[^>]*\bwp-block-gallery\b[^>]*>)(.*)(<\/%1$s>\s*$)/ms',
283 preg_quote( 'figure', '/' )
284 );
285
286 $block_content = preg_replace_callback(
287 $replace_regex,
288 static function ( $matches ) use ( $controls_html ) {
289 return $matches[1] . '<div class="tw-gallery-carousel-list">' . $matches[2] . '</div>' . $controls_html . $matches[3];
290 },
291 $block_content
292 );
293 }
294
295 return $block_content;
296 }
297 add_filter( 'render_block_core/gallery', 'twentig_filter_gallery_block', 10, 2 );
298