PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.13.1
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.13.1
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
← All changes | includes/api/loop-builder-controller.php +158 -25 2.8.02.13.1 View file →
@@ -1,9 +1,8 @@
1 1 <?php
2 2
3 3 namespace ABlocks\API;
4 4
5 -use ABlocks\Helper;
6 5 use WP_REST_Server;
7 6 use WP_REST_Response;
8 7
9 8 if ( ! defined( 'ABSPATH' ) ) {
@@ -81,12 +80,24 @@
81 80 $term_id = (int) $payload['term_id'];
82 81 $page = (int) ( isset( $payload['page'] ) ? $payload['page'] : 1 );
83 82 $is_archive = (bool) ( isset( $payload['is_archive'] ) ? $payload['is_archive'] : false );
84 83
84 + // This route is public, so the request may only narrow the loop to a
85 + // post type or taxonomy the site already exposes to visitors. sanitize_key
86 + // fixes the spelling, not the visibility: without this an anonymous
87 + // request could point an archive loop at an internal post type.
88 + if ( ! empty( $payload['archive_post_type'] ) && ! is_post_type_viewable( (string) $payload['archive_post_type'] ) ) {
89 + return $this->empty_response( $term_id, $post_id, 400 );
90 + }
91 + if ( ! empty( $taxonomy ) && ! is_taxonomy_viewable( (string) $taxonomy ) ) {
92 + return $this->empty_response( $term_id, $post_id, 400 );
93 + }
94 +
85 95 /* ---------------- ARCHIVE LOGIC (UNCHANGED) ---------------- */
86 96
87 97 if ( $is_archive ) {
88 98
99 + $template_slug = '';
89 100 if ( ! empty( $payload['archive_post_type'] ) ) {
90 101 $template_slug = 'archive-' . $payload['archive_post_type'];
91 102
92 103 } elseif ( ! empty( $payload['taxonomy'] ) ) {
@@ -113,30 +124,31 @@
113 124 ],
114 125 ],
115 126 ]);
116 127
117 - $post_content = current( $template_posts )->post_content;
118 - $blocks = parse_blocks( $post_content );
128 + $template_post = current( $template_posts );
129 + $blocks = $template_post ? parse_blocks( $template_post->post_content ) : [];
119 130
120 - $block_data = Helper::get_block_attributes_recursive(
121 - $loop_builder_block_id,
122 - 'ablocks/loop-builder',
123 - $blocks
124 - );
131 + } else {
125 132
126 - } else {
133 + $post = get_post( $post_id );
134 + $blocks = $this->is_post_readable( $post ) ? parse_blocks( $post->post_content ) : [];
135 + }//end if
127 136
128 - $block_data = Helper::get_block_attributes(
129 - $post_id,
130 - $loop_builder_block_id,
131 - 'ablocks/loop-builder'
132 - );
137 + // The loop may sit inside a synced pattern or template part referenced
138 + // from the content, or in the theme template rendered around the post.
139 + $loop_builder_block = $this->find_block( $blocks, $loop_builder_block_id, 'ablocks/loop-builder' );
140 + if ( ! $loop_builder_block ) {
141 + $loop_builder_block = $this->find_block_in_theme_templates( $loop_builder_block_id, 'ablocks/loop-builder' );
142 + }
133 143
134 - $post = get_post( $post_id );
135 - $post_content = $post->post_content;
136 - $blocks = parse_blocks( $post_content );
137 - }//end if
144 + if ( ! $loop_builder_block ) {
145 + return $this->empty_response( $term_id, $post_id, 404 );
146 + }
138 147
148 + $block_data = [ 'parentAttributes' => $loop_builder_block['attrs'] ];
149 + $blocks = [ $loop_builder_block ];
150 +
139 151 /* ---------------- QUERY LOGIC (UNCHANGED) ---------------- */
140 152
141 153 $query_vars = isset( $block_data['parentAttributes']['query'] )
142 154 ? $this->convert_to_wp_query_args( $block_data['parentAttributes']['query'] )
@@ -149,9 +161,23 @@
149 161 if ( $is_archive && ! empty( $payload['archive_post_type'] ) ) {
150 162 $query_vars['post_type'] = $payload['archive_post_type'];
151 163 }
152 164
153 - $query_vars['posts_per_page'] = $query_vars['posts_per_page'] * $page;
165 + // "Load more" asks for page N and receives the first N pages at once, so
166 + // the request's page number multiplies the stored page size. It comes
167 + // from an anonymous request, so bound the result: at most
168 + // `ablocks/loop_builder/max_posts_per_request` posts (never fewer than
169 + // one stored page), however large the page number.
170 + $per_page = (int) $query_vars['posts_per_page'];
171 + $max_posts = (int) apply_filters( 'ablocks/loop_builder/max_posts_per_request', 100, $block_data['parentAttributes'] );
172 + if ( $per_page < 1 ) {
173 + // A loop set to show every post still gets the bound.
174 + $query_vars['posts_per_page'] = max( 1, $max_posts );
175 + } else {
176 + $max_posts = max( $per_page, $max_posts );
177 + $page = max( 1, min( $page, (int) ceil( $max_posts / $per_page ) ) );
178 + $query_vars['posts_per_page'] = min( $per_page * $page, $max_posts );
179 + }
154 180
155 181 if ( ! empty( $taxonomy ) && $term_id ) {
156 182 $query_vars['tax_query'] = [
157 183 [
@@ -162,13 +188,8 @@
162 188 ],
163 189 ];
164 190 }
165 191
166 - $blocks = $this->get_loop_builder_blocks_by_block_id(
167 - $blocks,
168 - $loop_builder_block_id
169 - );
170 -
171 192 $updated_blocks = $this->update_loop_builder_query(
172 193 $blocks,
173 194 $loop_template_block_id,
174 195 $query_vars,
@@ -180,9 +201,16 @@
180 201 foreach ( $updated_blocks as $block ) {
181 202 $html .= serialize_block( $block );
182 203 }
183 204
184 - $rendered = apply_filters( 'the_content', do_blocks( $html ) );
205 + // Render the fragment the way block templates are rendered. Running the
206 + // already-rendered markup through the_content re-applied wpautop (stray
207 + // <p> tags) and let other plugins append their post-content extras.
208 + $rendered = do_shortcode( shortcode_unautop( $html ) );
209 + $rendered = do_blocks( $rendered );
210 + $rendered = wptexturize( $rendered );
211 + $rendered = convert_smilies( $rendered );
212 + $rendered = wp_filter_content_tags( $rendered, 'template' );
185 213
186 214 /* ---------------- REST RESPONSE (ONLY CHANGE) ---------------- */
187 215
188 216 return new WP_REST_Response(
@@ -195,8 +223,113 @@
195 223 ],
196 224 ],
197 225 200
198 226 );
227 + }
228 +
229 + /**
230 + * The response for a request that cannot be rendered.
231 + */
232 + private function empty_response( $term_id, $post_id, $status ) {
233 + return new WP_REST_Response(
234 + [
235 + 'success' => false,
236 + 'data' => [
237 + 'html' => '',
238 + 'term_id' => $term_id,
239 + 'post_id' => $post_id,
240 + ],
241 + ],
242 + $status
243 + );
244 + }
245 +
246 + /**
247 + * Only published, non-password-protected content is public; anything else
248 + * needs the current user to be able to read it.
249 + */
250 + private function is_post_readable( $post ) {
251 + if ( ! $post instanceof \WP_Post ) {
252 + return false;
253 + }
254 + if ( 'publish' === $post->post_status && ! post_password_required( $post ) ) {
255 + return true;
256 + }
257 + return current_user_can( 'read_post', $post->ID );
258 + }
259 +
260 + /**
261 + * Find a block by name + block_id, following synced pattern references
262 + * (core/block) and template parts (core/template-part).
263 + */
264 + private function find_block( array $blocks, $block_id, $block_name, array $visited = [] ) {
265 + foreach ( $blocks as $block ) {
266 + $name = isset( $block['blockName'] ) ? $block['blockName'] : '';
267 +
268 + if ( $name === $block_name && ( isset( $block['attrs']['block_id'] ) ? $block['attrs']['block_id'] : '' ) === $block_id ) {
269 + return $block;
270 + }
271 +
272 + if ( ! empty( $block['innerBlocks'] ) ) {
273 + $found = $this->find_block( $block['innerBlocks'], $block_id, $block_name, $visited );
274 + if ( $found ) {
275 + return $found;
276 + }
277 + }
278 +
279 + $ref_key = '';
280 + $content = null;
281 + if ( 'core/block' === $name && ! empty( $block['attrs']['ref'] ) ) {
282 + $ref_key = 'wp_block:' . (int) $block['attrs']['ref'];
283 + if ( ! isset( $visited[ $ref_key ] ) ) {
284 + $ref_post = get_post( (int) $block['attrs']['ref'] );
285 + if ( $ref_post && 'wp_block' === $ref_post->post_type && $this->is_post_readable( $ref_post ) ) {
286 + $content = $ref_post->post_content;
287 + }
288 + }
289 + } elseif ( 'core/template-part' === $name && ! empty( $block['attrs']['slug'] ) ) {
290 + $theme = ! empty( $block['attrs']['theme'] ) ? $block['attrs']['theme'] : get_stylesheet();
291 + $ref_key = 'wp_template_part:' . $theme . '//' . $block['attrs']['slug'];
292 + if ( ! isset( $visited[ $ref_key ] ) ) {
293 + $part = get_block_template( $theme . '//' . $block['attrs']['slug'], 'wp_template_part' );
294 + if ( $part && 'publish' === $part->status ) {
295 + $content = $part->content;
296 + }
297 + }
298 + }
299 +
300 + if ( is_string( $content ) && '' !== $content ) {
301 + $visited[ $ref_key ] = true;
302 + $found = $this->find_block( parse_blocks( $content ), $block_id, $block_name, $visited );
303 + if ( $found ) {
304 + return $found;
305 + }
306 + }
307 + }//end foreach
308 + return null;
309 + }
310 +
311 + /**
312 + * Fallback for loops placed in the active theme's templates or template
313 + * parts rather than in the post content itself.
314 + */
315 + private function find_block_in_theme_templates( $block_id, $block_name ) {
316 + foreach ( [ 'wp_template', 'wp_template_part' ] as $template_type ) {
317 + foreach ( get_block_templates( [], $template_type ) as $template ) {
318 + $content = (string) $template->content;
319 + if (
320 + 'publish' !== $template->status ||
321 + ( false === strpos( $content, $block_id ) && false === strpos( $content, '<!-- wp:block ' ) && false === strpos( $content, '<!-- wp:template-part ' ) )
322 + ) {
323 + continue;
324 + }
325 + $found = $this->find_block( parse_blocks( $content ), $block_id, $block_name );
326 + if ( $found ) {
327 + return $found;
328 + }
329 + }
330 + }
331 + return null;
199 332 }
200 333
201 334 public function get_loop_builder_blocks_by_block_id( $blocks, $target_block_id ) {
202 335 foreach ( $blocks as $block ) {