| 1 |
<?php |
| 2 |
/** |
| 3 |
* Server-side rendering for the table-builder block and its rows/cells |
| 4 |
* |
| 5 |
* @package TableKit |
| 6 |
*/ |
| 7 |
|
| 8 |
namespace TableBuilder\Render; |
| 9 |
|
| 10 |
defined( 'ABSPATH' ) || exit; |
| 11 |
|
| 12 |
/** |
| 13 |
* Renders table-builder block markup in PHP (used by the shortcode and |
| 14 |
* dynamic-block rendering paths). |
| 15 |
*/ |
| 16 |
class BlockRenderer { |
| 17 |
|
| 18 |
/** |
| 19 |
* Renders static table-builder block structure in PHP. |
| 20 |
* |
| 21 |
* @param array $attributes Block attributes. |
| 22 |
* @param array $row_blocks Parsed row inner blocks to render as tbody content. |
| 23 |
* @param string $tbody_content Pre-rendered tbody HTML; used instead of $row_blocks when given. |
| 24 |
* @return string Rendered block HTML. |
| 25 |
*/ |
| 26 |
public static function render_table_builder( array $attributes = array(), array $row_blocks = array(), string $tbody_content = '' ): string { |
| 27 |
$caption = $attributes['caption'] ?? ''; |
| 28 |
$headers = is_array( $attributes['headers'] ?? null ) ? $attributes['headers'] : array(); |
| 29 |
$footers = is_array( $attributes['footers'] ?? null ) ? $attributes['footers'] : array(); |
| 30 |
$has_header = ! empty( $attributes['hasHeader'] ); |
| 31 |
$has_footer = ! empty( $attributes['hasFooter'] ); |
| 32 |
$show_caption = ! empty( $attributes['showCaption'] ); |
| 33 |
$is_empty = ! empty( $attributes['isEmpty'] ); |
| 34 |
$block_class = trim( (string) ( $attributes['blockClass'] ?? '' ) ); |
| 35 |
$block_id = (string) ( $attributes['blockID'] ?? '' ); |
| 36 |
$col_widths = is_array( $attributes['columnWidths'] ?? null ) ? $attributes['columnWidths'] : array(); |
| 37 |
|
| 38 |
$settings = array( |
| 39 |
'enableSorting' => ! empty( $attributes['enableSorting'] ), |
| 40 |
'tableConditionalFormat' => is_array( $attributes['tableConditionalFormat'] ?? null ) ? $attributes['tableConditionalFormat'] : array(), |
| 41 |
'mergeTableCell' => $attributes['mergeTableCell'] ?? array(), |
| 42 |
'freezeSettings' => $attributes['freezeSettings'] ?? array(), |
| 43 |
'blockClass' => $block_class, |
| 44 |
'enableFreeze' => ! empty( $attributes['enableFreeze'] ), |
| 45 |
'columnWidths' => $col_widths, |
| 46 |
'rowHeights' => is_array( $attributes['rowHeights'] ?? null ) ? $attributes['rowHeights'] : array(), |
| 47 |
'headerHeight' => absint( $attributes['headerHeight'] ?? 0 ), |
| 48 |
'resizerEnabled' => isset( $attributes['resizerEnabled'] ) ? (bool) $attributes['resizerEnabled'] : true, |
| 49 |
'mobileBreakpoint' => absint( $attributes['responsiveBreakpoint'] ?? 768 ), |
| 50 |
); |
| 51 |
|
| 52 |
$figure_classes = trim( 'wp-block-tablebuilder-table-builder table-builder-block ' . $block_class ); |
| 53 |
$figure_id = ! empty( $block_id ) ? 'block-' . sanitize_html_class( $block_id ) : ''; |
| 54 |
|
| 55 |
$has_widths = ! empty( $col_widths ) && array_sum( array_map( 'floatval', $col_widths ) ) > 0; |
| 56 |
$table_style = $has_widths ? 'table-layout:fixed;width:100%;' : ''; |
| 57 |
|
| 58 |
$rendered_tbody = ''; |
| 59 |
if ( ! empty( $tbody_content ) ) { |
| 60 |
$rendered_tbody = wp_kses_post( $tbody_content ); |
| 61 |
} elseif ( ! empty( $row_blocks ) ) { |
| 62 |
$rendered_tbody = self::render_table_rows( $row_blocks ); |
| 63 |
} |
| 64 |
|
| 65 |
ob_start(); |
| 66 |
?> |
| 67 |
<figure class="<?php echo esc_attr( $figure_classes ); ?>" data-block="tablebuilder/table-builder" data-settings="<?php echo esc_attr( wp_json_encode( $settings ) ); ?>"<?php echo ! empty( $figure_id ) ? ' id="' . esc_attr( $figure_id ) . '"' : ''; ?>> |
| 68 |
<?php if ( ! $is_empty ) : ?> |
| 69 |
<table class="gkit-table"<?php echo ! empty( $table_style ) ? ' style="' . esc_attr( $table_style ) . '"' : ''; ?>> |
| 70 |
<?php if ( $has_header ) : ?> |
| 71 |
<thead class="gkit-table__header"> |
| 72 |
<tr> |
| 73 |
<?php foreach ( $headers as $index => $header ) : ?> |
| 74 |
<?php $col_style = self::get_column_style( $col_widths, $index ); ?> |
| 75 |
<th class="gkit-table__header-content"<?php echo ! empty( $col_style ) ? ' style="' . esc_attr( $col_style ) . '"' : ''; ?>> |
| 76 |
<div><?php echo wp_kses_post( $header['title'] ?? '' ); ?></div> |
| 77 |
</th> |
| 78 |
<?php endforeach; ?> |
| 79 |
</tr> |
| 80 |
</thead> |
| 81 |
<?php endif; ?> |
| 82 |
|
| 83 |
<tbody class="gkit-table__body"><?php echo $rendered_tbody; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built above via wp_kses_post() or from esc_attr()-escaped attributes plus core's render_block(). ?></tbody> |
| 84 |
|
| 85 |
<?php if ( $has_footer ) : ?> |
| 86 |
<tfoot class="gkit-table__footer"> |
| 87 |
<tr> |
| 88 |
<?php foreach ( $footers as $index => $footer ) : ?> |
| 89 |
<?php $col_style = self::get_column_style( $col_widths, $index ); ?> |
| 90 |
<td class="gkit-table__footer-content"<?php echo ! empty( $col_style ) ? ' style="' . esc_attr( $col_style ) . '"' : ''; ?>> |
| 91 |
<div><?php echo wp_kses_post( $footer['title'] ?? '' ); ?></div> |
| 92 |
</td> |
| 93 |
<?php endforeach; ?> |
| 94 |
</tr> |
| 95 |
</tfoot> |
| 96 |
<?php endif; ?> |
| 97 |
</table> |
| 98 |
|
| 99 |
<?php if ( $show_caption ) : ?> |
| 100 |
<figcaption class="gkit-table__caption"><?php echo wp_kses_post( $caption ); ?></figcaption> |
| 101 |
<?php endif; ?> |
| 102 |
<?php endif; ?> |
| 103 |
</figure> |
| 104 |
<?php |
| 105 |
|
| 106 |
return (string) ob_get_clean(); |
| 107 |
} |
| 108 |
|
| 109 |
/** |
| 110 |
* Renders row and cell inner blocks for tbody markup. |
| 111 |
* |
| 112 |
* @param array $row_blocks Parsed "tablebuilder/table-builder-row" inner blocks. |
| 113 |
* @return string Rendered <tr> markup. |
| 114 |
*/ |
| 115 |
private static function render_table_rows( array $row_blocks ): string { |
| 116 |
$output = ''; |
| 117 |
|
| 118 |
foreach ( $row_blocks as $row_block ) { |
| 119 |
if ( ( $row_block['blockName'] ?? '' ) !== 'tablebuilder/table-builder-row' ) { |
| 120 |
continue; |
| 121 |
} |
| 122 |
|
| 123 |
$row_class = trim( 'wp-block-tablebuilder-table-builder-row gkit-table__body-row ' . ( $row_block['attrs']['blockClass'] ?? '' ) ); |
| 124 |
$output .= '<tr class="' . esc_attr( $row_class ) . '">'; |
| 125 |
|
| 126 |
foreach ( $row_block['innerBlocks'] ?? array() as $item_block ) { |
| 127 |
if ( ( $item_block['blockName'] ?? '' ) !== 'tablebuilder/table-builder-item' ) { |
| 128 |
continue; |
| 129 |
} |
| 130 |
|
| 131 |
$item_class = trim( 'wp-block-tablebuilder-table-builder-item gkit-table__body-content ' . ( $item_block['attrs']['blockClass'] ?? '' ) ); |
| 132 |
$item_markup = self::render_nested_inner_blocks( $item_block['innerBlocks'] ?? array() ); |
| 133 |
$output .= '<td class="' . esc_attr( $item_class ) . '"><div>' . $item_markup . '</div></td>'; |
| 134 |
} |
| 135 |
|
| 136 |
$output .= '</tr>'; |
| 137 |
} |
| 138 |
|
| 139 |
return $output; |
| 140 |
} |
| 141 |
|
| 142 |
/** |
| 143 |
* Renders nested inner blocks of a cell. |
| 144 |
* |
| 145 |
* @param array $blocks Parsed inner blocks to render via core's render_block(). |
| 146 |
* @return string Combined rendered HTML. |
| 147 |
*/ |
| 148 |
private static function render_nested_inner_blocks( array $blocks ): string { |
| 149 |
$output = ''; |
| 150 |
|
| 151 |
foreach ( $blocks as $inner ) { |
| 152 |
$output .= render_block( $inner ); |
| 153 |
} |
| 154 |
|
| 155 |
return $output; |
| 156 |
} |
| 157 |
|
| 158 |
/** |
| 159 |
* Converts stored column widths into percentages for saved frontend parity. |
| 160 |
* |
| 161 |
* @param array $column_widths Stored column width values, keyed by column index. |
| 162 |
* @param int $index Column index to compute the style for. |
| 163 |
* @return string An inline "width:N%;" style, or an empty string if not applicable. |
| 164 |
*/ |
| 165 |
private static function get_column_style( array $column_widths, int $index ): string { |
| 166 |
if ( empty( $column_widths[ $index ] ) ) { |
| 167 |
return ''; |
| 168 |
} |
| 169 |
|
| 170 |
$total_width = array_sum( array_map( 'floatval', $column_widths ) ); |
| 171 |
if ( $total_width <= 0 ) { |
| 172 |
return ''; |
| 173 |
} |
| 174 |
|
| 175 |
$percentage = ( (float) $column_widths[ $index ] / $total_width ) * 100; |
| 176 |
return 'width:' . round( $percentage, 4 ) . '%;'; |
| 177 |
} |
| 178 |
} |
| 179 |
|