PluginProbe
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables / 2.2.13
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables v2.2.13
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
table-builder-block / includes / Config / CPT / VirtualPostBuilder.php

VirtualPostBuilder.php in TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables 2.2.13, at includes/Config/CPT/VirtualPostBuilder.php

263 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Builds synthetic "virtual" tablekit_table posts for inline-only table blocks
4 *
5 * @package TableKit
6 */
7
8 namespace TableBuilder\Config\CPT;
9
10 defined( 'ABSPATH' ) || exit;
11
12 use WP_Post;
13 use WP_Query;
14
15 /**
16 * Creates and renders synthetic WP_Post rows for table blocks that exist
17 * inline in other posts' content, so they can appear in the Tables admin list.
18 */
19 class VirtualPostBuilder {
20
21 private const VIRTUAL_ID_BASE = -1000000;
22
23 /**
24 * Scans post content for inline table blocks.
25 *
26 * @var InlineTableScanner
27 */
28 private InlineTableScanner $scanner;
29
30 /**
31 * Stores the scanner used to locate inline table blocks.
32 *
33 * @param InlineTableScanner $scanner Scanner used to locate inline table blocks.
34 */
35 public function __construct( InlineTableScanner $scanner ) {
36 $this->scanner = $scanner;
37 }
38
39 /**
40 * Append synthetic "virtual" WP_Post rows to a query's results — one per
41 * source post containing inline table blocks — so they show up in the
42 * Tables admin list table alongside real tablekit_table posts.
43 *
44 * @param WP_Post[] $posts Posts already matched by the query.
45 * @param WP_Query $query The query being filtered.
46 * @return WP_Post[] Posts with virtual entries appended, when applicable.
47 */
48 public function inject_inline_block_tables( array $posts, WP_Query $query ): array {
49 if ( ! $this->is_cpt_admin_list_query( $query ) ) {
50 return $posts;
51 }
52
53 $inline_tables = $this->scanner->get_all_inline_block_tables();
54
55 if ( empty( $inline_tables ) ) {
56 return $posts;
57 }
58
59 $grouped = $this->group_inline_tables_by_source( $inline_tables );
60 foreach ( array_values( $grouped ) as $index => $group ) {
61 $posts[] = $this->build_virtual_post( $group, $index );
62 }
63
64 return $posts;
65 }
66
67 /**
68 * Map each virtual (negative) post ID back to the real source post ID it
69 * represents, so bulk actions on virtual rows can be redirected to the
70 * underlying real post.
71 *
72 * @return array<int,int> Virtual post ID => real source post ID.
73 */
74 public function get_virtual_source_id_map(): array {
75 $map = array();
76 $grouped = $this->group_inline_tables_by_source( $this->scanner->get_all_inline_block_tables() );
77
78 foreach ( array_values( $grouped ) as $index => $group ) {
79 $map[ self::VIRTUAL_ID_BASE - $index ] = (int) $group['source_id'];
80 }
81
82 return $map;
83 }
84
85 /**
86 * Renders a read-only shortcode text field with a copy-to-clipboard button.
87 *
88 * @param string $shortcode The shortcode text to display, e.g. `[tableKit post_id="12"]`.
89 * @return void
90 */
91 public function render_shortcode_input( string $shortcode ): void {
92 echo '<div class="tablekit-shortcode-wrap" style="position:relative;">';
93 printf(
94 '<input type="text" class="tablekit-shortcode-field" readonly value="%s" style="width:100%%;padding-right:32px;" />',
95 esc_attr( $shortcode )
96 );
97 $copy_label = esc_attr__( 'Copy shortcode', 'table-builder-block' );
98 printf(
99 '<button type="button" class="tablekit-copy-shortcode" data-copy-text="%s" aria-label="%s" title="%s" style="position:absolute;right:6px;top:50%%;transform:translateY(-50%%);border:0;background:transparent;color:#2271b1;cursor:pointer;padding:0;line-height:1;">'
100 . '<span class="tablekit-copy-icon" aria-hidden="true"><svg viewBox="0 0 20 20" focusable="false"><rect x="7" y="5" width="9" height="11" rx="2"></rect><path d="M4 12.5V4a2 2 0 0 1 2-2h7.5"></path></svg></span>'
101 . '</button>',
102 esc_attr( $shortcode ),
103 $copy_label, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already run through esc_attr__() above.
104 $copy_label // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already run through esc_attr__() above.
105 );
106 echo '</div>';
107 }
108
109 /**
110 * Renders a collapsible panel listing shortcodes for each child table
111 * block found within a source post.
112 *
113 * @param array $blocks Child block descriptors (block_name/block_id/shortcode_id).
114 * @param int $post_id The (real or virtual) post ID this panel belongs to.
115 * @param int $source_id The real source post ID the shortcodes should target.
116 * @return void
117 */
118 public function render_child_shortcode_panel( array $blocks, int $post_id, int $source_id ): void {
119 $panel_id = 'tablekit-children-' . absint( $post_id );
120 $count = count( $blocks );
121 /* translators: %d: Number of child shortcodes. */
122 $label_open = esc_js( sprintf( __( '&#9654; Child shortcodes (%d)', 'table-builder-block' ), $count ) );
123 /* translators: %d: Number of child shortcodes. */
124 $label_close = esc_js( sprintf( __( '&#9660; Child shortcodes (%d)', 'table-builder-block' ), $count ) );
125 ?>
126 <div class="tablekit-child-shortcode-toggle-wrap">
127 <button
128 type="button"
129 onclick="(function(btn){var p=document.getElementById('<?php echo esc_js( $panel_id ); ?>');var o=p.style.display!=='none';p.style.display=o?'none':'block';btn.innerHTML=o?'<?php echo $label_open; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- pre-escaped via esc_js() above. */ ?>':'<?php echo $label_close; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- pre-escaped via esc_js() above. */ ?>';})(this)"
130 class="tablekit-child-shortcode-toggle">
131 <?php
132 /* translators: %d: Number of child shortcodes. */
133 echo wp_kses_post( sprintf( __( '&#9654; Child shortcodes (%d)', 'table-builder-block' ), $count ) );
134 ?>
135 </button>
136 </div>
137 <div id="<?php echo esc_attr( $panel_id ); ?>" class="tablekit-child-shortcode-panel" style="display:none;">
138 <?php foreach ( $blocks as $block ) : ?>
139 <div class="tablekit-child-shortcode-item">
140 <span class="tablekit-child-shortcode-label">
141 <?php echo esc_html( TableCPT::get_block_label( $block['block_name'] ) ); ?>
142 </span>
143 <?php $child_shortcode = $this->build_child_shortcode( $block, $source_id ); ?>
144 <?php $this->render_shortcode_input( $child_shortcode ); ?>
145 </div>
146 <?php endforeach; ?>
147 </div>
148 <?php
149 }
150
151 /**
152 * Checks whether a query is the main admin list-table query for the Tables CPT.
153 *
154 * @param WP_Query $query The query to check.
155 * @return bool True if this is the Tables admin list query.
156 */
157 private function is_cpt_admin_list_query( WP_Query $query ): bool {
158 return is_admin()
159 && $query->is_main_query()
160 && TableCPT::POST_TYPE === $query->get( 'post_type' );
161 }
162
163 /**
164 * Group flat inline-table records by their source post ID.
165 *
166 * @param array $inline_tables Flat list of inline table records from InlineTableScanner.
167 * @return array<int,array> Records grouped by source_id, each with a "blocks" sub-array.
168 */
169 private function group_inline_tables_by_source( array $inline_tables ): array {
170 $grouped = array();
171
172 foreach ( $inline_tables as $table ) {
173 $sid = (int) $table['source_id'];
174
175 if ( ! isset( $grouped[ $sid ] ) ) {
176 $grouped[ $sid ] = array(
177 'source_id' => $sid,
178 'source_title' => $table['source_title'],
179 'source_type' => $table['source_type'],
180 'source_author' => $table['source_author'],
181 'source_date' => $table['source_date'],
182 'blocks' => array(),
183 );
184 }
185
186 $grouped[ $sid ]['blocks'][] = array(
187 'block_name' => $table['block_name'],
188 'block_id' => $table['block_id'],
189 'shortcode_id' => $table['shortcode_id'],
190 );
191 }
192
193 return $grouped;
194 }
195
196 /**
197 * Builds a synthetic (never-saved) WP_Post representing one source post's
198 * group of inline table blocks, for display in the Tables admin list table.
199 *
200 * @param array $group A single group from group_inline_tables_by_source().
201 * @param int $index Zero-based index used to derive a stable negative virtual ID.
202 * @return WP_Post The virtual post, with tablekit_* properties attached.
203 */
204 private function build_virtual_post( array $group, int $index ): WP_Post {
205 $block_count = count( $group['blocks'] );
206
207 $title = sprintf(
208 /* translators: %2$s = source post title. */
209 _n( 'Table in: %2$s', 'Tables in: %2$s', $block_count, 'table-builder-block' ),
210 $block_count,
211 $group['source_title']
212 );
213
214 $virtual_post = new WP_Post(
215 (object) array(
216 'ID' => self::VIRTUAL_ID_BASE - $index,
217 'post_title' => $title,
218 'post_type' => TableCPT::POST_TYPE,
219 'post_status' => 'publish',
220 'post_author' => 0,
221 'post_date' => $group['source_date'],
222 'post_date_gmt' => get_gmt_from_date( $group['source_date'] ),
223 'post_content' => '',
224 'post_excerpt' => '',
225 'post_name' => '',
226 'post_modified' => $group['source_date'],
227 'post_modified_gmt' => get_gmt_from_date( $group['source_date'] ),
228 'comment_status' => 'closed',
229 'ping_status' => 'closed',
230 'guid' => '',
231 'menu_order' => 0,
232 'post_mime_type' => '',
233 'comment_count' => 0,
234 'filter' => 'raw',
235 )
236 );
237
238 $virtual_post->tablekit_is_virtual = true;
239 $virtual_post->tablekit_source_id = $group['source_id'];
240 $virtual_post->tablekit_source_title = $group['source_title'];
241 $virtual_post->tablekit_source_type = $group['source_type'];
242 $virtual_post->tablekit_source_author = $group['source_author'];
243 $virtual_post->tablekit_blocks = $group['blocks'];
244
245 return $virtual_post;
246 }
247
248 /**
249 * Builds the [tableKit] shortcode text for a single child block.
250 *
251 * @param array $block Block descriptor with a "shortcode_id" key.
252 * @param int $source_id Real source post ID to fall back to when the block has no shortcode_id.
253 * @return string The shortcode text.
254 */
255 private function build_child_shortcode( array $block, int $source_id ): string {
256 if ( '' !== $block['shortcode_id'] ) {
257 return sprintf( '[tableKit id="%s"]', $block['shortcode_id'] );
258 }
259
260 return sprintf( '[tableKit post_id="%d"]', $source_id );
261 }
262 }
263