PluginProbe
Gutenberg / 23.7.2
Gutenberg v23.7.2
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / build / scripts / block-library / latest-posts.php

latest-posts.php in Gutenberg 23.7.2, at build/scripts/block-library/latest-posts.php

318 lines 10.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server-side rendering of the `core/latest-posts` block.
4 *
5 * @package WordPress
6 */
7
8 /**
9 * The excerpt length set by the Latest Posts core block
10 * set at render time and used by the block itself.
11 *
12 * @var int
13 */
14 global $block_core_latest_posts_excerpt_length;
15 $block_core_latest_posts_excerpt_length = 0;
16
17 /**
18 * Callback for the excerpt_length filter used by
19 * the Latest Posts block at render time.
20 *
21 * @since 5.4.0
22 *
23 * @global int $block_core_latest_posts_excerpt_length Excerpt length set by the Latest Posts core block.
24 *
25 * @return int Returns the global $block_core_latest_posts_excerpt_length variable
26 * to allow the excerpt_length filter respect the Latest Block setting.
27 */
28 function gutenberg_block_core_latest_posts_get_excerpt_length() {
29 global $block_core_latest_posts_excerpt_length;
30 return $block_core_latest_posts_excerpt_length;
31 }
32
33 /**
34 * Renders the `core/latest-posts` block on server.
35 *
36 * @since 5.0.0
37 *
38 * @global WP_Post $post Global post object.
39 * @global int $block_core_latest_posts_excerpt_length Excerpt length set by the Latest Posts core block.
40 *
41 * @param array $attributes The block attributes.
42 *
43 * @return string Returns the post content with latest posts added.
44 */
45 function gutenberg_render_block_core_latest_posts( $attributes ) {
46 global $post, $block_core_latest_posts_excerpt_length;
47 static $rendering_stack = array();
48
49 $args = array(
50 'posts_per_page' => $attributes['postsToShow'],
51 'post_status' => 'publish',
52 'order' => $attributes['order'],
53 'orderby' => $attributes['orderBy'],
54 'ignore_sticky_posts' => true,
55 'no_found_rows' => true,
56 );
57
58 $block_core_latest_posts_excerpt_length = $attributes['excerptLength'];
59 add_filter( 'excerpt_length', 'gutenberg_block_core_latest_posts_get_excerpt_length', 20 );
60
61 $categories = $attributes['categories'] ?? null;
62 if ( ! empty( $categories ) && is_array( $categories ) ) {
63 $args['category__in'] = array_column( $categories, 'id' );
64 }
65 if ( isset( $attributes['selectedAuthor'] ) ) {
66 $args['author'] = $attributes['selectedAuthor'];
67 }
68
69 $query = new WP_Query( $args );
70
71 if ( isset( $attributes['displayFeaturedImage'] ) && $attributes['displayFeaturedImage'] ) {
72 update_post_thumbnail_cache( $query );
73 }
74
75 $list_items_markup = '';
76 // Snapshot the current global post so it can be restored after the loop.
77 // We use have_posts()/the_post() rather than foreach so that setup_postdata()
78 // is called for each post, establishing the correct global context for
79 // do_blocks() and other filters that run during full-content rendering.
80 $previous_post = $post ?? null;
81
82 while ( $query->have_posts() ) {
83 $query->the_post();
84 $post_link = esc_url( get_permalink() );
85 $title = get_the_title();
86
87 if ( ! $title ) {
88 $title = __( '(no title)' );
89 }
90
91 $list_items_markup .= '<li>';
92
93 if ( $attributes['displayFeaturedImage'] && has_post_thumbnail() ) {
94 $image_style = '';
95 if ( isset( $attributes['featuredImageSizeWidth'] ) ) {
96 $image_style .= sprintf( 'max-width:%spx;', $attributes['featuredImageSizeWidth'] );
97 }
98 if ( isset( $attributes['featuredImageSizeHeight'] ) ) {
99 $image_style .= sprintf( 'max-height:%spx;', $attributes['featuredImageSizeHeight'] );
100 }
101
102 $image_classes = 'wp-block-latest-posts__featured-image';
103 if ( isset( $attributes['featuredImageAlign'] ) ) {
104 $image_classes .= ' align' . $attributes['featuredImageAlign'];
105 }
106
107 $featured_image = get_the_post_thumbnail(
108 null,
109 $attributes['featuredImageSizeSlug'],
110 array(
111 'style' => esc_attr( $image_style ),
112 )
113 );
114 if ( $attributes['addLinkToFeaturedImage'] ) {
115 $featured_image = sprintf(
116 '<a href="%1$s" aria-label="%2$s">%3$s</a>',
117 esc_url( $post_link ),
118 esc_attr( $title ),
119 $featured_image
120 );
121 }
122 $list_items_markup .= sprintf(
123 '<div class="%1$s">%2$s</div>',
124 esc_attr( $image_classes ),
125 $featured_image
126 );
127 }
128
129 $list_items_markup .= sprintf(
130 '<a class="wp-block-latest-posts__post-title" href="%1$s">%2$s</a>',
131 esc_url( $post_link ),
132 $title
133 );
134
135 if ( isset( $attributes['displayAuthor'] ) && $attributes['displayAuthor'] ) {
136 $author_display_name = get_the_author_meta( 'display_name' );
137
138 /* translators: byline. %s: author. */
139 $byline = sprintf( __( 'by %s' ), $author_display_name );
140
141 if ( ! empty( $author_display_name ) ) {
142 $list_items_markup .= sprintf(
143 '<div class="wp-block-latest-posts__post-author">%1$s</div>',
144 $byline
145 );
146 }
147 }
148
149 if ( isset( $attributes['displayPostDate'] ) && $attributes['displayPostDate'] ) {
150 $list_items_markup .= sprintf(
151 '<time datetime="%1$s" class="wp-block-latest-posts__post-date">%2$s</time>',
152 esc_attr( get_the_date( 'c' ) ),
153 get_the_date( '' )
154 );
155 }
156
157 if ( isset( $attributes['displayPostContent'] ) && $attributes['displayPostContent']
158 && isset( $attributes['displayPostContentRadio'] ) && 'excerpt' === $attributes['displayPostContentRadio'] ) {
159
160 $trimmed_excerpt = get_the_excerpt();
161
162 /*
163 * Adds a "Read more" link with screen reader text.
164 * [&hellip;] is the default excerpt ending from wp_trim_excerpt() in Core.
165 */
166 if ( str_ends_with( $trimmed_excerpt, ' [&hellip;]' ) ) {
167 /** This filter is documented in wp-includes/formatting.php */
168 $excerpt_length = (int) apply_filters( 'excerpt_length', $block_core_latest_posts_excerpt_length );
169 if ( $excerpt_length <= $block_core_latest_posts_excerpt_length ) {
170 $trimmed_excerpt = substr( $trimmed_excerpt, 0, -11 );
171 $trimmed_excerpt .= sprintf(
172 /* translators: 1: A URL to a post, 2: Hidden accessibility text: Post title */
173 __( '… <a class="wp-block-latest-posts__read-more" href="%1$s" rel="noopener">Read more<span class="screen-reader-text">: %2$s</span></a>' ),
174 esc_url( $post_link ),
175 esc_html( $title )
176 );
177 }
178 }
179
180 if ( post_password_required() ) {
181 $trimmed_excerpt = __( 'This content is password protected.' );
182 }
183
184 $list_items_markup .= sprintf(
185 '<div class="wp-block-latest-posts__post-excerpt">%1$s</div>',
186 $trimmed_excerpt
187 );
188 }
189
190 if ( isset( $attributes['displayPostContent'] ) && $attributes['displayPostContent']
191 && isset( $attributes['displayPostContentRadio'] ) && 'full_post' === $attributes['displayPostContentRadio'] ) {
192
193 if ( post_password_required() ) {
194 $post_content = __( 'This content is password protected.' );
195 } else {
196 $post_id = get_the_ID();
197 $post_content = get_post_field( 'post_content', $post_id, 'raw' );
198 // Check if we're already rendering this post to prevent infinite recursion.
199 if ( in_array( $post_id, $rendering_stack, true ) ) {
200 $post_content = '';
201 } else {
202 $rendering_stack[] = $post_id;
203
204 try {
205 // Run through the actions that are typically taken on the_content.
206 $post_content = _wp_apply_block_content_filters( $post_content, 'latest-posts' );
207 } finally {
208 array_pop( $rendering_stack );
209 }
210 }
211 }
212
213 $post_content = str_replace( ']]>', ']]&gt;', $post_content );
214 $list_items_markup .= sprintf(
215 '<div class="wp-block-latest-posts__post-full-content">%1$s</div>',
216 $post_content
217 );
218 }
219
220 $list_items_markup .= "</li>\n";
221 }
222
223 // Restore the post context that was active before this secondary query.
224 // wp_reset_postdata() is intentionally not used here: when this block is
225 // rendered inside another post's content (e.g. via do_blocks()), it would
226 // restore to the main query post rather than the outer rendering post.
227 $post = $previous_post;
228 if ( $previous_post instanceof WP_Post ) {
229 setup_postdata( $previous_post );
230 }
231
232 remove_filter( 'excerpt_length', 'gutenberg_block_core_latest_posts_get_excerpt_length', 20 );
233
234 $layout = $attributes['layout'] ?? array();
235 $legacy_layout_type = (
236 isset( $attributes['postLayout'] ) &&
237 'grid' === $attributes['postLayout']
238 ) ? 'grid' : 'default';
239 $layout_type = $layout['type'] ?? $legacy_layout_type;
240 $column_count = $layout['columnCount'] ?? ( $attributes['columns'] ?? null );
241
242 $classes = array( 'wp-block-latest-posts__list' );
243 if ( 'grid' === $layout_type ) {
244 $classes[] = 'is-grid';
245 }
246 if ( 'grid' === $layout_type && ! empty( $column_count ) ) {
247 $classes[] = sanitize_title( 'columns-' . $column_count );
248 }
249 if ( 'grid' === $layout_type && ! empty( $column_count ) && ! empty( $layout['minimumColumnWidth'] ) ) {
250 $classes[] = 'has-native-responsive-grid';
251 }
252 if ( isset( $attributes['displayPostDate'] ) && $attributes['displayPostDate'] ) {
253 $classes[] = 'has-dates';
254 }
255 if ( isset( $attributes['displayAuthor'] ) && $attributes['displayAuthor'] ) {
256 $classes[] = 'has-author';
257 }
258 if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
259 $classes[] = 'has-link-color';
260 }
261
262 $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) );
263
264 return sprintf(
265 '<ul %1$s>%2$s</ul>',
266 $wrapper_attributes,
267 $list_items_markup
268 );
269 }
270
271 /**
272 * Registers the `core/latest-posts` block on server.
273 *
274 * @since 5.0.0
275 */
276 function gutenberg_register_block_core_latest_posts() {
277 register_block_type_from_metadata(
278 __DIR__ . '/latest-posts',
279 array(
280 'render_callback' => 'gutenberg_render_block_core_latest_posts',
281 )
282 );
283 }
284 add_action( 'init', 'gutenberg_register_block_core_latest_posts', 20 );
285
286 /**
287 * Handles outdated versions of the `core/latest-posts` block by converting
288 * attribute `categories` from a numeric string to an array with key `id`.
289 *
290 * This is done to accommodate the changes introduced in #20781 that sought to
291 * add support for multiple categories to the block. However, given that this
292 * block is dynamic, the usual provisions for block migration are insufficient,
293 * as they only act when a block is loaded in the editor.
294 *
295 * TODO: Remove when and if the bottom client-side deprecation for this block
296 * is removed.
297 *
298 * @since 5.5.0
299 *
300 * @param array $block A single parsed block object.
301 *
302 * @return array The migrated block object.
303 */
304 function gutenberg_block_core_latest_posts_migrate_categories( $block ) {
305 if (
306 'core/latest-posts' === $block['blockName'] &&
307 ! empty( $block['attrs']['categories'] ) &&
308 is_string( $block['attrs']['categories'] )
309 ) {
310 $block['attrs']['categories'] = array(
311 array( 'id' => absint( $block['attrs']['categories'] ) ),
312 );
313 }
314
315 return $block;
316 }
317 add_filter( 'render_block_data', 'gutenberg_block_core_latest_posts_migrate_categories' );
318