PluginProbe
Tableberg – Simple Gutenberg Table Block / 1.0.4
Tableberg – Simple Gutenberg Table Block v1.0.4
1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 trunk 0.0.2 0.2.1 0.3.2 0.3.3 0.4.1 0.5.0 0.5.1 0.5.2 0.5.3 0.5.4 0.5.5 0.5.6 0.5.7 All 42 releases
tableberg / renderer / Table / TableRenderer.php

TableRenderer.php in Tableberg – Simple Gutenberg Table Block 1.0.4, at renderer/Table/TableRenderer.php

567 lines 19.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Tableberg\Renderer\Table;
4
5 use Tableberg\Renderer\Cell\CellRenderer;
6 use Tableberg\Renderer\Cell\CellRenderContext;
7 use Tableberg\Renderer\Attrs\StringAttr;
8
9 class TableRenderer {
10 /**
11 * @return bool
12 */
13 private function is_pro_active() {
14 if (!function_exists('tp_fs')) {
15 return false;
16 }
17
18 $fs = call_user_func('tp_fs');
19
20 return is_object($fs) && $fs->can_use_premium_code();
21 }
22
23 private function has_visible_border($border) {
24 $trimmed_border = trim($border);
25
26 if ($trimmed_border === '') {
27 return false;
28 }
29
30 $parts = preg_split('/\s+/', $trimmed_border, 3);
31 $width = $parts[0] ?? '';
32 $style = $parts[1] ?? '';
33
34 if ($width === 'none' || $width === 'hidden') {
35 return false;
36 }
37
38 if (preg_match('/^0(?:\.0+)?(?:[a-z%]+)?$/i', $width) === 1) {
39 return false;
40 }
41
42 if ($style === 'none' || $style === 'hidden') {
43 return false;
44 }
45
46 return true;
47 }
48
49 public function render($attributes) {
50 $attrs = TableAttrs::from_array($attributes);
51 $isProActive = $this->is_pro_active();
52
53 $rows = $attrs->table->rows->value();
54 $cols = $attrs->table->cols->value();
55
56 $headerEnabled = $attrs->table->headerEnabled->value();
57 $footerEnabled = $attrs->table->footerEnabled->value();
58 $stickyHeader = $attrs->table->stickyHeader->value();
59 $caption = $attrs->table->caption;
60 $tableWidth = trim($attrs->table->tableWidth->asAttr());
61 $tableAlignment = $attrs->table->tableAlignment->asAttr();
62 $cellSpacingHorizontal = trim($attrs->table->cellSpacing->horizontal->asAttr());
63 $cellSpacingVertical = trim($attrs->table->cellSpacing->vertical->asAttr());
64 $tableBorderTop = trim($attrs->table->tableBorder->top->asAttr());
65 $tableBorderRight = trim($attrs->table->tableBorder->right->asAttr());
66 $tableBorderBottom = trim($attrs->table->tableBorder->bottom->asAttr());
67 $tableBorderLeft = trim($attrs->table->tableBorder->left->asAttr());
68 $fixedColumnWidths = $attrs->table->fixedColumnWidths->value();
69
70 $isHorizontalSpacingZero = $cellSpacingHorizontal === '0';
71 $isVerticalSpacingZero = $cellSpacingVertical === '0';
72 $hasCellSpacing = !$isHorizontalSpacingZero || !$isVerticalSpacingZero;
73
74 $isWideWidth = $tableWidth === 'wide';
75 $isFullWidth = $tableWidth === 'full';
76
77 $canApplyCustomWidth =
78 $tableWidth !== '' &&
79 !$isWideWidth &&
80 !$isFullWidth &&
81 $tableWidth !== 'auto';
82
83 if (!$canApplyCustomWidth) {
84 $tableWidth = '';
85 }
86
87 $wrapperAlignmentClass = $this->getWrapperAlignmentClass(
88 $isWideWidth,
89 $isFullWidth,
90 $canApplyCustomWidth,
91 $tableAlignment
92 );
93
94 $tableStyles = [
95 'border-collapse: ' . ($hasCellSpacing ? 'separate' : 'collapse'),
96 ];
97
98 if ($hasCellSpacing) {
99 $tableStyles[] = "border-spacing: {$cellSpacingHorizontal} {$cellSpacingVertical}";
100 }
101
102 if ($tableWidth !== '') {
103 $tableStyles[] = "width: {$tableWidth}";
104 $tableStyles[] = "max-width: {$tableWidth}";
105 } else {
106 $tableStyles[] = 'width: 100%';
107 }
108
109 if ($tableBorderTop !== '') {
110 $tableStyles[] = "border-top: {$tableBorderTop}";
111 }
112
113 if ($tableBorderRight !== '') {
114 $tableStyles[] = "border-right: {$tableBorderRight}";
115 }
116
117 if ($tableBorderBottom !== '') {
118 $tableStyles[] = "border-bottom: {$tableBorderBottom}";
119 }
120
121 if ($tableBorderLeft !== '') {
122 $tableStyles[] = "border-left: {$tableBorderLeft}";
123 }
124
125 $tableStyleAttr = "style='" . implode('; ', $tableStyles) . ";'";
126
127 $tableClasses = ['wp-block-tableberg'];
128
129 if ($this->has_visible_border($tableBorderTop)) {
130 $tableClasses[] = 'tableberg-has-table-border-top';
131 }
132
133 if ($this->has_visible_border($tableBorderRight)) {
134 $tableClasses[] = 'tableberg-has-table-border-right';
135 }
136
137 if ($this->has_visible_border($tableBorderBottom)) {
138 $tableClasses[] = 'tableberg-has-table-border-bottom';
139 }
140
141 if ($this->has_visible_border($tableBorderLeft)) {
142 $tableClasses[] = 'tableberg-has-table-border-left';
143 }
144
145 if ($hasCellSpacing) {
146 $tableClasses[] = 'tableberg-has-cell-spacing';
147
148 if ($isHorizontalSpacingZero) {
149 $tableClasses[] = 'tableberg-cell-spacing-horizontal-zero';
150 }
151
152 if ($isVerticalSpacingZero) {
153 $tableClasses[] = 'tableberg-cell-spacing-vertical-zero';
154 }
155 }
156
157 $tableClassAttr = implode(' ', $tableClasses);
158
159 $sortableColumns = [];
160
161 if ($isProActive) {
162 foreach ($attrs->columns as $column => $columnConfig) {
163 if ($columnConfig->sortable === null) {
164 continue;
165 }
166
167 $sortableColumns[$column] = $columnConfig->sortable->asAttr();
168 }
169 }
170
171 $paginationPageSize = (int) $attrs->table->pagination->pageSize->value();
172 if ($paginationPageSize < 1) {
173 $paginationPageSize = 1;
174 }
175
176 $paginationConfig = [
177 'enabled' => $isProActive && $attrs->table->pagination->enabled->value(),
178 'pageSize' => $paginationPageSize,
179 'showPageNumbers' => $attrs->table->pagination->showPageNumbers->value(),
180 'showPrevNext' => $attrs->table->pagination->showPrevNext->value(),
181 ];
182
183 $searchEnabledAsStr = (
184 $isProActive && $attrs->table->search->enabled->value()
185 ) ? 'true' : 'false';
186 $searchPlaceholder = $isProActive
187 ? $attrs->table->search->placeholder->asAttr()
188 : '';
189 $searchPosition = $isProActive
190 ? $attrs->table->search->position->asAttr()
191 : 'left';
192 $searchHighlightColor = $isProActive
193 ? $attrs->table->search->highlightColor->asAttr()
194 : '';
195 $responsiveDataAttrs = $this->buildResponsiveDataAttrs($attrs, $rows, $cols);
196
197 $globalCellStyles = [
198 'padding' => [
199 'top' => $attrs->cellDefaults->styles->padding->top->asAttr(),
200 'right' => $attrs->cellDefaults->styles->padding->right->asAttr(),
201 'bottom' => $attrs->cellDefaults->styles->padding->bottom->asAttr(),
202 'left' => $attrs->cellDefaults->styles->padding->left->asAttr(),
203 ],
204 'orientation' => $attrs->cellDefaults->styles->orientation->asAttr(),
205 'elementGap' => $attrs->cellDefaults->styles->elementGap->asAttr(),
206 'wrap' => $attrs->cellDefaults->styles->wrap->asAttr(),
207 'verticalAlign' => $attrs->cellDefaults->styles->verticalAlign->asAttr(),
208 'backgroundColor' => $attrs->cellDefaults->styles->backgroundColor->asAttr(),
209 'border' => [
210 'top' => $attrs->cellDefaults->styles->border->top->asAttr(),
211 'right' => $attrs->cellDefaults->styles->border->right->asAttr(),
212 'bottom' => $attrs->cellDefaults->styles->border->bottom->asAttr(),
213 'left' => $attrs->cellDefaults->styles->border->left->asAttr(),
214 ],
215 'borderRadius' => [
216 'topLeft' => $attrs->cellDefaults->styles->borderRadius->topLeft->asAttr(),
217 'topRight' => $attrs->cellDefaults->styles->borderRadius->topRight->asAttr(),
218 'bottomRight' => $attrs->cellDefaults->styles->borderRadius->bottomRight->asAttr(),
219 'bottomLeft' => $attrs->cellDefaults->styles->borderRadius->bottomLeft->asAttr(),
220 ],
221 ];
222
223 $cellRenderer = new CellRenderer();
224 $hiddenBySpan = [];
225
226 $rowsHtml = '';
227
228 for ($row = 0; $row < $rows; $row++) {
229 $cellsHtml = '';
230 $rowHeight = $this->getRowHeight($row, $attrs->rows);
231
232 for ($col = 0; $col < $cols; $col++) {
233 if (isset($hiddenBySpan[$row][$col])) {
234 continue;
235 }
236
237 $cell = $this->getCell($row, $col, $attrs->cells);
238 $span = $this->getCellSpan($cell);
239
240 $rowSpan = is_numeric($span['rowSpan']) ? (int) $span['rowSpan'] : 1;
241 $colSpan = is_numeric($span['colSpan']) ? (int) $span['colSpan'] : 1;
242
243 if ($rowSpan > 1 || $colSpan > 1) {
244 for ($rowOffset = 0; $rowOffset < $rowSpan; $rowOffset++) {
245 for ($colOffset = 0; $colOffset < $colSpan; $colOffset++) {
246 if ($rowOffset === 0 && $colOffset === 0) {
247 continue;
248 }
249
250 $hiddenRow = $row + $rowOffset;
251 $hiddenCol = $col + $colOffset;
252 $hiddenBySpan[$hiddenRow][$hiddenCol] = true;
253 }
254 }
255 }
256
257 $isHeaderRowCell = $headerEnabled && $row === 0;
258 $isFooterCell = $footerEnabled && $rows > 0 && $row === $rows - 1;
259
260 $tag = ($isHeaderRowCell || $isFooterCell) ? 'th' : 'td';
261
262 $sortableType = (
263 $isHeaderRowCell &&
264 array_key_exists($col, $sortableColumns)
265 ) ? $sortableColumns[$col] : null;
266
267 $columnWidth = null;
268 if ($colSpan === 1) {
269 if ($fixedColumnWidths && $cols > 0) {
270 $columnWidth = (string) (100 / $cols) . '%';
271 } else {
272 $columnWidth = $this->getColumnWidth(
273 $col,
274 $attrs->columns
275 );
276 }
277 }
278
279 $cellsHtml .= $cellRenderer->render(
280 CellRenderContext::create(
281 $row,
282 $col,
283 $rowSpan,
284 $colSpan,
285 $tag,
286 $globalCellStyles,
287 $this->getCellElements($cell),
288 $this->getCellStyleOverride($cell),
289 $this->getCellRibbon($cell),
290 $sortableType,
291 $columnWidth,
292 $rowHeight,
293 $stickyHeader,
294 $this->getCellClassName($cell)
295 )
296 );
297 }
298
299 $rowsHtml .= "<tr data-tableberg-row='{$row}'>$cellsHtml</tr>";
300 }
301
302 $sortingBoolAsStr = !empty($sortableColumns) ? 'true' : 'false';
303 $headerBoolAsStr = $headerEnabled ? 'true' : 'false';
304 $footerBoolAsStr = $footerEnabled ? 'true' : 'false';
305
306 $columnsData = [];
307 foreach ($sortableColumns as $column => $sortType) {
308 $columnsData[$column] = [
309 'sortable' => $sortType,
310 ];
311 }
312
313 $columnsJson = json_encode($columnsData);
314 if (!is_string($columnsJson)) {
315 $columnsJson = '{}';
316 }
317
318 $paginationJson = json_encode($paginationConfig);
319 if (!is_string($paginationJson)) {
320 $paginationJson = '{}';
321 }
322
323 $wrapperClass = trim("tableberg-table-wrapper {$wrapperAlignmentClass}");
324
325 $figureAlignmentClass = '';
326 if ($isWideWidth) {
327 $figureAlignmentClass = 'alignwide';
328 } elseif ($isFullWidth) {
329 $figureAlignmentClass = 'alignfull';
330 }
331
332 $figureClass = trim(
333 implode(' ', array_filter([
334 'wp-block-tableberg',
335 $attrs->table->className->asAttr(),
336 $figureAlignmentClass,
337 ]))
338 );
339
340 $figureStyles = [];
341 $spacingSides = [
342 'margin-top' => $attrs->table->margin->top,
343 'margin-right' => $attrs->table->margin->right,
344 'margin-bottom' => $attrs->table->margin->bottom,
345 'margin-left' => $attrs->table->margin->left,
346 'padding-top' => $attrs->table->padding->top,
347 'padding-right' => $attrs->table->padding->right,
348 'padding-bottom' => $attrs->table->padding->bottom,
349 'padding-left' => $attrs->table->padding->left,
350 ];
351 foreach ($spacingSides as $prop => $sideAttr) {
352 if ($sideAttr->isNotEmpty()) {
353 $figureStyles[] = $prop . ': ' . $sideAttr->asAttr();
354 }
355 }
356 $figureStyleAttr = !empty($figureStyles)
357 ? "style='" . implode('; ', $figureStyles) . ";'"
358 : '';
359
360 $captionHtml = '';
361 if ($caption->isNotEmpty()) {
362 $captionHtml = "<figcaption class='tableberg-table-caption wp-element-caption'>{$caption->asHtml()}</figcaption>";
363 }
364
365 $html =
366 "<figure class='{$figureClass}' {$figureStyleAttr}>
367 <div class='{$wrapperClass}'>
368 <table
369 class='{$tableClassAttr}'
370 {$tableStyleAttr}
371 data-tableberg-sortable='$sortingBoolAsStr'
372 data-tableberg-columns='$columnsJson'
373 data-tableberg-pagination='$paginationJson'
374 data-tableberg-search-enabled='$searchEnabledAsStr'
375 data-tableberg-search-placeholder='$searchPlaceholder'
376 data-tableberg-search-position='$searchPosition'
377 data-tableberg-search-highlight-color='$searchHighlightColor'
378 data-tableberg-header='$headerBoolAsStr'
379 data-tableberg-footer='$footerBoolAsStr'
380 {$responsiveDataAttrs}
381 >
382 <tbody>
383 {$rowsHtml}
384 </tbody>
385 </table>
386 </div>
387 {$captionHtml}
388 </figure>";
389
390 return $html;
391 }
392
393 /**
394 * @param bool $isWideWidth
395 * @param bool $isFullWidth
396 * @param bool $canApplyCustomWidth
397 * @param string $alignment
398 * @return string
399 */
400 private function getWrapperAlignmentClass(
401 $isWideWidth,
402 $isFullWidth,
403 $canApplyCustomWidth,
404 $alignment
405 ) {
406 if ($isWideWidth) {
407 return 'alignwide';
408 }
409
410 if ($isFullWidth) {
411 return 'alignfull';
412 }
413
414 if (!$canApplyCustomWidth) {
415 return '';
416 }
417
418 if ($alignment === 'left' || $alignment === 'right') {
419 return 'justify-table-' . $alignment;
420 }
421
422 if ($alignment === 'center') {
423 return 'justify-table-center';
424 }
425
426 return '';
427 }
428
429 /**
430 * @param TableAttrs $attrs
431 * @param int $rows
432 * @param int $cols
433 * @return string
434 */
435 private function buildResponsiveDataAttrs($attrs, $rows, $cols) {
436 $tablet = $attrs->table->responsive->tablet;
437 $mobile = $attrs->table->responsive->mobile;
438
439 $tabletEnabled = $tablet->enabled->value();
440 $mobileEnabled = $mobile->enabled->value();
441
442 if (!$tabletEnabled && !$mobileEnabled) {
443 return '';
444 }
445
446 $tabletMaxWidth = max(1, (int) $tablet->maxWidth->value());
447 $mobileMaxWidth = max(1, (int) $mobile->maxWidth->value());
448 $tabletStackCount = max(1, (int) $tablet->stackCount->value());
449 $mobileStackCount = max(1, (int) $mobile->stackCount->value());
450
451 return "
452 data-tableberg-responsive='true'
453 data-tableberg-rows='{$rows}'
454 data-tableberg-cols='{$cols}'
455 data-tableberg-tablet-enabled='{$tablet->enabled->asAttr()}'
456 data-tableberg-tablet-width='{$tabletMaxWidth}'
457 data-tableberg-tablet-mode='{$tablet->mode->asAttr()}'
458 data-tableberg-tablet-transpose='{$tablet->transpose->asAttr()}'
459 data-tableberg-tablet-count='{$tabletStackCount}'
460 data-tableberg-tablet-repeat-first-col='{$tablet->repeatFirstCol->asAttr()}'
461 data-tableberg-mobile-enabled='{$mobile->enabled->asAttr()}'
462 data-tableberg-mobile-width='{$mobileMaxWidth}'
463 data-tableberg-mobile-mode='{$mobile->mode->asAttr()}'
464 data-tableberg-mobile-transpose='{$mobile->transpose->asAttr()}'
465 data-tableberg-mobile-count='{$mobileStackCount}'
466 data-tableberg-mobile-repeat-first-col='{$mobile->repeatFirstCol->asAttr()}'
467 ";
468 }
469
470 private function getCell($row, $col, $cells) {
471 $key = $row . ',' . $col;
472
473 if (!is_array($cells) || !array_key_exists($key, $cells)) {
474 return null;
475 }
476
477 return $cells[$key];
478 }
479
480 private function getCellElements($cell) {
481 if (!$cell instanceof CellData) {
482 return [];
483 }
484
485 return is_array($cell->elements) ? $cell->elements : [];
486 }
487
488 private function getCellStyleOverride($cell) {
489 if (!$cell instanceof CellData || !is_array($cell->styles)) {
490 return null;
491 }
492
493 return StringAttr::fromNestedArray($cell->styles);
494 }
495
496 private function getCellRibbon($cell) {
497 if (!$cell instanceof CellData || !is_array($cell->ribbon)) {
498 return null;
499 }
500
501 return $cell->ribbon;
502 }
503
504 /**
505 * @param CellData|null $cell
506 * @return string|null
507 */
508 private function getCellClassName($cell) {
509 if (!$cell instanceof CellData || !$cell->className instanceof StringAttr) {
510 return null;
511 }
512
513 $className = $cell->className->asAttr();
514
515 return $className === '' ? null : $className;
516 }
517
518 private function getCellSpan($cell) {
519 $span = ['rowSpan' => 1, 'colSpan' => 1];
520
521 if ($cell instanceof CellData && $cell->span instanceof Span) {
522 $span['rowSpan'] = $cell->span->rowSpan->value();
523 $span['colSpan'] = $cell->span->colSpan->value();
524 }
525
526 return $span;
527 }
528
529 /**
530 * @param int $column
531 * @param array<int|string, ColumnConfig> $columns
532 * @return string|null
533 */
534 private function getColumnWidth($column, $columns) {
535 if (!is_array($columns) || !array_key_exists($column, $columns)) {
536 return null;
537 }
538
539 $columnConfig = $columns[$column];
540 if (!$columnConfig instanceof ColumnConfig || $columnConfig->width === null) {
541 return null;
542 }
543
544 $width = $columnConfig->width->asAttr();
545 return $width === '' ? null : $width;
546 }
547
548 /**
549 * @param int $row
550 * @param array<int|string, RowConfig> $rowConfigs
551 * @return string|null
552 */
553 private function getRowHeight($row, $rowConfigs) {
554 if (!is_array($rowConfigs) || !array_key_exists($row, $rowConfigs)) {
555 return null;
556 }
557
558 $rowConfig = $rowConfigs[$row];
559 if (!$rowConfig instanceof RowConfig || $rowConfig->height === null) {
560 return null;
561 }
562
563 $height = $rowConfig->height->asAttr();
564 return $height === '' ? null : $height;
565 }
566 }
567