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
ablocks / includes / blocks / table-of-content / block.php

block.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.14.0, at includes/blocks/table-of-content/block.php

406 lines 15.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ABlocks\Blocks\TableOfContent;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 use ABlocks\Classes\BlockBaseAbstract;
9 use ABlocks\Classes\CssGenerator;
10 use ABlocks\Classes\CssGeneratorV2;
11 use ABlocks\Helper;
12 use ABlocks\Controls\Typography;
13 use ABlocks\Controls\Background;
14 use ABlocks\Controls\Border;
15 use ABlocks\Controls\Dimensions;
16 use ABlocks\Controls\Alignment;
17 use ABlocks\Controls\Range;
18 use ABlocks\Controls\Color;
19 use ABlocks\Controls\BoxShadow;
20
21 class Block extends BlockBaseAbstract {
22 protected $block_name = 'table-of-content';
23
24
25 public function build_css_v1( $attributes ) {
26 $css_generator = new CssGenerator( $attributes );
27
28 $css_generator->add_class_styles(
29 '{{WRAPPER}} .ablocks-toc__header-title',
30 $this->get_toc_title_css( $attributes ),
31 $this->get_toc_title_css( $attributes, 'Tablet' ),
32 $this->get_toc_title_css( $attributes, 'Mobile' ),
33 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_toc_title_css( $attributes, $device ); } )
34 );
35 $css_generator->add_class_styles(
36 '{{WRAPPER}} .ablocks-toc__header',
37 $this->get_toc_header_css( $attributes ),
38 $this->get_toc_header_css( $attributes, 'Tablet' ),
39 $this->get_toc_header_css( $attributes, 'Mobile' ),
40 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_toc_header_css( $attributes, $device ); } )
41 );
42 $css_generator->add_class_styles(
43 '{{WRAPPER}} .ablocks-toc-body',
44 $this->get_toc_body_css( $attributes ),
45 $this->get_toc_body_css( $attributes, 'Tablet' ),
46 $this->get_toc_body_css( $attributes, 'Mobile' ),
47 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_toc_body_css( $attributes, $device ); } )
48 );
49 $css_generator->add_class_styles(
50 '{{WRAPPER}} .ablocks-toc__header-toggle-icon .ablocks-toc__show',
51 $this->get_toc_header_icon_css( $attributes ),
52 $this->get_toc_header_icon_css( $attributes, 'Tablet' ),
53 $this->get_toc_header_icon_css( $attributes, 'Mobile' ),
54 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_toc_header_icon_css( $attributes, $device ); } )
55 );
56 $css_generator->add_class_styles(
57 '{{WRAPPER}} .ablocks-toc__header-toggle-icon .ablocks-toc__show:hover',
58 $this->get_toc_header_icon_hover_css( $attributes ),
59 $this->get_toc_header_icon_hover_css( $attributes, 'Tablet' ),
60 $this->get_toc_header_icon_hover_css( $attributes, 'Mobile' ),
61 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_toc_header_icon_hover_css( $attributes, $device ); } )
62 );
63 $css_generator->add_class_styles(
64 '{{WRAPPER}} .ablocks-block-container .ablocks-toc-list,
65 {{WRAPPER}} .ablocks-block-container .ablocks-toc-list li a',
66 $this->get_list_item_css( $attributes ),
67 $this->get_list_item_css( $attributes, 'Tablet' ),
68 $this->get_list_item_css( $attributes, 'Mobile' ),
69 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_list_item_css( $attributes, $device ); } )
70 );
71
72 return $css_generator->generate_css();
73 }
74 public function build_css_v2( $attributes ) {
75 $css_generator = new CssGeneratorV2( $attributes, $this->block_name );
76
77 $css_generator->add_class_styles(
78 '{{WRAPPER}} .ablocks-toc__header-title',
79 $this->get_toc_title_css( $attributes ),
80 $this->get_toc_title_css( $attributes, 'Tablet' ),
81 $this->get_toc_title_css( $attributes, 'Mobile' ),
82 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_toc_title_css( $attributes, $device ); } )
83 );
84 $css_generator->add_class_styles(
85 '{{WRAPPER}} .ablocks-toc__header',
86 $this->get_toc_header_css( $attributes ),
87 $this->get_toc_header_css( $attributes, 'Tablet' ),
88 $this->get_toc_header_css( $attributes, 'Mobile' ),
89 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_toc_header_css( $attributes, $device ); } )
90 );
91 $css_generator->add_class_styles(
92 '{{WRAPPER}} .ablocks-toc-body',
93 $this->get_toc_body_css( $attributes ),
94 $this->get_toc_body_css( $attributes, 'Tablet' ),
95 $this->get_toc_body_css( $attributes, 'Mobile' ),
96 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_toc_body_css( $attributes, $device ); } )
97 );
98 $css_generator->add_class_styles(
99 '{{WRAPPER}} .ablocks-toc__header-toggle-icon .ablocks-toc__show',
100 $this->get_toc_header_icon_css( $attributes ),
101 $this->get_toc_header_icon_css( $attributes, 'Tablet' ),
102 $this->get_toc_header_icon_css( $attributes, 'Mobile' ),
103 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_toc_header_icon_css( $attributes, $device ); } )
104 );
105 $css_generator->add_class_styles(
106 '{{WRAPPER}} .ablocks-toc__header-toggle-icon .ablocks-toc__show:hover',
107 $this->get_toc_header_icon_hover_css( $attributes ),
108 $this->get_toc_header_icon_hover_css( $attributes, 'Tablet' ),
109 $this->get_toc_header_icon_hover_css( $attributes, 'Mobile' ),
110 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_toc_header_icon_hover_css( $attributes, $device ); } )
111 );
112 $css_generator->add_class_styles(
113 '{{WRAPPER}} .ablocks-toc-body .ablocks-toc-list',
114 $this->get_marker_list_style_css( $attributes ),
115 $this->get_marker_list_style_css( $attributes, 'Tablet' ),
116 $this->get_marker_list_style_css( $attributes, 'Mobile' ),
117 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_marker_list_style_css( $attributes, $device ); } )
118 );
119 $css_generator->add_class_styles(
120 '{{WRAPPER}} .ablocks-toc-list,
121 {{WRAPPER}} .ablocks-toc-list li a',
122 $this->get_list_item_css( $attributes ),
123 $this->get_list_item_css( $attributes, 'Tablet' ),
124 $this->get_list_item_css( $attributes, 'Mobile' ),
125 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_list_item_css( $attributes, $device ); } )
126 );
127 $css_generator->add_class_styles(
128 '{{WRAPPER}} .ablocks-toc-body .ablocks-toc-list li a',
129 $this->get_list_item_gap_css( $attributes ),
130 $this->get_list_item_gap_css( $attributes, 'Tablet' ),
131 $this->get_list_item_gap_css( $attributes, 'Mobile' ),
132 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_list_item_gap_css( $attributes, $device ); } )
133 );
134 $css_generator->add_class_styles(
135 '{{WRAPPER}} a.ablocks-toc-item-link.active',
136 $this->get_active_list_item_css( $attributes ),
137 $this->get_active_list_item_css( $attributes, 'Tablet' ),
138 $this->get_active_list_item_css( $attributes, 'Mobile' ),
139 $css_generator->custom_device_map( function ( $device ) use ( $attributes ) { return $this->get_active_list_item_css( $attributes, $device ); } )
140 );
141
142 return $css_generator->generate_css();
143 }
144
145 public function build_css( $attributes ) {
146 if ( isset( $attributes['blockVersion'] ) && (int) $attributes['blockVersion'] === 2 ) {
147 return $this->build_css_v2( $attributes );
148 }
149 return $this->build_css_v1( $attributes );
150 }
151
152
153 public function get_toc_title_css( $attributes, $device = '' ) {
154 $typographyValueGlobal = ! empty( $attributes['titleTypographyGlobal'] ) ? $attributes['titleTypographyGlobal'] : array();
155 $toc_title_typography_css = ! empty( $attributes['titleTypography'] ) ? Typography::get_css( $attributes['titleTypography'], '', $device, $typographyValueGlobal ) : array();
156 return array_merge( $toc_title_typography_css,
157 [ 'color' => Color::get_css( isset( $attributes['titleColor'] ) ? $attributes['titleColor'] : '' ) ], );
158 }
159 public function get_toc_header_css( $attributes, $device = '' ) {
160 $headerorder = ! empty( $attributes['headerBorder'] ) ? Border::get_css( $attributes['headerBorder'], '', $device ) : array();
161 $header_padding_css = ! empty( $attributes['header_padding'] ) ? Dimensions::get_css( $attributes['header_padding'], 'padding', $device ) : array();
162 return array_merge(
163 [ 'background' => Color::get_css( isset( $attributes['headerBG'] ) ? $attributes['headerBG'] : '' ) ],
164 $headerorder, $header_padding_css );
165 }
166 public function get_toc_body_css( $attributes, $device = '' ) {
167 $listing_padding_css = ! empty( $attributes['list_padding'] ) ? Dimensions::get_css( $attributes['list_padding'], 'padding', $device ) : array();
168 return array_merge(
169 [ 'background' => Color::get_css( isset( $attributes['bodyBG'] ) ? $attributes['bodyBG'] : '' ) ],
170 $listing_padding_css, );
171 }
172 public function get_toc_header_icon_css( $attributes, $device = '' ) {
173 $css = array_merge(
174 [ 'fill' => Color::get_css( isset( $attributes['iconColor'] ) ? $attributes['iconColor'] : '' ) ],
175 Range::get_css([
176 'attributeValue' => $attributes['iconSize'] ?? null,
177 'isResponsive' => false,
178 'defaultValue' => 20,
179 'unitDefaultValue' => 'px',
180 'property' => 'font-size',
181 'device' => $device,
182 ]),
183 Dimensions::get_css( $attributes['icon_padding'] ?? [], 'padding', $device ),
184 Border::get_css( $attributes['iconBorder'] ?? [], '', $device ),
185 BoxShadow::get_css( $attributes['iconBoxShadow'] ?? [], $device ),
186 );
187 return $css;
188 }
189
190 public function get_toc_header_icon_hover_css( $attributes, $device = '' ) {
191 $css = array_merge(
192 Border::get_hover_css( $attributes['iconBorder'], $device ),
193 BoxShadow::get_hover_css( $attributes['iconBoxShadow'], $device )
194 );
195 return $css;
196 }
197
198 public function get_list_item_css( $attributes, $device = '' ) {
199 $typographyValueGlobal = ! empty( $attributes['contentTypographyGlobal'] ) ? $attributes['contentTypographyGlobal'] : array();
200 $contentTypography = ! empty( $attributes['contentTypography'] ) ? Typography::get_css( $attributes['contentTypography'], '', $device, $typographyValueGlobal ) : array();
201 return array_merge(
202 [ 'color' => Color::get_css( isset( $attributes['itemColor'] ) ? $attributes['itemColor'] : '' ) ],
203 $contentTypography,
204 );
205 }
206
207 public function get_list_item_gap_css( $attributes, $device = '' ) {
208 return array_merge(
209 Range::get_css([
210 'attributeValue' => $attributes['listItemGap'],
211 'attribute_object_key' => 'value',
212 'isResponsive' => false,
213 'defaultValue' => 30,
214 'unitDefaultValue' => 'px',
215 'property' => 'line-height',
216 'hasUnit' => true,
217 'device' => $device,
218 ]),
219 );
220 }
221 public function get_active_list_item_css( $attributes, $device = '' ) {
222 return array_merge(
223 [ 'color' => Color::get_css( isset( $attributes['activeColor'] ) ? $attributes['activeColor'] : '' ) ],
224 );
225 }
226
227
228 public function get_marker_list_style_css( $attributes ) {
229 $css = [];
230 $allowed_list_types = [
231 'decimal',
232 'disc',
233 'circle',
234 'square',
235 'lower-alpha',
236 'lower-roman',
237 'none'
238 ];
239 if ( ! empty( $attributes['markerView'] ) && in_array( $attributes['markerView'], $allowed_list_types, true ) ) {
240 $css['list-style-type'] = $attributes['markerView'];
241 }
242 return $css;
243 }
244
245
246
247 public function add_toc_to_post_content( $content ) {
248
249 $content = preg_replace_callback('/<h([1-6])(.*?)>(.*?)<\/h\1>/i', function ( $matches ) {
250 $level = $matches[1]; // Heading level (1-6)
251 $attributes = $matches[2]; // Existing attributes (classes, styles, etc.)
252 $heading = $matches[3]; // Heading text/content
253 $anchor = sanitize_title( $heading ); // Generate a sanitized ID
254 // Check if the id attribute is already present
255 if ( strpos( $attributes, 'id=' ) === false ) {
256 // Insert the id attribute after the opening <h> tag, preserving existing attributes
257 return '<h' . $level . $attributes . ' id="' . $anchor . '">' . $heading . '</h' . $level . '>';
258 }
259 return $matches[0]; // If id is already present, return the original match
260 }, $content);
261 return $content;
262 }
263
264
265 public function render_block_content( $attributes, $content, $block_instance ) {
266 $post = get_post();
267 if ( ! $post ) {
268 return '';
269 }
270
271 add_filter( 'the_content', [ $this, 'add_toc_to_post_content' ] );
272
273 // Sanitize and escape icon attributes
274 $open_icon_attributes = array(
275 'path' => esc_attr( $attributes['openIconSvgPath'] ),
276 'viewBox' => esc_attr( $attributes['openIconSvgViewBox'] ),
277 'className' => sanitize_text_field( $attributes['openIconClass'] ),
278 'width' => '20',
279 'height' => '20',
280 );
281 $close_icon_attributes = array(
282 'path' => esc_attr( $attributes['closeIconSvgPath'] ),
283 'viewBox' => esc_attr( $attributes['closeIconSvgViewBox'] ),
284 'className' => sanitize_text_field( $attributes['closeIconClass'] ),
285 'width' => '20',
286 'height' => '20',
287 );
288
289 $post_content = $post->post_content;
290 preg_match_all( '/<h([1-6])[^>]*>(.*?)<\/h\1>/', $post_content, $matches, PREG_SET_ORDER );
291 $toc = '';
292
293 $headings = [];
294 $unique_anchors = [];
295
296 // Process headings
297 foreach ( $matches as $match ) {
298 $level = intval( $match[1] );
299 $heading_html = trim( $match[2] );
300 $heading = esc_html( wp_strip_all_tags( $heading_html ) );
301
302 $base_anchor = strtolower( sanitize_title( $heading ) );
303 $anchor = $base_anchor;
304 $count = 1;
305
306 while ( in_array( $anchor, $unique_anchors, true ) ) {
307 $anchor = $base_anchor . '-' . $count;
308 $count++;
309 }
310
311 if ( (
312 ( $level === 1 && $attributes['H1'] ) ||
313 ( $level === 2 && $attributes['H2'] ) ||
314 ( $level === 3 && $attributes['H3'] ) ||
315 ( $level === 4 && $attributes['H4'] ) ||
316 ( $level === 5 && $attributes['H5'] ) ||
317 ( $level === 6 && $attributes['H6'] ) ) ) {
318
319 $headings[] = [
320 'level' => $level,
321 'heading' => $heading,
322 'anchor' => esc_attr( $anchor ),
323 ];
324 $unique_anchors[] = $anchor;
325 }
326 }//end foreach
327
328 $toc_list = $this->generate_toc_list( $attributes, $headings );
329 $has_toc = $toc_list !== '';
330
331 // Build TOC header
332 if ( (bool) $attributes['hideTitle'] === true ) :
333 $toc .= '<div class="ablocks-toc__header">';
334 $toc .= $has_toc ? '<span class="ablocks-toc__header-title">' . esc_html( $attributes['tocTableTitle'] ) . '</span>' : '';
335 if ( $attributes['collapSible'] ) :
336 $toc .= '<div class="ablocks-toc__header-toggle-icon">';
337 $toc .= '<span class="ablocks-toc__show">' . Helper::render_svg_icon_using_attr( $close_icon_attributes ) . '</span>';
338 $toc .= '<span class="ablocks-toc__hide">' . Helper::render_svg_icon_using_attr( $open_icon_attributes ) . '</span>';
339 $toc .= '</div>';
340 endif;
341 $toc .= '</div>';
342 endif;
343
344 // Build TOC body
345 $toc .= '<div class="ablocks-toc-body">';
346 $toc .= $toc_list;
347 $toc .= '</div>';
348
349 return $toc;
350 }
351
352 private function generate_toc_list( $attributes, $headings ) {
353 if ( empty( $headings ) ) {
354 return '';
355 }
356
357 $toc = '';
358 $marker_view = 'ol';
359 $current_level = 0;
360 $open_lists = [];
361 foreach ( $headings as $index => $heading ) {
362 if ( ! isset( $heading['level'], $heading['heading'], $heading['anchor'] ) ) {
363 continue;
364 }
365
366 $level = (int) $heading['level'];
367
368 // If the first item, open the root list
369 if ( $index === 0 ) {
370 $toc .= '<' . esc_attr( $marker_view ) . ' class="ablocks-toc-list">';
371 $open_lists[] = $marker_view;
372 $current_level = $level;
373 }
374
375 // If deeper heading level, open a nested list
376 while ( $level > $current_level ) {
377 $toc .= '<' . esc_attr( $marker_view ) . ' class="ablocks-toc-list">';
378 $open_lists[] = $marker_view;
379 $current_level++;
380 }
381
382 // If shallower heading level, close open lists
383 while ( $level < $current_level ) {
384 $toc .= '</' . esc_attr( array_pop( $open_lists ) ) . '>';
385 $current_level--;
386 }
387
388 // Close previous <li> before adding a new one (except for the first)
389 if ( $index > 0 ) {
390 $toc .= '</li>';
391 }
392
393 // Add list item
394 $toc .= '<li class="ablocks-toc-item">';
395 $toc .= '<a class="ablocks-toc-item-link" href="#' . esc_attr( $heading['anchor'] ) . '">' . esc_html( $heading['heading'] ) . '</a>';
396 }//end foreach
397
398 // Close any remaining open lists
399 while ( ! empty( $open_lists ) ) {
400 $toc .= '</li></' . esc_attr( array_pop( $open_lists ) ) . '>';
401 }
402
403 return $toc;
404 }
405 }
406