Completed
Push — story-block/fix/external-media... ( 0dd10e )
by
unknown
61:54 queued 51:09
created

story.php ➔ render_image()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 4
nop 1
dl 0
loc 34
rs 8.7537
c 0
b 0
f 0
1
<?php
2
/**
3
 * Story Block.
4
 *
5
 * @since 8.6.1
6
 *
7
 * @package automattic/jetpack
8
 */
9
10
namespace Automattic\Jetpack\Extensions\Story;
11
12
use Automattic\Jetpack\Blocks;
13
use Jetpack;
14
use Jetpack_Gutenberg;
15
16
const FEATURE_NAME = 'story';
17
const BLOCK_NAME   = 'jetpack/' . FEATURE_NAME;
18
19
const EMBED_SIZE        = array( 180, 320 );
20
const CROP_UP_TO        = 0.2;
21
const MAX_BULLETS       = 7;
22
const IMAGE_BREAKPOINTS = '(max-width: 460px) 576w, (max-width: 614px) 768w, 120vw'; // 120vw to match the 20% CROP_UP_TO ratio
23
24
/**
25
 * Registers the block for use in Gutenberg
26
 * This is done via an action so that we can disable
27
 * registration if we need to.
28
 */
29
function register_block() {
30
	Blocks::jetpack_register_block(
31
		BLOCK_NAME,
32
		array( 'render_callback' => __NAMESPACE__ . '\render_block' )
33
	);
34
}
35
add_action( 'init', __NAMESPACE__ . '\register_block' );
36
37
/**
38
 * Add missing `width`, `height`, `srcset` and `sizes` properties to images of the mediaFiles block attributes
39
 *
40
 * @param array $media_files  - List of media, each as an array containing the media attributes.
41
 *
42
 * @returns array $media_files
43
 */
44
function with_width_height_srcset_and_sizes( $media_files ) {
45
	return array_map(
46
		function ( $media_file ) {
47
			if ( ! isset( $media_file['id'] ) || ! empty( $media_file['srcset'] ) ) {
48
				return $media_file;
49
			}
50
			$attachment_id = $media_file['id'];
51
			if ( 'image' === $media_file['type'] ) {
52
				$image = wp_get_attachment_image_src( $attachment_id, 'full', false );
53
				if ( ! $image ) {
54
					return $media_file;
55
				}
56
				list( $src, $width, $height ) = $image;
57
				$image_meta                   = wp_get_attachment_metadata( $attachment_id );
58
				if ( ! is_array( $image_meta ) ) {
59
					return $media_file;
60
				}
61
				$size_array = array( absint( $width ), absint( $height ) );
62
				return array_merge(
63
					$media_file,
64
					array(
65
						'width'   => absint( $width ),
66
						'height'  => absint( $height ),
67
						'srcset'  => wp_calculate_image_srcset( $size_array, $src, $image_meta, $attachment_id ),
68
						'sizes'   => IMAGE_BREAKPOINTS,
69
						'title'   => get_the_title( $attachment_id ),
70
						'alt'     => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
71
						'caption' => wp_get_attachment_caption( $attachment_id ),
72
					)
73
				);
74
			} else {
75
				$video_meta = wp_get_attachment_metadata( $attachment_id );
76
				if ( ! isset( $video_meta['width'] ) || ! isset( $video_meta['height'] ) ) {
77
					return $media_file;
78
				}
79
				$url         = ! empty( $video_meta['original']['url'] ) ? $video_meta['original']['url'] : $media_file['url'];
80
				$description = ! empty( $video_meta['videopress']['description'] ) ? $video_meta['videopress']['description'] : $media_file['alt'];
81
				$media_file  = array_merge(
82
					$media_file,
83
					array(
84
						'width'   => absint( $video_meta['width'] ),
85
						'height'  => absint( $video_meta['height'] ),
86
						'alt'     => $description,
87
						'url'     => $url,
88
						'title'   => get_the_title( $attachment_id ),
89
						'caption' => wp_get_attachment_caption( $attachment_id ),
90
					)
91
				);
92
93
				// Set the poster attribute for the video tag if a poster image is available.
94
				if ( ! empty( $video_meta['videopress']['poster'] ) ) {
95
					$poster_url = $video_meta['videopress']['poster'];
96
				} elseif ( ! empty( $video_meta['thumb'] ) ) {
97
					$video_url  = wp_get_attachment_url( $attachment_id );
98
					$poster_url = str_replace( wp_basename( $video_url ), $video_meta['thumb'], $video_url );
99
				}
100
101
				if ( $poster_url ) {
102
					$poster_width  = esc_attr( $media_file['width'] );
103
					$poster_height = esc_attr( $media_file['height'] );
104
					$content_width = (int) Jetpack::get_content_width();
105 View Code Duplication
					if ( is_numeric( $content_width ) ) {
106
						$poster_height = round( ( $content_width * $poster_height ) / $poster_width );
107
						$poster_width  = $content_width;
108
					}
109
					$media_file = array_merge(
110
						$media_file,
111
						array(
112
							'poster' => add_query_arg( 'resize', rawurlencode( $poster_width . ',' . $poster_height ), $poster_url ),
0 ignored issues
show
Bug introduced by
The variable $poster_url does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
113
						)
114
					);
115
				}
116
				return $media_file;
117
			}
118
		},
119
		$media_files
120
	);
121
}
122
123
/**
124
 * Render an image inside a slide
125
 *
126
 * @param array $media  - Image information.
127
 *
128
 * @returns string
129
 */
130
function render_image( $media ) {
131
	if ( empty( $media['id'] ) || empty( $media['url'] ) ) {
132
		return __( 'Error retrieving media', 'jetpack' );
133
	}
134
	if ( empty( $media['id'] ) ) {
135
		return sprintf(
136
			'<img src="%s" alt="%s" class="wp-story-image" width="%d" height="%d" />',
137
			esc_url( $media['url'] ),
138
			esc_attr( isset( $media['alt'] ) ? $media['alt'] : '' ),
139
			EMBED_SIZE[0],
140
			EMBED_SIZE[1]
141
		);
142
	}
143
	$image      = wp_get_attachment_image_src( $media['id'], 'full', false );
144
	$crop_class = '';
145
	if ( $image ) {
146
		list( , $width, $height ) = $image;
147
		$crop_class               = get_image_crop_class( $width, $height );
148
	}
149
	// need to specify the size of the embed so it picks an image that is large enough for the `src` attribute
150
	// `sizes` is optimized for 1080x1920 (9:16) images
151
	// Note that the Story block does not have thumbnail support, it will load the right
152
	// image based on the viewport size only.
153
	return wp_get_attachment_image(
154
		$media['id'],
155
		EMBED_SIZE,
156
		false,
157
		array(
158
			'class' => sprintf( 'wp-story-image wp-image-%d %s', $media['id'], $crop_class ),
159
			'sizes' => IMAGE_BREAKPOINTS,
160
			'title' => get_the_title( $media['id'] ),
161
		)
162
	);
163
}
164
165
/**
166
 * Return the css crop class if image width and height requires it
167
 *
168
 * @param int $width   - Image width.
169
 * @param int $height  - Image height.
170
 *
171
 * @returns string The CSS class which will display a cropped image
172
 */
173
function get_image_crop_class( $width, $height ) {
174
	$crop_class          = '';
175
	$media_aspect_ratio  = $width / $height;
176
	$target_aspect_ratio = EMBED_SIZE[0] / EMBED_SIZE[1];
177
	if ( $media_aspect_ratio >= $target_aspect_ratio ) {
178
		// image wider than canvas.
179
		$media_too_wide_to_crop = $media_aspect_ratio > $target_aspect_ratio / ( 1 - CROP_UP_TO );
180
		if ( ! $media_too_wide_to_crop ) {
181
			$crop_class = 'wp-story-crop-wide';
182
		}
183
	} else {
184
		// image narrower than canvas.
185
		$media_too_narrow_to_crop = $media_aspect_ratio < $target_aspect_ratio * ( 1 - CROP_UP_TO );
186
		if ( ! $media_too_narrow_to_crop ) {
187
			$crop_class = 'wp-story-crop-narrow';
188
		}
189
	}
190
	return $crop_class;
191
}
192
193
/**
194
 * Render a video inside a slide
195
 *
196
 * @param array $media  - Video information.
197
 *
198
 * @returns string
199
 */
200
function render_video( $media ) {
201
	if ( empty( $media['id'] ) || empty( $media['mime'] ) || empty( $media['url'] ) ) {
202
		return __( 'Error retrieving media', 'jetpack' );
203
	}
204
205
	$metadata = wp_get_attachment_metadata( $media['id'] );
206
207
	if ( ! empty( $metadata ) && ! empty( $metadata['videopress'] ) ) {
208
		// Use poster image for VideoPress videos.
209
		$poster_url  = $metadata['videopress']['poster'];
210
		$description = ! empty( $metadata['videopress']['description'] ) ? $metadata['videopress']['description'] : '';
211
		$meta_width  = ! empty( $metadata['videopress']['width'] ) ? $metadata['videopress']['width'] : '';
212
		$meta_height = ! empty( $metadata['videopress']['height'] ) ? $metadata['videopress']['height'] : '';
213
	} elseif ( ! empty( $metadata['thumb'] ) ) {
214
		// On WordPress.com, VideoPress videos have a 'thumb' property with the
215
		// poster image filename instead.
216
		$video_url   = wp_get_attachment_url( $media['id'] );
217
		$poster_url  = str_replace( wp_basename( $video_url ), $metadata['thumb'], $video_url );
218
		$description = ! empty( $media['alt'] ) ? $media['alt'] : '';
219
		$meta_width  = ! empty( $metadata['width'] ) ? $metadata['width'] : '';
220
		$meta_height = ! empty( $metadata['height'] ) ? $metadata['height'] : '';
221
	}
222
223
	if ( ! empty( $poster_url ) ) {
224
		$poster_width  = esc_attr( $meta_width );
0 ignored issues
show
Bug introduced by
The variable $meta_width does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
225
		$poster_height = esc_attr( $meta_height );
0 ignored issues
show
Bug introduced by
The variable $meta_height does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
226
		$content_width = (int) Jetpack::get_content_width();
227 View Code Duplication
		if ( is_numeric( $content_width ) ) {
228
			$poster_height = round( ( $content_width * $poster_height ) / $poster_width );
229
			$poster_width  = $content_width;
230
		}
231
		return sprintf(
232
			'<img title="%1$s" alt="%2$s" class="%3$s" src="%4$s"%5$s%6$s>',
233
			esc_attr( get_the_title( $media['id'] ) ),
234
			esc_attr( $description ),
0 ignored issues
show
Bug introduced by
The variable $description does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
235
			'wp-block-jetpack-story_image wp-story-image ' .
236
			get_image_crop_class( $meta_width, $meta_height ),
237
			esc_attr( add_query_arg( 'resize', rawurlencode( $poster_width . ',' . $poster_height ), $poster_url ) ),
238
			! empty( $meta_width ) ? ' width="' . esc_attr( $meta_width ) . '"' : '',
239
			! empty( $meta_height ) ? ' height="' . esc_attr( $meta_height ) . '"' : ''
240
		);
241
	}
242
243
	return sprintf(
244
		'<video
245
			title="%1$s"
246
			type="%2$s"
247
			class="wp-story-video intrinsic-ignore wp-video-%3$s"
248
			data-id="%3$s"
249
			src="%4$s">
250
		</video>',
251
		esc_attr( get_the_title( $media['id'] ) ),
252
		esc_attr( $media['mime'] ),
253
		$media['id'],
254
		esc_attr( $media['url'] )
255
	);
256
}
257
258
/**
259
 * Render a slide
260
 *
261
 * @param array $media  - Media information.
262
 * @param int   $index  - Index of the slide, first slide will be displayed by default, others hidden.
263
 *
264
 * @returns string
265
 */
266
function render_slide( $media, $index = 0 ) {
267
	$media_template = '';
268
	$media_type     = ! empty( $media['type'] ) ? $media['type'] : null;
269
	if ( ! $media_type ) {
270
		return '';
271
	}
272
	switch ( $media_type ) {
273
		case 'image':
274
			$media_template = render_image( $media, $index );
275
			break;
276
		case 'video':
277
			$media_template = render_video( $media, $index );
278
			break;
279
		case 'file':
280
			// VideoPress videos can sometimes have type 'file', and mime 'video/videopress' or 'video/mp4'.
281
			if ( 'video' === substr( $media['mime'], 0, 5 ) ) {
282
				$media_template = render_video( $media, $index );
283
			}
284
			break;
285
	}
286
	return sprintf(
287
		'<div class="wp-story-slide" style="display: %s;">
288
			<figure>%s</figure>
289
		</div>',
290
		0 === $index ? 'block' : 'none',
291
		$media_template
292
	);
293
}
294
295
/**
296
 * Render the top right icon on top of the story embed
297
 *
298
 * @param array $settings  - The block settings.
299
 *
300
 * @returns string
301
 */
302
function render_top_right_icon( $settings ) {
303
	$show_slide_count = isset( $settings['showSlideCount'] ) ? $settings['showSlideCount'] : false;
304
	$slide_count      = isset( $settings['slides'] ) ? count( $settings['slides'] ) : 0;
305
	if ( $show_slide_count ) {
306
		// Render the story block icon along with the slide count.
307
		return sprintf(
308
			'<div class="wp-story-embed-icon">
309
				<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" role="img" aria-hidden="true" focusable="false">
310
					<path d="M0 0h24v24H0z" fill="none"></path>
311
					<path fill-rule="evenodd" clip-rule="evenodd" d="M6 3H14V17H6L6 3ZM4 3C4 1.89543 4.89543 1 6 1H14C15.1046 1 16 1.89543 16 3V17C16 18.1046 15.1046 19 14 19H6C4.89543 19 4 18.1046 4 17V3ZM18 5C19.1046 5 20 5.89543 20 7V21C20 22.1046 19.1046 23 18 23H10C8.89543 23 8 22.1046 8 21H18V5Z"></path>
312
				</svg>
313
				<span>%d</span>
314
			</div>',
315
			$slide_count
316
		);
317
	} else {
318
		// Render the Fullscreen Gridicon.
319
		return (
320
			'<div class="wp-story-embed-icon-expand">
321
				<svg class="gridicon gridicons-fullscreen" role="img" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
322
					<g>
323
						<path d="M21 3v6h-2V6.41l-3.29 3.3-1.42-1.42L17.59 5H15V3zM3 3v6h2V6.41l3.29 3.3 1.42-1.42L6.41 5H9V3zm18 18v-6h-2v2.59l-3.29-3.29-1.41 1.41L17.59 19H15v2zM9 21v-2H6.41l3.29-3.29-1.41-1.42L5 17.59V15H3v6z"></path>
324
					</g>
325
				</svg>
326
			</div>'
327
		);
328
	}
329
}
330
331
/**
332
 * Render a pagination bullet
333
 *
334
 * @param int    $slide_index  - The slide index it corresponds to.
335
 * @param string $class_name   - Optional css class name(s) to customize the bullet element.
336
 *
337
 * @returns string
338
 */
339
function render_pagination_bullet( $slide_index, $class_name = '' ) {
340
	return sprintf(
341
		'<div class="wp-story-pagination-bullet %s" aria-label="%s">
342
			<div class="wp-story-pagination-bullet-bar"></div>
343
		</div>',
344
		esc_attr( $class_name ),
345
		/* translators: %d is the slide number (1, 2, 3...) */
346
		sprintf( __( 'Go to slide %d', 'jetpack' ), $slide_index )
347
	);
348
}
349
350
/**
351
 * Render pagination on top of the story embed
352
 *
353
 * @param array $settings  - The block settings.
354
 *
355
 * @returns string
356
 */
357
function render_pagination( $settings ) {
358
	$show_slide_count = isset( $settings['showSlideCount'] ) ? $settings['showSlideCount'] : false;
359
	if ( $show_slide_count ) {
360
		return '';
361
	}
362
	$slide_count     = isset( $settings['slides'] ) ? count( $settings['slides'] ) : 0;
363
	$bullet_count    = min( $slide_count, MAX_BULLETS );
364
	$bullet_ellipsis = $slide_count > $bullet_count
365
		? render_pagination_bullet( $bullet_count + 1, 'wp-story-pagination-ellipsis' )
366
		: '';
367
	return sprintf(
368
		'<div class="wp-story-pagination wp-story-pagination-bullets">
369
			%s
370
		</div>',
371
		join( "\n", array_map( __NAMESPACE__ . '\render_pagination_bullet', range( 1, $bullet_count ) ) ) . $bullet_ellipsis
372
	);
373
}
374
375
/**
376
 * Render story block
377
 *
378
 * @param array $attributes  - Block attributes.
379
 *
380
 * @returns string
381
 */
382
function render_block( $attributes ) {
383
	Jetpack_Gutenberg::load_assets_as_required( FEATURE_NAME );
384
385
	$media_files              = isset( $attributes['mediaFiles'] ) ? $attributes['mediaFiles'] : array();
386
	$settings_from_attributes = isset( $attributes['settings'] ) ? $attributes['settings'] : array();
387
388
	$settings = array_merge(
389
		$settings_from_attributes,
390
		array(
391
			'slides' => with_width_height_srcset_and_sizes( $media_files ),
392
		)
393
	);
394
395
	return sprintf(
396
		'<div class="%1$s" data-id="%2$s" data-settings="%3$s">
397
			<div class="wp-story-app">
398
				<div class="wp-story-display-contents" style="display: contents;">
399
					<a class="wp-story-container" href="%4$s" title="%5$s">
400
						<div class="wp-story-meta">
401
							<div class="wp-story-icon">
402
								<img alt="%6$s" src="%7$s" width="40" height="40">
403
							</div>
404
							<div>
405
								<div class="wp-story-title">
406
									%8$s
407
								</div>
408
							</div>
409
						</div>
410
						<div class="wp-story-wrapper">
411
							%9$s
412
						</div>
413
						<div class="wp-story-overlay">
414
							%10$s
415
						</div>
416
						%11$s
417
					</a>
418
				</div>
419
			</div>
420
		</div>',
421
		esc_attr( Blocks::classes( FEATURE_NAME, $attributes, array( 'wp-story', 'aligncenter' ) ) ),
422
		esc_attr( 'wp-story-' . get_the_ID() ),
423
		filter_var( wp_json_encode( $settings ), FILTER_SANITIZE_SPECIAL_CHARS ),
424
		get_permalink() . '?wp-story-load-in-fullscreen=true&amp;wp-story-play-on-load=true',
425
		__( 'Play story in new tab', 'jetpack' ),
426
		__( 'Site icon', 'jetpack' ),
427
		esc_attr( get_site_icon_url( 80, includes_url( 'images/w-logo-blue.png' ) ) ),
428
		esc_html( get_the_title() ),
429
		! empty( $media_files[0] ) ? render_slide( $media_files[0] ) : '',
430
		render_top_right_icon( $settings ),
431
		render_pagination( $settings )
432
	);
433
}
434