PluginProbe
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables / 2.3.0
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables v2.3.0
2.3.0 2.2.14 2.2.13 2.2.12 2.2.11 2.2.10 2.2.9 2.2.8 2.2.7 2.2.6 2.2.5 2.2.4 2.2.3 trunk 1.0.0 1.0.1 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2
← All changes | includes/Config/CPT/TableCPTAdmin.php +283 -148 2.2.8 → 2.3.0 View file →
@@ -1,105 +1,160 @@
1 1 <?php
2 +/**
3 + * Admin list-table integration for the "tablekit_table" post type
4 + *
5 + * @package TableKit
6 + */
2 7
3 8 namespace TableBuilder\Config\CPT;
4 9
5 -defined('ABSPATH') || exit;
10 +defined( 'ABSPATH' ) || exit;
6 11
7 12 use WP_Post;
8 13
9 -class TableCPTAdmin
10 -{
14 +/**
15 + * Adds source/shortcode columns, row actions, and notices to the Tables admin list table.
16 + */
17 +class TableCPTAdmin {
18 +
19 + /**
20 + * Scans post content for inline table blocks.
21 + *
22 + * @var InlineTableScanner
23 + */
11 24 private InlineTableScanner $scanner;
25 +
26 + /**
27 + * Builds virtual "tablekit_table" posts for inline-only tables.
28 + *
29 + * @var VirtualPostBuilder
30 + */
12 31 private VirtualPostBuilder $virtual_posts;
13 32
14 - public function __construct(InlineTableScanner $scanner, VirtualPostBuilder $virtual_posts)
15 - {
33 + /**
34 + * Stores the collaborators used to resolve and render inline-table admin data.
35 + *
36 + * @param InlineTableScanner $scanner Scanner used to locate inline table blocks.
37 + * @param VirtualPostBuilder $virtual_posts Builder used to render shortcode inputs/panels for admin list columns.
38 + */
39 + public function __construct( InlineTableScanner $scanner, VirtualPostBuilder $virtual_posts ) {
16 40 $this->scanner = $scanner;
17 41 $this->virtual_posts = $virtual_posts;
18 42 }
19 43
20 - public function print_recommendation_notice(): void
21 - {
22 - if (! is_admin()) {
44 + /**
45 + * Prints an admin notice on the Tables list screen recommending a single
46 + * table per page for easier management/performance.
47 + *
48 + * @return void
49 + */
50 + public function print_recommendation_notice(): void {
51 + if ( ! is_admin() ) {
23 52 return;
24 53 }
25 54
26 55 $screen = get_current_screen();
27 - if (! $screen || TableCPT::POST_TYPE !== $screen->post_type) {
56 + if ( ! $screen || TableCPT::POST_TYPE !== $screen->post_type ) {
28 57 return;
29 58 }
30 59
31 - $message = __('TableKit supports multiple tables in the same page. However, to keep your content easier to manage and improve compatibility, performance, and shortcode handling, we recommend creating a single table whenever possible.', 'table-builder-block');
60 + $message = __( 'TableKit supports multiple tables in the same page. However, to keep your content easier to manage and improve compatibility, performance, and shortcode handling, we recommend creating a single table whenever possible.', 'table-builder-block' );
32 61
33 62 echo '<div class="notice notice-info is-dismissible" >';
34 - echo '<p>' . esc_html($message) . '</p>';
63 + echo '<p>' . esc_html( $message ) . '</p>';
35 64 echo '</div>';
36 65 }
37 66
38 - public function prepare_bulk_post_ids(): void
39 - {
40 - if (empty($_REQUEST['post_type']) || TableCPT::POST_TYPE !== (string) $_REQUEST['post_type']) {
67 + /**
68 + * Remap any virtual (negative, synthetic) post IDs in a bulk action's
69 + * selected posts back to their real source post IDs, before WP core's own
70 + * bulk-action handling runs on edit.php. Hooked to "load-edit.php".
71 + *
72 + * @return void
73 + */
74 + public function prepare_bulk_post_ids(): void {
75 + // phpcs:disable WordPress.Security.NonceVerification.Recommended -- this only
76 + // remaps virtual->real post IDs ahead of WP core's own bulk-action handling on
77 + // edit.php (load-edit.php runs before edit.php verifies the bulk-action nonce);
78 + // it never performs a state change itself.
79 + $post_type = isset( $_REQUEST['post_type'] ) ? sanitize_key( wp_unslash( $_REQUEST['post_type'] ) ) : '';
80 +
81 + if ( '' === $post_type || TableCPT::POST_TYPE !== $post_type ) {
41 82 return;
42 83 }
43 84
44 - if (empty($_REQUEST['post'])) {
85 + if ( empty( $_REQUEST['post'] ) ) {
45 86 return;
46 87 }
47 88
48 89 $virtual_source_map = $this->virtual_posts->get_virtual_source_id_map();
49 90
50 - if (empty($virtual_source_map)) {
91 + if ( empty( $virtual_source_map ) ) {
51 92 return;
52 93 }
53 94
54 - $post_ids = array_map('intval', (array) wp_unslash($_REQUEST['post']));
95 + $post_ids = array_map( 'intval', (array) wp_unslash( $_REQUEST['post'] ) );
96 + // phpcs:enable WordPress.Security.NonceVerification.Recommended
55 97 $normalized_ids = array();
56 98
57 - foreach ($post_ids as $post_id) {
58 - if ($post_id > 0) {
99 + foreach ( $post_ids as $post_id ) {
100 + if ( $post_id > 0 ) {
59 101 $normalized_ids[] = $post_id;
60 102 continue;
61 103 }
62 104
63 - if (isset($virtual_source_map[$post_id])) {
64 - $normalized_ids[] = (int) $virtual_source_map[$post_id];
105 + if ( isset( $virtual_source_map[ $post_id ] ) ) {
106 + $normalized_ids[] = (int) $virtual_source_map[ $post_id ];
65 107 }
66 108 }
67 109
68 - $normalized_ids = array_values(array_unique(array_filter($normalized_ids)));
110 + $normalized_ids = array_values( array_unique( array_filter( $normalized_ids ) ) );
69 111
70 112 $_REQUEST['post'] = $normalized_ids;
71 113 $_POST['post'] = $normalized_ids;
72 114 }
73 115
74 - public function show_list_table_checkbox(bool $show, WP_Post $post): bool
75 - {
76 - if (empty($post->tablekit_is_virtual)) {
116 + /**
117 + * Determines whether a bulk-action checkbox should show for a list-table row,
118 + * gating virtual rows on the viewer's capability over the real source post.
119 + *
120 + * @param bool $show Default visibility as determined by WP core.
121 + * @param WP_Post $post The row's post (real or virtual).
122 + * @return bool
123 + */
124 + public function show_list_table_checkbox( bool $show, WP_Post $post ): bool {
125 + if ( empty( $post->tablekit_is_virtual ) ) {
77 126 return $show;
78 127 }
79 128
80 - return current_user_can('edit_post', (int) $post->tablekit_source_id);
129 + return current_user_can( 'edit_post', (int) $post->tablekit_source_id );
81 130 }
82 131
83 - public function add_list_columns(array $columns): array
84 - {
132 + /**
133 + * Adds TableKit-specific columns (source/author, and shortcode columns when
134 + * Pro is active) to the Tables admin list table, dropping the default "author" column.
135 + *
136 + * @param array $columns Existing list table columns.
137 + * @return array Modified columns.
138 + */
139 + public function add_list_columns( array $columns ): array {
85 140 $updated = array();
86 141
87 - foreach ($columns as $key => $label) {
88 - if ('author' === $key) {
142 + foreach ( $columns as $key => $label ) {
143 + if ( 'author' === $key ) {
89 144 continue;
90 145 }
91 146
92 - $updated[$key] = $label;
147 + $updated[ $key ] = $label;
93 148
94 - if ('title' === $key) {
95 - if ($this->is_pro_active()) {
96 - $updated['tablekit_shortcode'] = __('Shortcode', 'table-builder-block');
97 - $updated['tablekit_child_shortcode'] = __('Child Shortcodes', 'table-builder-block');
149 + if ( 'title' === $key ) {
150 + if ( $this->is_pro_active() ) {
151 + $updated['tablekit_shortcode'] = __( 'Shortcode', 'table-builder-block' );
152 + $updated['tablekit_child_shortcode'] = __( 'Child Shortcodes', 'table-builder-block' );
98 153 }
99 154
100 - $updated['tablekit_source'] = __('Source', 'table-builder-block');
101 - $updated['tablekit_author'] = __('Author', 'table-builder-block');
155 + $updated['tablekit_source'] = __( 'Source', 'table-builder-block' );
156 + $updated['tablekit_author'] = __( 'Author', 'table-builder-block' );
102 157 }
103 158 }
104 159
105 160 return $updated;
@@ -104,115 +159,139 @@
104 159
105 160 return $updated;
106 161 }
107 162
108 - public function render_list_column(string $column, int $post_id): void
109 - {
163 + /**
164 + * Renders the contents of a custom Tables admin list-table column.
165 + *
166 + * @param string $column Column key being rendered.
167 + * @param int $post_id The row's post ID (unused directly; the global $post is used instead).
168 + * @return void
169 + */
170 + public function render_list_column( string $column, int $post_id ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- required by the "manage_{$post_type}_posts_custom_column" action signature; the global $post is used instead.
110 171 global $post;
111 172
112 - if (! ($post instanceof WP_Post)) {
173 + if ( ! ( $post instanceof WP_Post ) ) {
113 174 return;
114 175 }
115 176
116 - switch ($column) {
177 + switch ( $column ) {
117 178 case 'tablekit_shortcode':
118 - if (! $this->is_pro_active()) {
179 + if ( ! $this->is_pro_active() ) {
119 180 break;
120 181 }
121 182
122 - $this->render_column_shortcode($post);
183 + $this->render_column_shortcode( $post );
123 184 break;
124 185 case 'tablekit_child_shortcode':
125 - if (! $this->is_pro_active()) {
186 + if ( ! $this->is_pro_active() ) {
126 187 break;
127 188 }
128 189
129 - $this->render_column_child_shortcodes($post);
190 + $this->render_column_child_shortcodes( $post );
130 191 break;
131 192 case 'tablekit_source':
132 - $this->render_column_source($post);
193 + $this->render_column_source( $post );
133 194 break;
134 195 case 'tablekit_author':
135 - $this->render_column_author($post);
196 + $this->render_column_author( $post );
136 197 break;
137 198 }
138 199 }
139 200
140 - public function filter_row_actions(array $actions, WP_Post $post): array
141 - {
142 - if (empty($post->tablekit_is_virtual)) {
201 + /**
202 + * Replace a virtual row's default row actions (Edit/Trash/Delete, etc.) with
203 + * actions pointed at its real source post instead of the non-existent virtual post.
204 + *
205 + * @param array $actions Default row actions.
206 + * @param WP_Post $post The row's post (real or virtual).
207 + * @return array Row actions to display.
208 + */
209 + public function filter_row_actions( array $actions, WP_Post $post ): array {
210 + if ( empty( $post->tablekit_is_virtual ) ) {
143 211 return $actions;
144 212 }
145 213
146 - $source_id = (int) $post->tablekit_source_id;
147 - $source_post = get_post($source_id);
214 + $source_id = (int) $post->tablekit_source_id;
215 + $source_post = get_post( $source_id );
148 216 $source_actions = array();
149 - $title = (string) $post->tablekit_source_title;
217 + $title = (string) $post->tablekit_source_title;
150 218
151 - if ($source_post instanceof WP_Post && current_user_can('edit_post', $source_id) && 'trash' !== $source_post->post_status) {
152 - $edit_link = get_edit_post_link($source_id);
219 + if ( $source_post instanceof WP_Post && current_user_can( 'edit_post', $source_id ) && 'trash' !== $source_post->post_status ) {
220 + $edit_link = get_edit_post_link( $source_id );
153 221
154 - if (false !== $edit_link && '' !== $edit_link) {
222 + if ( false !== $edit_link && '' !== $edit_link ) {
155 223 $source_actions['edit'] = sprintf(
156 224 '<a href="%s" aria-label="%s">%s</a>',
157 - esc_url((string) $edit_link),
158 - esc_attr(sprintf(__('Edit &#8220;%s&#8221;'), $title)),
159 - esc_html__('Edit')
225 + esc_url( (string) $edit_link ),
226 + /* translators: %s: Title of the source post the table belongs to. */
227 + esc_attr( sprintf( __( 'Edit &#8220;%s&#8221;', 'table-builder-block' ), $title ) ),
228 + esc_html__( 'Edit', 'table-builder-block' )
160 229 );
161 230 }
162 231
163 - if ('publish' === $source_post->post_status) {
164 - $view_link = get_permalink($source_id);
232 + if ( 'publish' === $source_post->post_status ) {
233 + $view_link = get_permalink( $source_id );
165 234
166 - if (false !== $view_link && '' !== $view_link) {
235 + if ( false !== $view_link && '' !== $view_link ) {
167 236 $source_actions['view'] = sprintf(
168 237 '<a href="%s" rel="bookmark" aria-label="%s">%s</a>',
169 - esc_url((string) $view_link),
170 - esc_attr(sprintf(__('View &#8220;%s&#8221;'), $title)),
171 - esc_html__('View')
238 + esc_url( (string) $view_link ),
239 + /* translators: %s: Title of the source post the table belongs to. */
240 + esc_attr( sprintf( __( 'View &#8220;%s&#8221;', 'table-builder-block' ), $title ) ),
241 + esc_html__( 'View', 'table-builder-block' )
172 242 );
173 243 }
174 244 }
175 245 }
176 246
177 - if (current_user_can('delete_post', $source_id)) {
178 - if ($source_post instanceof WP_Post && 'trash' === $source_post->post_status) {
247 + if ( current_user_can( 'delete_post', $source_id ) ) {
248 + if ( $source_post instanceof WP_Post && 'trash' === $source_post->post_status ) {
179 249 $source_actions['delete'] = sprintf(
180 250 '<a href="%s" class="submitdelete" aria-label="%s">%s</a>',
181 - get_delete_post_link($source_id),
182 - esc_attr(sprintf(__('Delete &#8220;%s&#8221; permanently'), $title)),
183 - esc_html__('Delete Permanently')
251 + get_delete_post_link( $source_id ),
252 + /* translators: %s: Title of the source post the table belongs to. */
253 + esc_attr( sprintf( __( 'Delete &#8220;%s&#8221; permanently', 'table-builder-block' ), $title ) ),
254 + esc_html__( 'Delete Permanently', 'table-builder-block' )
184 255 );
185 - } elseif (EMPTY_TRASH_DAYS) {
256 + } elseif ( EMPTY_TRASH_DAYS ) {
186 257 $source_actions['trash'] = sprintf(
187 258 '<a href="%s" class="submitdelete" aria-label="%s">%s</a>',
188 - get_delete_post_link($source_id),
189 - esc_attr(sprintf(__('Move &#8220;%s&#8221; to the Trash'), $title)),
190 - esc_html_x('Trash', 'verb')
259 + get_delete_post_link( $source_id ),
260 + /* translators: %s: Title of the source post the table belongs to. */
261 + esc_attr( sprintf( __( 'Move &#8220;%s&#8221; to the Trash', 'table-builder-block' ), $title ) ),
262 + esc_html_x( 'Trash', 'verb', 'table-builder-block' )
191 263 );
192 264 }
193 265
194 - if ($source_post instanceof WP_Post && ('trash' === $source_post->post_status || ! EMPTY_TRASH_DAYS)) {
266 + if ( $source_post instanceof WP_Post && ( 'trash' === $source_post->post_status || ! EMPTY_TRASH_DAYS ) ) {
195 267 $source_actions['delete'] = sprintf(
196 268 '<a href="%s" class="submitdelete" aria-label="%s">%s</a>',
197 - get_delete_post_link($source_id),
198 - esc_attr(sprintf(__('Delete &#8220;%s&#8221; permanently'), $title)),
199 - esc_html__('Delete Permanently')
269 + get_delete_post_link( $source_id ),
270 + /* translators: %s: Title of the source post the table belongs to. */
271 + esc_attr( sprintf( __( 'Delete &#8220;%s&#8221; permanently', 'table-builder-block' ), $title ) ),
272 + esc_html__( 'Delete Permanently', 'table-builder-block' )
200 273 );
201 274 }
202 275 }
203 276
204 - return empty($source_actions) ? $actions : $source_actions;
277 + return empty( $source_actions ) ? $actions : $source_actions;
205 278 }
206 279
207 - public function enqueue_admin_assets(string $hook): void
208 - {
209 - if ('edit.php' !== $hook) {
280 + /**
281 + * Enqueues the Tables admin list-table's CSS/JS on the "edit.php" screen
282 + * for the Tables post type only.
283 + *
284 + * @param string $hook Current admin page hook suffix.
285 + * @return void
286 + */
287 + public function enqueue_admin_assets( string $hook ): void {
288 + if ( 'edit.php' !== $hook ) {
210 289 return;
211 290 }
212 291
213 292 $screen = get_current_screen();
214 - if (! $screen || TableCPT::POST_TYPE !== $screen->post_type) {
293 + if ( ! $screen || TableCPT::POST_TYPE !== $screen->post_type ) {
215 294 return;
216 295 }
217 296
218 297 $style_path = TABLE_BUILDER_BLOCK_PLUGIN_DIR . 'assets/css/table-cpt-admin.css';
@@ -221,9 +300,9 @@
221 300 wp_enqueue_style(
222 301 'tablekit-cpt-admin',
223 302 TABLE_BUILDER_BLOCK_PLUGIN_URL . 'assets/css/table-cpt-admin.css',
224 303 array(),
225 - file_exists($style_path) ? (string) filemtime($style_path) : TABLE_BUILDER_BLOCK_PLUGIN_VERSION
304 + file_exists( $style_path ) ? (string) filemtime( $style_path ) : TABLE_BUILDER_BLOCK_PLUGIN_VERSION
226 305 );
227 306
228 307 wp_enqueue_script(
229 308 'tablekit-cpt-admin',
@@ -228,9 +307,9 @@
228 307 wp_enqueue_script(
229 308 'tablekit-cpt-admin',
230 309 TABLE_BUILDER_BLOCK_PLUGIN_URL . 'assets/js/table-cpt-admin.js',
231 310 array(),
232 - file_exists($script_path) ? (string) filemtime($script_path) : TABLE_BUILDER_BLOCK_PLUGIN_VERSION,
311 + file_exists( $script_path ) ? (string) filemtime( $script_path ) : TABLE_BUILDER_BLOCK_PLUGIN_VERSION,
233 312 true
234 313 );
235 314
236 315 wp_localize_script(
@@ -236,117 +315,159 @@
236 315 wp_localize_script(
237 316 'tablekit-cpt-admin',
238 317 'tablekitCptAdmin',
239 318 array(
240 - 'copiedText' => __('Copied!', 'table-builder-block'),
241 - 'failedText' => __('Copy failed', 'table-builder-block'),
319 + 'copiedText' => __( 'Copied!', 'table-builder-block' ),
320 + 'failedText' => __( 'Copy failed', 'table-builder-block' ),
242 321 )
243 322 );
244 323 }
245 324
246 - private function render_column_shortcode(WP_Post $post): void
247 - {
248 - if (! empty($post->tablekit_is_virtual)) {
325 + /**
326 + * Renders the "Shortcode" column: a copyable `[tableKit post_id="..."]` shortcode.
327 + *
328 + * @param WP_Post $post The row's post (real or virtual).
329 + * @return void
330 + */
331 + private function render_column_shortcode( WP_Post $post ): void {
332 + if ( ! empty( $post->tablekit_is_virtual ) ) {
249 333 $this->virtual_posts->render_shortcode_input(
250 - sprintf('[tableKit post_id="%d"]', (int) $post->tablekit_source_id)
334 + sprintf( '[tableKit post_id="%d"]', (int) $post->tablekit_source_id )
251 335 );
252 336 return;
253 337 }
254 338
255 - $this->virtual_posts->render_shortcode_input(sprintf('[tableKit post_id="%d"]', (int) $post->ID));
339 + $this->virtual_posts->render_shortcode_input( sprintf( '[tableKit post_id="%d"]', (int) $post->ID ) );
256 340 }
257 341
258 - private function render_column_child_shortcodes(WP_Post $post): void
259 - {
260 - $child_blocks = $this->get_child_shortcode_blocks($post);
342 + /**
343 + * Renders the "Child Shortcodes" column: a collapsible panel listing shortcodes
344 + * for each child table block found in the row's post.
345 + *
346 + * @param WP_Post $post The row's post (real or virtual).
347 + * @return void
348 + */
349 + private function render_column_child_shortcodes( WP_Post $post ): void {
350 + $child_blocks = $this->get_child_shortcode_blocks( $post );
261 351
262 - if (empty($child_blocks)) {
352 + if ( empty( $child_blocks ) ) {
263 353 echo '&mdash;';
264 354 return;
265 355 }
266 356
267 - $source_id = ! empty($post->tablekit_is_virtual)
357 + $source_id = ! empty( $post->tablekit_is_virtual )
268 358 ? (int) $post->tablekit_source_id
269 359 : (int) $post->ID;
270 360
271 - $this->virtual_posts->render_child_shortcode_panel($child_blocks, (int) $post->ID, $source_id);
361 + $this->virtual_posts->render_child_shortcode_panel( $child_blocks, (int) $post->ID, $source_id );
272 362 }
273 363
274 - private function render_column_source(WP_Post $post): void
275 - {
276 - if (! empty($post->tablekit_is_virtual)) {
277 - $this->render_virtual_source_cell($post);
364 + /**
365 + * Renders the "Source" column, dispatching to the virtual- or real-post variant.
366 + *
367 + * @param WP_Post $post The row's post (real or virtual).
368 + * @return void
369 + */
370 + private function render_column_source( WP_Post $post ): void {
371 + if ( ! empty( $post->tablekit_is_virtual ) ) {
372 + $this->render_virtual_source_cell( $post );
278 373 return;
279 374 }
280 375
281 - $this->render_real_source_cell($post);
376 + $this->render_real_source_cell( $post );
282 377 }
283 378
284 - private function render_virtual_source_cell(WP_Post $post): void
285 - {
286 - $source_type_obj = get_post_type_object($post->tablekit_source_type);
379 + /**
380 + * Renders the "Source" column contents for a virtual row: a link to the
381 + * source post plus a breakdown of block types/counts found in it.
382 + *
383 + * @param WP_Post $post The virtual row's post.
384 + * @return void
385 + */
386 + private function render_virtual_source_cell( WP_Post $post ): void {
387 + $source_type_obj = get_post_type_object( $post->tablekit_source_type );
287 388 $type_label = $source_type_obj
288 - ? esc_html($source_type_obj->labels->singular_name)
289 - : esc_html($post->tablekit_source_type);
389 + ? esc_html( $source_type_obj->labels->singular_name )
390 + : esc_html( $post->tablekit_source_type );
290 391
291 - $block_counts = $this->count_blocks_by_name((array) $post->tablekit_blocks);
392 + $block_counts = $this->count_blocks_by_name( (array) $post->tablekit_blocks );
292 393
293 394 echo '<div class="tablekit-source-cell">';
294 395 printf(
295 396 '<strong>%s:</strong> <a href="%s" target="_blank">%s</a><br>',
296 - $type_label,
297 - esc_url((string) get_edit_post_link($post->tablekit_source_id)),
298 - esc_html((string) $post->tablekit_source_title)
397 + $type_label, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already run through esc_html() above.
398 + esc_url( (string) get_edit_post_link( $post->tablekit_source_id ) ),
399 + esc_html( (string) $post->tablekit_source_title )
299 400 );
300 - $this->render_block_type_list($block_counts);
401 + $this->render_block_type_list( $block_counts );
301 402 echo '</div>';
302 403 }
303 404
304 - private function render_real_source_cell(WP_Post $post): void
305 - {
405 + /**
406 + * Renders the "Source" column contents for a real tablekit_table post: an
407 + * "All Tables" label plus a breakdown of the table blocks it contains.
408 + *
409 + * @param WP_Post $post The real row's post.
410 + * @return void
411 + */
412 + private function render_real_source_cell( WP_Post $post ): void {
306 413 echo '<div class="tablekit-source-cell">';
307 - echo '<strong>' . esc_html__('All Tables', 'table-builder-block') . '</strong><br>';
414 + echo '<strong>' . esc_html__( 'All Tables', 'table-builder-block' ) . '</strong><br>';
308 415
309 - $blocks = $this->get_child_shortcode_blocks($post);
416 + $blocks = $this->get_child_shortcode_blocks( $post );
310 417
311 - if (empty($blocks)) {
418 + if ( empty( $blocks ) ) {
312 419 echo '<span style="color:#999;font-size:11px;">'
313 - . esc_html__('No table blocks found', 'table-builder-block')
420 + . esc_html__( 'No table blocks found', 'table-builder-block' )
314 421 . '</span>';
315 422 } else {
316 - $this->render_block_type_list($this->count_blocks_by_name($blocks));
423 + $this->render_block_type_list( $this->count_blocks_by_name( $blocks ) );
317 424 }
318 425
319 426 echo '</div>';
320 427 }
321 428
322 - private function render_column_author(WP_Post $post): void
323 - {
324 - if (! empty($post->tablekit_is_virtual)) {
325 - echo esc_html($post->tablekit_source_author ?: '&mdash;');
429 + /**
430 + * Renders the "Author" column: source post's author for virtual rows,
431 + * or the post's own author otherwise.
432 + *
433 + * @param WP_Post $post The row's post (real or virtual).
434 + * @return void
435 + */
436 + private function render_column_author( WP_Post $post ): void {
437 + if ( ! empty( $post->tablekit_is_virtual ) ) {
438 + $author = $post->tablekit_source_author;
439 + echo esc_html( $author ? $author : '&mdash;' );
326 440 return;
327 441 }
328 442
329 - echo esc_html(get_the_author_meta('display_name', $post->post_author) ?: '&mdash;');
443 + $author_name = get_the_author_meta( 'display_name', $post->post_author );
444 + echo esc_html( $author_name ? $author_name : '&mdash;' );
330 445 }
331 446
332 - private function get_child_shortcode_blocks(WP_Post $post): array
333 - {
334 - if (! empty($post->tablekit_is_virtual) && ! empty($post->tablekit_blocks)) {
447 + /**
448 + * Gets the list of child table-block descriptors for a row's post (from its
449 + * cached virtual data, or freshly scanned for a real post).
450 + *
451 + * @param WP_Post $post The row's post (real or virtual).
452 + * @return array Block descriptors with block_name/block_id/shortcode_id keys.
453 + */
454 + private function get_child_shortcode_blocks( WP_Post $post ): array {
455 + if ( ! empty( $post->tablekit_is_virtual ) && ! empty( $post->tablekit_blocks ) ) {
335 456 return (array) $post->tablekit_blocks;
336 457 }
337 458
338 - $matched = $this->scanner->extract_table_blocks(parse_blocks((string) $post->post_content));
459 + $matched = $this->scanner->extract_table_blocks( parse_blocks( (string) $post->post_content ) );
339 460 $blocks = array();
340 461
341 - foreach ($matched as $block) {
462 + foreach ( $matched as $block ) {
342 463 $attrs = $block['attrs'] ?? array();
343 - $block_id = (string) ($attrs['blockID'] ?? '');
464 + $block_id = (string) ( $attrs['blockID'] ?? '' );
344 465
345 466 $blocks[] = array(
346 - 'block_name' => (string) ($block['blockName'] ?? ''),
467 + 'block_name' => (string) ( $block['blockName'] ?? '' ),
347 468 'block_id' => $block_id,
348 - 'shortcode_id' => $this->scanner->resolve_shortcode_id($attrs, $block_id),
469 + 'shortcode_id' => $this->scanner->resolve_shortcode_id( $attrs, $block_id ),
349 470 );
350 471 }
351 472
352 473 return $blocks;
@@ -351,15 +472,20 @@
351 472
352 473 return $blocks;
353 474 }
354 475
355 - private function render_block_type_list(array $block_counts): void
356 - {
357 - foreach ($block_counts as $block_name => $count) {
358 - echo '<span class="tablekit-source-block">-&gt; ' . esc_html(TableCPT::get_block_label($block_name));
476 + /**
477 + * Renders a list of block-type labels with their counts (e.g. "-> Data Table block ×2").
478 + *
479 + * @param array $block_counts Block counts keyed by block name, from count_blocks_by_name().
480 + * @return void
481 + */
482 + private function render_block_type_list( array $block_counts ): void {
483 + foreach ( $block_counts as $block_name => $count ) {
484 + echo '<span class="tablekit-source-block">-&gt; ' . esc_html( TableCPT::get_block_label( $block_name ) );
359 485
360 - if ($count > 1) {
361 - printf(' <strong class="tablekit-source-block-count">&times;%d</strong>', $count);
486 + if ( $count > 1 ) {
487 + printf( ' <strong class="tablekit-source-block-count">&times;%d</strong>', $count ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- %d forces integer output.
362 488 }
363 489
364 490 echo '</span>';
365 491 }
@@ -364,17 +490,22 @@
364 490 echo '</span>';
365 491 }
366 492 }
367 493
368 - private function count_blocks_by_name(array $blocks): array
369 - {
494 + /**
495 + * Tallies block descriptors by block name.
496 + *
497 + * @param array $blocks Block descriptors with a "block_name" key.
498 + * @return array<string,int> Counts keyed by block name.
499 + */
500 + private function count_blocks_by_name( array $blocks ): array {
370 501 $counts = array();
371 502
372 - foreach ($blocks as $block) {
373 - $name = (string) ($block['block_name'] ?? '');
503 + foreach ( $blocks as $block ) {
504 + $name = (string) ( $block['block_name'] ?? '' );
374 505
375 - if ('' !== $name) {
376 - $counts[$name] = ($counts[$name] ?? 0) + 1;
506 + if ( '' !== $name ) {
507 + $counts[ $name ] = ( $counts[ $name ] ?? 0 ) + 1;
377 508 }
378 509 }
379 510
380 511 return $counts;
@@ -379,9 +510,13 @@
379 510
380 511 return $counts;
381 512 }
382 513
383 - private function is_pro_active(): bool
384 - {
385 - return defined('TABLE_BUILDER_BLOCK_PRO_PLUGIN_VERSION');
514 + /**
515 + * Whether the Pro add-on plugin is active.
516 + *
517 + * @return bool
518 + */
519 + private function is_pro_active(): bool {
520 + return defined( 'TABLE_BUILDER_BLOCK_PRO_PLUGIN_VERSION' );
386 521 }
387 522 }