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 / InlineTableScanner.php

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

268 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Scans post content for inline table blocks
4 *
5 * @package TableKit
6 */
7
8 namespace TableBuilder\Config\CPT;
9
10 defined( 'ABSPATH' ) || exit;
11
12 use TableBuilder\Shortcode\ShortcodeUtils;
13 use WP_Post;
14 use WP_Query;
15
16 /**
17 * Finds and caches table blocks embedded inline within other posts' content.
18 */
19 class InlineTableScanner {
20
21 private const BLOCK_NAMESPACE = 'tablebuilder/';
22 private const CACHE_GROUP = 'tablekit_blocks';
23 private const CACHE_KEY = 'inline_tables_v2';
24 private const CACHE_TTL = 5 * MINUTE_IN_SECONDS;
25
26 /**
27 * Builds a SELECT-style options list of source posts containing inline table
28 * blocks, keyed by source post ID, labeled with block count.
29 *
30 * @return array<string,string> Options keyed by source post ID (as a string).
31 */
32 public function get_inline_table_source_options(): array {
33 $inline_tables = $this->get_all_inline_block_tables();
34
35 if ( empty( $inline_tables ) ) {
36 return array();
37 }
38
39 $options = array();
40 $grouped = $this->group_inline_tables_by_source( $inline_tables );
41
42 foreach ( array_values( $grouped ) as $group ) {
43 $block_count = count( $group['blocks'] );
44
45 $label = sprintf(
46 /* translators: %2$s = source post title. */
47 _n( 'Table in: %2$s', 'Tables in: %2$s', $block_count, 'table-builder-block' ),
48 $block_count,
49 $group['source_title']
50 );
51
52 $options[ (string) $group['source_id'] ] = $label;
53 }
54
55 return $options;
56 }
57
58 /**
59 * Scans every scannable post type for table blocks embedded inline (i.e. not
60 * inside a dedicated tablekit_table post), cached for 5 minutes.
61 *
62 * @return array Flat list of inline table block records (source_id/source_title/
63 * source_type/source_author/source_date/block_name/block_id/shortcode_id/attrs).
64 */
65 public function get_all_inline_block_tables(): array {
66 $cached = wp_cache_get( self::CACHE_KEY, self::CACHE_GROUP );
67
68 if ( false !== $cached ) {
69 return (array) $cached;
70 }
71
72 $results = array();
73 $post_types = $this->get_scannable_post_types();
74
75 if ( empty( $post_types ) ) {
76 $this->store_cache( $results );
77 return $results;
78 }
79
80 $query = new WP_Query(
81 array(
82 'post_type' => $post_types,
83 'post_status' => array( 'publish', 'draft', 'pending', 'private' ),
84 'posts_per_page' => -1,
85 'no_found_rows' => true,
86 's' => self::BLOCK_NAMESPACE,
87 'search_columns' => array( 'post_content' ),
88 )
89 );
90
91 if ( ! $query->have_posts() ) {
92 $this->store_cache( $results );
93 return $results;
94 }
95
96 foreach ( $query->posts as $post ) {
97 $found = $this->extract_table_blocks( parse_blocks( $post->post_content ) );
98
99 if ( empty( $found ) ) {
100 continue;
101 }
102
103 $author_name = (string) get_the_author_meta( 'display_name', $post->post_author );
104
105 foreach ( $found as $block ) {
106 $attrs = $block['attrs'] ?? array();
107 $block_id = (string) ( $attrs['blockID'] ?? '' );
108
109 $results[] = array(
110 'source_id' => (int) $post->ID,
111 'source_title' => (string) get_the_title( $post ),
112 'source_type' => (string) $post->post_type,
113 'source_author' => $author_name,
114 'source_date' => (string) $post->post_date,
115 'block_name' => (string) $block['blockName'],
116 'block_id' => $block_id,
117 'shortcode_id' => $this->resolve_shortcode_id( $attrs, $block_id ),
118 'attrs' => $attrs,
119 );
120 }
121 }
122
123 $this->store_cache( $results );
124
125 return $results;
126 }
127
128 /**
129 * Gets the list of post types to scan for inline table blocks: every
130 * REST-exposed post type except the Tables CPT itself, filterable via
131 * "tablekit_scannable_post_types".
132 *
133 * @return string[] Post type names.
134 */
135 public function get_scannable_post_types(): array {
136 static $cached = null;
137 if ( is_array( $cached ) ) {
138 return $cached;
139 }
140
141 $post_types = get_post_types( array( 'show_in_rest' => true ), 'names' );
142 unset( $post_types[ TableCPT::POST_TYPE ] );
143
144 /**
145 * Filters the post types scanned for inline table blocks.
146 *
147 * @since Unknown
148 *
149 * @param string[] $post_types REST-exposed post type names, excluding the Tables CPT itself.
150 */
151 $cached = (array) apply_filters( 'tablekit_scannable_post_types', array_values( $post_types ) );
152
153 return $cached;
154 }
155
156 /**
157 * Narrow a parsed block tree down to just the plugin's table blocks.
158 *
159 * @param array $blocks Parsed block tree.
160 * @return array Table blocks only.
161 */
162 public function extract_table_blocks( array $blocks ): array {
163 return ShortcodeUtils::filter_table_blocks( $blocks );
164 }
165
166 /**
167 * Invalidates the inline-table scan cache if the saved/deleted post is one
168 * of the scannable post types. Hooked to "save_post"/"delete_post".
169 *
170 * @param int $post_id Post ID being saved/deleted.
171 * @param WP_Post|null $post Optional. The post object, to avoid a redundant get_post() lookup.
172 * @return void
173 */
174 public function maybe_invalidate_cache( int $post_id, ?WP_Post $post = null ): void {
175 if ( null === $post ) {
176 $post = get_post( $post_id );
177 }
178
179 if ( ! ( $post instanceof WP_Post ) ) {
180 return;
181 }
182
183 if ( in_array( $post->post_type, $this->get_scannable_post_types(), true ) ) {
184 $this->invalidate_cache();
185 }
186 }
187
188 /**
189 * Clears the inline-table scan cache.
190 *
191 * @return void
192 */
193 public function invalidate_cache(): void {
194 wp_cache_delete( self::CACHE_KEY, self::CACHE_GROUP );
195 }
196
197 /**
198 * Resolves a block's short shortcode ID: its stored "shortId" attribute if
199 * present, otherwise a deterministic 6-digit hash derived from its blockID.
200 *
201 * @param array $attrs Block attributes.
202 * @param string $block_id Block's own ID (blockID attribute), used as the hash source.
203 * @return string The resolved shortcode ID, or an empty string if $block_id is also empty.
204 */
205 public function resolve_shortcode_id( array $attrs, string $block_id ): string {
206 $shortcode_id = (string) ( $attrs['shortId'] ?? '' );
207
208 if ( '' !== $shortcode_id ) {
209 return $shortcode_id;
210 }
211
212 if ( '' === $block_id ) {
213 return '';
214 }
215
216 $hash = 0;
217
218 foreach ( str_split( $block_id ) as $char ) {
219 $hash = ( ( $hash * 31 ) + ord( $char ) ) & 0x7FFFFFFF;
220 }
221
222 return str_pad( (string) ( $hash % 1000000 ), 6, '0', STR_PAD_LEFT );
223 }
224
225 /**
226 * Group flat inline-table records by their source post ID.
227 *
228 * @param array $inline_tables Flat list of inline table records from get_all_inline_block_tables().
229 * @return array<int,array> Records grouped by source_id, each with a "blocks" sub-array.
230 */
231 private function group_inline_tables_by_source( array $inline_tables ): array {
232 $grouped = array();
233
234 foreach ( $inline_tables as $table ) {
235 $sid = (int) $table['source_id'];
236
237 if ( ! isset( $grouped[ $sid ] ) ) {
238 $grouped[ $sid ] = array(
239 'source_id' => $sid,
240 'source_title' => $table['source_title'],
241 'source_type' => $table['source_type'],
242 'source_author' => $table['source_author'],
243 'source_date' => $table['source_date'],
244 'blocks' => array(),
245 );
246 }
247
248 $grouped[ $sid ]['blocks'][] = array(
249 'block_name' => $table['block_name'],
250 'block_id' => $table['block_id'],
251 'shortcode_id' => $table['shortcode_id'],
252 );
253 }
254
255 return $grouped;
256 }
257
258 /**
259 * Stores a scan result set in the object cache.
260 *
261 * @param array $results Result set from get_all_inline_block_tables().
262 * @return void
263 */
264 private function store_cache( array $results ): void {
265 wp_cache_set( self::CACHE_KEY, $results, self::CACHE_GROUP, self::CACHE_TTL );
266 }
267 }
268