PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.13.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.13.0
2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 All 80 releases
ablocks / includes / api / loop-builder-controller.php

loop-builder-controller.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.13.0, at includes/api/loop-builder-controller.php

430 lines 12.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ABlocks\API;
4
5 use WP_REST_Server;
6 use WP_REST_Response;
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 class LoopBuilderController {
13
14 public function __construct() {
15 add_action( 'rest_api_init', [ $this, 'register_routes' ] );
16 }
17
18 public function register_routes() {
19
20 register_rest_route(
21 ABLOCKS_REST_NAMESPACE,
22 '/loop-builder',
23 [
24 'methods' => WP_REST_Server::READABLE,
25 'callback' => [ $this, 'render_loop_builder' ],
26 'permission_callback' => '__return_true',
27 'args' => [
28 'loop_builder_block_id' => [
29 'required' => true,
30 'type' => 'string',
31 'sanitize_callback' => 'sanitize_text_field',
32 ],
33 'loop_template_block_id' => [
34 'required' => true,
35 'type' => 'string',
36 'sanitize_callback' => 'sanitize_text_field',
37 ],
38 'taxonomy' => [
39 'required' => false,
40 'type' => 'string',
41 'sanitize_callback' => 'sanitize_key',
42 ],
43 'post_id' => [
44 'required' => false,
45 'type' => 'integer',
46 'sanitize_callback' => 'absint',
47 ],
48 'term_id' => [
49 'required' => false,
50 'type' => 'integer',
51 'sanitize_callback' => 'absint',
52 ],
53 'page' => [
54 'required' => false,
55 'type' => 'integer',
56 'default' => 1,
57 'sanitize_callback' => 'absint',
58 ],
59 'is_archive' => [
60 'required' => false,
61 'type' => 'boolean',
62 'default' => false,
63 ],
64 'archive_post_type' => [
65 'required' => false,
66 'type' => 'string',
67 'sanitize_callback' => 'sanitize_key',
68 ],
69 ],
70 ]
71 );
72 }
73
74 public function render_loop_builder( $payload ) {
75
76 $post_id = $payload['post_id'];
77 $loop_builder_block_id = $payload['loop_builder_block_id'];
78 $loop_template_block_id = $payload['loop_template_block_id'];
79 $taxonomy = $payload['taxonomy'];
80 $term_id = (int) $payload['term_id'];
81 $page = (int) ( isset( $payload['page'] ) ? $payload['page'] : 1 );
82 $is_archive = (bool) ( isset( $payload['is_archive'] ) ? $payload['is_archive'] : false );
83
84 /* ---------------- ARCHIVE LOGIC (UNCHANGED) ---------------- */
85
86 if ( $is_archive ) {
87
88 $template_slug = '';
89 if ( ! empty( $payload['archive_post_type'] ) ) {
90 $template_slug = 'archive-' . $payload['archive_post_type'];
91
92 } elseif ( ! empty( $payload['taxonomy'] ) ) {
93
94 if ( ! empty( $payload['term_slug'] ) ) {
95 $template_slug = 'archive-' . $payload['taxonomy'] . '-' . $payload['term_slug'];
96 } else {
97 $template_slug = 'archive-' . $payload['taxonomy'];
98 }
99 }
100
101 $theme_slug = wp_get_theme()->get_stylesheet();
102
103 $template_posts = get_posts([
104 'post_type' => 'wp_template',
105 'post_status' => 'publish',
106 'numberposts' => 1,
107 'name' => $template_slug,
108 'tax_query' => [
109 [
110 'taxonomy' => 'wp_theme',
111 'field' => 'slug',
112 'terms' => $theme_slug,
113 ],
114 ],
115 ]);
116
117 $template_post = current( $template_posts );
118 $blocks = $template_post ? parse_blocks( $template_post->post_content ) : [];
119
120 } else {
121
122 $post = get_post( $post_id );
123 $blocks = $this->is_post_readable( $post ) ? parse_blocks( $post->post_content ) : [];
124 }//end if
125
126 // The loop may sit inside a synced pattern or template part referenced
127 // from the content, or in the theme template rendered around the post.
128 $loop_builder_block = $this->find_block( $blocks, $loop_builder_block_id, 'ablocks/loop-builder' );
129 if ( ! $loop_builder_block ) {
130 $loop_builder_block = $this->find_block_in_theme_templates( $loop_builder_block_id, 'ablocks/loop-builder' );
131 }
132
133 if ( ! $loop_builder_block ) {
134 return new WP_REST_Response(
135 [
136 'success' => false,
137 'data' => [
138 'html' => '',
139 'term_id' => $term_id,
140 'post_id' => $post_id,
141 ],
142 ],
143 404
144 );
145 }
146
147 $block_data = [ 'parentAttributes' => $loop_builder_block['attrs'] ];
148 $blocks = [ $loop_builder_block ];
149
150 /* ---------------- QUERY LOGIC (UNCHANGED) ---------------- */
151
152 $query_vars = isset( $block_data['parentAttributes']['query'] )
153 ? $this->convert_to_wp_query_args( $block_data['parentAttributes']['query'] )
154 : [
155 'post_type' => 'post',
156 'post_status' => 'publish',
157 'posts_per_page' => get_option( 'posts_per_page' ),
158 ];
159
160 if ( $is_archive && ! empty( $payload['archive_post_type'] ) ) {
161 $query_vars['post_type'] = $payload['archive_post_type'];
162 }
163
164 $query_vars['posts_per_page'] = $query_vars['posts_per_page'] * $page;
165
166 if ( ! empty( $taxonomy ) && $term_id ) {
167 $query_vars['tax_query'] = [
168 [
169 'taxonomy' => $taxonomy,
170 'field' => 'term_id',
171 'terms' => [ $term_id ],
172 'operator' => 'IN',
173 ],
174 ];
175 }
176
177 $updated_blocks = $this->update_loop_builder_query(
178 $blocks,
179 $loop_template_block_id,
180 $query_vars,
181 $term_id
182 );
183
184 $html = '';
185
186 foreach ( $updated_blocks as $block ) {
187 $html .= serialize_block( $block );
188 }
189
190 // Render the fragment the way block templates are rendered. Running the
191 // already-rendered markup through the_content re-applied wpautop (stray
192 // <p> tags) and let other plugins append their post-content extras.
193 $rendered = do_shortcode( shortcode_unautop( $html ) );
194 $rendered = do_blocks( $rendered );
195 $rendered = wptexturize( $rendered );
196 $rendered = convert_smilies( $rendered );
197 $rendered = wp_filter_content_tags( $rendered, 'template' );
198
199 /* ---------------- REST RESPONSE (ONLY CHANGE) ---------------- */
200
201 return new WP_REST_Response(
202 [
203 'success' => true,
204 'data' => [
205 'html' => $rendered,
206 'term_id' => $term_id,
207 'post_id' => $post_id,
208 ],
209 ],
210 200
211 );
212 }
213
214 /**
215 * Only published, non-password-protected content is public; anything else
216 * needs the current user to be able to read it.
217 */
218 private function is_post_readable( $post ) {
219 if ( ! $post instanceof \WP_Post ) {
220 return false;
221 }
222 if ( 'publish' === $post->post_status && ! post_password_required( $post ) ) {
223 return true;
224 }
225 return current_user_can( 'read_post', $post->ID );
226 }
227
228 /**
229 * Find a block by name + block_id, following synced pattern references
230 * (core/block) and template parts (core/template-part).
231 */
232 private function find_block( array $blocks, $block_id, $block_name, array $visited = [] ) {
233 foreach ( $blocks as $block ) {
234 $name = isset( $block['blockName'] ) ? $block['blockName'] : '';
235
236 if ( $name === $block_name && ( isset( $block['attrs']['block_id'] ) ? $block['attrs']['block_id'] : '' ) === $block_id ) {
237 return $block;
238 }
239
240 if ( ! empty( $block['innerBlocks'] ) ) {
241 $found = $this->find_block( $block['innerBlocks'], $block_id, $block_name, $visited );
242 if ( $found ) {
243 return $found;
244 }
245 }
246
247 $ref_key = '';
248 $content = null;
249 if ( 'core/block' === $name && ! empty( $block['attrs']['ref'] ) ) {
250 $ref_key = 'wp_block:' . (int) $block['attrs']['ref'];
251 if ( ! isset( $visited[ $ref_key ] ) ) {
252 $ref_post = get_post( (int) $block['attrs']['ref'] );
253 if ( $ref_post && 'wp_block' === $ref_post->post_type && $this->is_post_readable( $ref_post ) ) {
254 $content = $ref_post->post_content;
255 }
256 }
257 } elseif ( 'core/template-part' === $name && ! empty( $block['attrs']['slug'] ) ) {
258 $theme = ! empty( $block['attrs']['theme'] ) ? $block['attrs']['theme'] : get_stylesheet();
259 $ref_key = 'wp_template_part:' . $theme . '//' . $block['attrs']['slug'];
260 if ( ! isset( $visited[ $ref_key ] ) ) {
261 $part = get_block_template( $theme . '//' . $block['attrs']['slug'], 'wp_template_part' );
262 if ( $part && 'publish' === $part->status ) {
263 $content = $part->content;
264 }
265 }
266 }
267
268 if ( is_string( $content ) && '' !== $content ) {
269 $visited[ $ref_key ] = true;
270 $found = $this->find_block( parse_blocks( $content ), $block_id, $block_name, $visited );
271 if ( $found ) {
272 return $found;
273 }
274 }
275 }//end foreach
276 return null;
277 }
278
279 /**
280 * Fallback for loops placed in the active theme's templates or template
281 * parts rather than in the post content itself.
282 */
283 private function find_block_in_theme_templates( $block_id, $block_name ) {
284 foreach ( [ 'wp_template', 'wp_template_part' ] as $template_type ) {
285 foreach ( get_block_templates( [], $template_type ) as $template ) {
286 $content = (string) $template->content;
287 if (
288 'publish' !== $template->status ||
289 ( false === strpos( $content, $block_id ) && false === strpos( $content, '<!-- wp:block ' ) && false === strpos( $content, '<!-- wp:template-part ' ) )
290 ) {
291 continue;
292 }
293 $found = $this->find_block( parse_blocks( $content ), $block_id, $block_name );
294 if ( $found ) {
295 return $found;
296 }
297 }
298 }
299 return null;
300 }
301
302 public function get_loop_builder_blocks_by_block_id( $blocks, $target_block_id ) {
303 foreach ( $blocks as $block ) {
304 if ( ! empty( $block['attrs']['block_id'] ) && $block['attrs']['block_id'] === $target_block_id ) {
305 return [ $block ];
306 }
307 if ( ! empty( $block['innerBlocks'] ) ) {
308 $found = $this->get_loop_builder_blocks_by_block_id( $block['innerBlocks'], $target_block_id );
309 if ( ! empty( $found ) ) {
310 return $found;
311 }
312 }
313 }
314 return [];
315 }
316
317 public function update_loop_builder_query( $blocks, $loop_template_block_id, $query_vars, $term_id ) {
318 foreach ( $blocks as &$block ) {
319 // perfectly not worked if multiple loop used
320 // !empty($block['attrs']['block_id']) && $block['attrs']['block_id'] === $loop_template_block_id
321
322 if ( ! empty( $block['blockName'] ) && $block['blockName'] === 'ablocks/loop-template' ) {
323 if ( ! isset( $block['attrs']['query'] ) ) {
324 $block['attrs']['query'] = [];
325 }
326 // Merge new query vars
327 $block['attrs']['query'] = array_merge( $block['attrs']['query'], $query_vars );
328 }
329
330 if ( 'ablocks/loop-filter' === $block['blockName'] ) {
331 $block['attrs']['active_term_id'] = $term_id;
332 }
333
334 // Recurse into inner blocks
335 if ( ! empty( $block['innerBlocks'] ) ) {
336 $block['innerBlocks'] = $this->update_loop_builder_query( $block['innerBlocks'], $loop_template_block_id, $query_vars, $term_id );
337 }
338 }//end foreach
339 return $blocks;
340 }
341
342 public function convert_to_wp_query_args( array $input ): array {
343 $args = [
344 'post_status' => 'publish',
345 ];
346
347 // Basic pagination
348 $args['posts_per_page'] = isset( $input['perPage'] ) ? (int) $input['perPage'] : get_option( 'posts_per_page' );
349 $args['paged'] = isset( $input['pages'] ) && $input['pages'] > 0 ? (int) $input['pages'] : 1;
350
351 // Post type
352 if ( ! empty( $input['postType'] ) ) {
353 $args['post_type'] = $input['postType'];
354 }
355
356 // Order and orderby
357 if ( ! empty( $input['order'] ) ) {
358 $args['order'] = $input['order'];
359 }
360
361 if ( ! empty( $input['orderBy'] ) ) {
362 $args['orderby'] = $input['orderBy'];
363 }
364
365 // Author
366 if ( ! empty( $input['author'] ) ) {
367 $args['author'] = $input['author'];
368 }
369
370 // Search
371 if ( ! empty( $input['search'] ) ) {
372 $args['s'] = $input['search'];
373 }
374
375 // Exclude posts
376 if ( ! empty( $input['exclude'] ) && is_array( $input['exclude'] ) ) {
377 $args['post__not_in'] = array_map( 'intval', $input['exclude'] );
378 }
379
380 // Sticky posts
381 if ( ! empty( $input['sticky'] ) ) {
382 if ( $input['sticky'] === 'include' ) {
383 $args['ignore_sticky_posts'] = 0;
384 } elseif ( $input['sticky'] === 'exclude' ) {
385 $args['ignore_sticky_posts'] = 1;
386 }
387 }
388
389 // Post parent (for hierarchical post types like pages)
390 if ( ! empty( $input['parents'] ) && is_array( $input['parents'] ) ) {
391 $args['post_parent__in'] = array_map( 'intval', $input['parents'] );
392 }
393
394 // Offset
395 if ( isset( $input['offset'] ) ) {
396 $args['offset'] = (int) $input['offset'];
397 }
398
399 // Format (post format taxonomy)
400 if ( ! empty( $input['format'] ) ) {
401 $args['tax_query'][] = [
402 'taxonomy' => 'post_format',
403 'field' => 'slug',
404 'terms' => [ 'post-format-' . sanitize_title( $input['format'] ) ],
405 ];
406 }
407
408 // Category
409 if ( ! empty( $input['category'] ) ) {
410 $args['category_name'] = $input['category'];
411 }
412
413 // Tag
414 if ( ! empty( $input['tag'] ) ) {
415 $args['tag'] = $input['tag'];
416 }
417
418 // Generic taxonomy query
419 if ( ! empty( $input['taxonomy'] ) && ! empty( $input['value'] ) ) {
420 $args['tax_query'][] = [
421 'taxonomy' => $input['taxonomy'],
422 'field' => 'slug',
423 'terms' => is_array( $input['value'] ) ? $input['value'] : [ $input['value'] ],
424 ];
425 }
426
427 return $args;
428 }
429 }
430