PluginProbe
Tableberg – Simple Gutenberg Table Block / 1.0.2
Tableberg – Simple Gutenberg Table Block v1.0.2
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.2, at renderer/Table/TableRenderer.php

547 lines 18.3 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 $captionHtml = '';
341 if ($caption->isNotEmpty()) {
342 $captionHtml = "<figcaption class='tableberg-table-caption wp-element-caption'>{$caption->asHtml()}</figcaption>";
343 }
344
345 $html =
346 "<figure class='{$figureClass}'>
347 <div class='{$wrapperClass}'>
348 <table
349 class='{$tableClassAttr}'
350 {$tableStyleAttr}
351 data-tableberg-sortable='$sortingBoolAsStr'
352 data-tableberg-columns='$columnsJson'
353 data-tableberg-pagination='$paginationJson'
354 data-tableberg-search-enabled='$searchEnabledAsStr'
355 data-tableberg-search-placeholder='$searchPlaceholder'
356 data-tableberg-search-position='$searchPosition'
357 data-tableberg-search-highlight-color='$searchHighlightColor'
358 data-tableberg-header='$headerBoolAsStr'
359 data-tableberg-footer='$footerBoolAsStr'
360 {$responsiveDataAttrs}
361 >
362 <tbody>
363 {$rowsHtml}
364 </tbody>
365 </table>
366 </div>
367 {$captionHtml}
368 </figure>";
369
370 return $html;
371 }
372
373 /**
374 * @param bool $isWideWidth
375 * @param bool $isFullWidth
376 * @param bool $canApplyCustomWidth
377 * @param string $alignment
378 * @return string
379 */
380 private function getWrapperAlignmentClass(
381 $isWideWidth,
382 $isFullWidth,
383 $canApplyCustomWidth,
384 $alignment
385 ) {
386 if ($isWideWidth) {
387 return 'alignwide';
388 }
389
390 if ($isFullWidth) {
391 return 'alignfull';
392 }
393
394 if (!$canApplyCustomWidth) {
395 return '';
396 }
397
398 if ($alignment === 'left' || $alignment === 'right') {
399 return 'justify-table-' . $alignment;
400 }
401
402 if ($alignment === 'center') {
403 return 'justify-table-center';
404 }
405
406 return '';
407 }
408
409 /**
410 * @param TableAttrs $attrs
411 * @param int $rows
412 * @param int $cols
413 * @return string
414 */
415 private function buildResponsiveDataAttrs($attrs, $rows, $cols) {
416 $tablet = $attrs->table->responsive->tablet;
417 $mobile = $attrs->table->responsive->mobile;
418
419 $tabletEnabled = $tablet->enabled->value();
420 $mobileEnabled = $mobile->enabled->value();
421
422 if (!$tabletEnabled && !$mobileEnabled) {
423 return '';
424 }
425
426 $tabletMaxWidth = max(1, (int) $tablet->maxWidth->value());
427 $mobileMaxWidth = max(1, (int) $mobile->maxWidth->value());
428 $tabletStackCount = max(1, (int) $tablet->stackCount->value());
429 $mobileStackCount = max(1, (int) $mobile->stackCount->value());
430
431 return "
432 data-tableberg-responsive='true'
433 data-tableberg-rows='{$rows}'
434 data-tableberg-cols='{$cols}'
435 data-tableberg-tablet-enabled='{$tablet->enabled->asAttr()}'
436 data-tableberg-tablet-width='{$tabletMaxWidth}'
437 data-tableberg-tablet-mode='{$tablet->mode->asAttr()}'
438 data-tableberg-tablet-transpose='{$tablet->transpose->asAttr()}'
439 data-tableberg-tablet-count='{$tabletStackCount}'
440 data-tableberg-tablet-repeat-first-col='{$tablet->repeatFirstCol->asAttr()}'
441 data-tableberg-mobile-enabled='{$mobile->enabled->asAttr()}'
442 data-tableberg-mobile-width='{$mobileMaxWidth}'
443 data-tableberg-mobile-mode='{$mobile->mode->asAttr()}'
444 data-tableberg-mobile-transpose='{$mobile->transpose->asAttr()}'
445 data-tableberg-mobile-count='{$mobileStackCount}'
446 data-tableberg-mobile-repeat-first-col='{$mobile->repeatFirstCol->asAttr()}'
447 ";
448 }
449
450 private function getCell($row, $col, $cells) {
451 $key = $row . ',' . $col;
452
453 if (!is_array($cells) || !array_key_exists($key, $cells)) {
454 return null;
455 }
456
457 return $cells[$key];
458 }
459
460 private function getCellElements($cell) {
461 if (!$cell instanceof CellData) {
462 return [];
463 }
464
465 return is_array($cell->elements) ? $cell->elements : [];
466 }
467
468 private function getCellStyleOverride($cell) {
469 if (!$cell instanceof CellData || !is_array($cell->styles)) {
470 return null;
471 }
472
473 return StringAttr::fromNestedArray($cell->styles);
474 }
475
476 private function getCellRibbon($cell) {
477 if (!$cell instanceof CellData || !is_array($cell->ribbon)) {
478 return null;
479 }
480
481 return $cell->ribbon;
482 }
483
484 /**
485 * @param CellData|null $cell
486 * @return string|null
487 */
488 private function getCellClassName($cell) {
489 if (!$cell instanceof CellData || !$cell->className instanceof StringAttr) {
490 return null;
491 }
492
493 $className = $cell->className->asAttr();
494
495 return $className === '' ? null : $className;
496 }
497
498 private function getCellSpan($cell) {
499 $span = ['rowSpan' => 1, 'colSpan' => 1];
500
501 if ($cell instanceof CellData && $cell->span instanceof Span) {
502 $span['rowSpan'] = $cell->span->rowSpan->value();
503 $span['colSpan'] = $cell->span->colSpan->value();
504 }
505
506 return $span;
507 }
508
509 /**
510 * @param int $column
511 * @param array<int|string, ColumnConfig> $columns
512 * @return string|null
513 */
514 private function getColumnWidth($column, $columns) {
515 if (!is_array($columns) || !array_key_exists($column, $columns)) {
516 return null;
517 }
518
519 $columnConfig = $columns[$column];
520 if (!$columnConfig instanceof ColumnConfig || $columnConfig->width === null) {
521 return null;
522 }
523
524 $width = $columnConfig->width->asAttr();
525 return $width === '' ? null : $width;
526 }
527
528 /**
529 * @param int $row
530 * @param array<int|string, RowConfig> $rowConfigs
531 * @return string|null
532 */
533 private function getRowHeight($row, $rowConfigs) {
534 if (!is_array($rowConfigs) || !array_key_exists($row, $rowConfigs)) {
535 return null;
536 }
537
538 $rowConfig = $rowConfigs[$row];
539 if (!$rowConfig instanceof RowConfig || $rowConfig->height === null) {
540 return null;
541 }
542
543 $height = $rowConfig->height->asAttr();
544 return $height === '' ? null : $height;
545 }
546 }
547