Completed
Push — add/post-images-package ( ff119f )
by Jeremy
12:24
created

Post_Images::from_thumbnail()   F

Complexity

Conditions 26
Paths 215

Size

Total Lines 87

Duplication

Lines 73
Ratio 83.91 %

Importance

Changes 0
Metric Value
cc 26
nc 215
nop 3
dl 73
loc 87
rs 3.1458
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Useful for finding an image to display alongside/in representation of a specific post.
4
 *
5
 * Includes a few different methods, all of which return a similar-format array containing
6
 * details of any images found. Everything can (should) be called statically, it's just a
7
 * function-bucket. You can also call Post_Images::get_image() to cycle through all of the methods until
8
 * one of them finds something useful.
9
 *
10
 * @package  automattic/jetpack-post-images
11
 */
12
13
namespace Automattic\Jetpack;
14
15
/**
16
 * Post_Images Class, used to find representative images for posts.
17
 */
18
class Post_Images {
19
	/**
20
	 * If a slideshow is embedded within a post, then parse out the images involved and return them
21
	 *
22
	 * @param int $post_id Post ID.
23
	 * @param int $width   Image Width.
24
	 * @param int $height  Image height.
25
	 */
26
	public static function from_slideshow( $post_id, $width = 200, $height = 200 ) {
27
		$images = array();
28
29
		$post = get_post( $post_id );
30
31
		if ( ! $post ) {
32
			return $images;
33
		}
34
35
		if ( ! empty( $post->post_password ) ) {
36
			return $images;
37
		}
38
39
		// no slideshow - bail.
40
		if ( false === has_shortcode( $post->post_content, 'slideshow' ) ) {
41
			return $images;
42
		}
43
44
		$permalink = get_permalink( $post->ID );
45
46
		// phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited -- we manipulate globals because the gallery shortcode uses the global object.
47
		$old_post                  = $GLOBALS['post'];
48
		$GLOBALS['post']           = $post;
49
		$old_shortcodes            = $GLOBALS['shortcode_tags'];
50
		$GLOBALS['shortcode_tags'] = array( 'slideshow' => $old_shortcodes['slideshow'] );
51
52
		// Find all the slideshows.
53
		preg_match_all( '/' . get_shortcode_regex() . '/sx', $post->post_content, $slideshow_matches, PREG_SET_ORDER );
54
55
		ob_start(); // The slideshow shortcode handler calls wp_print_scripts and wp_print_styles... not too happy about that.
56
57 View Code Duplication
		foreach ( $slideshow_matches as $slideshow_match ) {
0 ignored issues
show
Bug introduced by
The expression $slideshow_matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
58
			$slideshow = do_shortcode_tag( $slideshow_match );
59
			$pos       = stripos( $slideshow, 'jetpack-slideshow' );
60
61
			// must be something wrong - or we changed the output format in which case none of the following will work.
62
			if ( false === $pos ) {
63
				continue;
64
			}
65
66
			$start       = strpos( $slideshow, '[', $pos );
67
			$end         = strpos( $slideshow, ']', $start );
68
			$post_images = json_decode( wp_specialchars_decode( str_replace( "'", '"', substr( $slideshow, $start, $end - $start + 1 ) ), ENT_QUOTES ) ); // parse via JSON
69
			// If the JSON didn't decode don't try and act on it.
70
			if ( is_array( $post_images ) ) {
71
				foreach ( $post_images as $post_image ) {
72
					$post_image_id = absint( $post_image->id );
73
					if ( ! $post_image_id ) {
74
						continue;
75
					}
76
77
					$meta = wp_get_attachment_metadata( $post_image_id );
78
79
					// Must be larger than 200x200 (or user-specified).
80
					if ( ! isset( $meta['width'] ) || $meta['width'] < $width ) {
81
						continue;
82
					}
83
					if ( ! isset( $meta['height'] ) || $meta['height'] < $height ) {
84
						continue;
85
					}
86
87
					$url = wp_get_attachment_url( $post_image_id );
88
89
					$images[] = array(
90
						'type'       => 'image',
91
						'from'       => 'slideshow',
92
						'src'        => $url,
93
						'src_width'  => $meta['width'],
94
						'src_height' => $meta['height'],
95
						'href'       => $permalink,
96
					);
97
				}
98
			}
99
		}
100
		ob_end_clean();
101
102
		$GLOBALS['shortcode_tags'] = $old_shortcodes;
103
		$GLOBALS['post']           = $old_post;
104
		// phpcs:enable
105
106
		return $images;
107
	}
108
109
	/**
110
	 * If a gallery is detected, then get all the images from it.
111
	 *
112
	 * @param int int $post_id Post ID.
113
	 */
114
	public static function from_gallery( $post_id ) {
115
		$images = array();
116
117
		$post = get_post( $post_id );
118
119
		if ( ! $post ) {
120
			return $images;
121
		}
122
123
		if ( ! empty( $post->post_password ) ) {
124
			return $images;
125
		}
126
127
		$permalink = get_permalink( $post->ID );
128
129
		/**
130
		 *  Juggle global post object because the gallery shortcode uses the
131
		 *  global object.
132
		 *
133
		 *  See core ticket:
134
		 *  https://core.trac.wordpress.org/ticket/39304
135
		 */
136 View Code Duplication
		if ( isset( $GLOBALS['post'] ) ) {
137
			$juggle_post     = $GLOBALS['post'];
138
			$GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
139
			$galleries       = get_post_galleries( $post->ID, false );
140
			$GLOBALS['post'] = $juggle_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
141
		} else {
142
			$GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
143
			$galleries       = get_post_galleries( $post->ID, false );
144
			unset( $GLOBALS['post'] );
145
		}
146
147 View Code Duplication
		foreach ( $galleries as $gallery ) {
148
			if ( isset( $gallery['type'] ) && 'slideshow' === $gallery['type'] && ! empty( $gallery['ids'] ) ) {
149
				$image_ids  = explode( ',', $gallery['ids'] );
150
				$image_size = isset( $gallery['size'] ) ? $gallery['size'] : 'thumbnail';
151
				foreach ( $image_ids as $image_id ) {
152
					$image = wp_get_attachment_image_src( $image_id, $image_size );
153
					if ( ! empty( $image[0] ) ) {
154
						list( $raw_src ) = explode( '?', $image[0] ); // pull off any Query string (?w=250).
155
						$raw_src         = wp_specialchars_decode( $raw_src ); // rawify it.
156
						$raw_src         = esc_url_raw( $raw_src ); // clean it.
157
						$images[]        = array(
158
							'type' => 'image',
159
							'from' => 'gallery',
160
							'src'  => $raw_src,
161
							'href' => $permalink,
162
						);
163
					}
164
				}
165
			} elseif ( ! empty( $gallery['src'] ) ) {
166
				foreach ( $gallery['src'] as $src ) {
167
					list( $raw_src ) = explode( '?', $src ); // pull off any Query string (?w=250).
168
					$raw_src         = wp_specialchars_decode( $raw_src ); // rawify it.
169
					$raw_src         = esc_url_raw( $raw_src ); // clean it.
170
					$images[]        = array(
171
						'type' => 'image',
172
						'from' => 'gallery',
173
						'src'  => $raw_src,
174
						'href' => $permalink,
175
					);
176
				}
177
			}
178
		}
179
180
		return $images;
181
	}
182
183
	/**
184
	 * Get attachment images for a specified post and return them. Also make sure
185
	 * their dimensions are at or above a required minimum.
186
	 *
187
	 * @param int $post_id Post ID.
188
	 * @param int $width   Image width.
189
	 * @param int $height  Image height.
190
	 */
191
	public static function from_attachment( $post_id, $width = 200, $height = 200 ) {
192
		$images = array();
193
194
		$post = get_post( $post_id );
195
196
		if ( ! empty( $post->post_password ) ) {
197
			return $images;
198
		}
199
200
		$post_images = get_posts(
201
			array(
202
				'post_parent'      => $post_id,   // Must be children of post.
203
				'numberposts'      => 5,          // No more than 5.
204
				'post_type'        => 'attachment', // Must be attachments.
205
				'post_mime_type'   => 'image', // Must be images.
206
				'suppress_filters' => false,
207
			)
208
		);
209
210
		if ( ! $post_images ) {
211
			return $images;
212
		}
213
214
		$permalink = get_permalink( $post_id );
215
216 View Code Duplication
		foreach ( $post_images as $post_image ) {
217
			$current_image = self::get_attachment_data( $post_image->ID, $permalink, $width, $height );
218
			if ( false !== $current_image ) {
219
				$images[] = $current_image;
220
			}
221
		}
222
223
		/*
224
		* We only want to pass back attached images that were actually inserted.
225
		* We can load up all the images found in the HTML source and then
226
		* compare URLs to see if an image is attached AND inserted.
227
		*/
228
		$html_images     = self::from_html( $post_id );
229
		$inserted_images = array();
230
231 View Code Duplication
		foreach ( $html_images as $html_image ) {
232
			$src = wp_parse_url( $html_image['src'] );
233
			// strip off any query strings from src.
234
			if ( ! empty( $src['scheme'] ) && ! empty( $src['host'] ) ) {
235
				$inserted_images[] = $src['scheme'] . '://' . $src['host'] . $src['path'];
236
			} elseif ( ! empty( $src['host'] ) ) {
237
				$inserted_images[] = set_url_scheme( 'http://' . $src['host'] . $src['path'] );
238
			} else {
239
				$inserted_images[] = site_url( '/' ) . $src['path'];
240
			}
241
		}
242 View Code Duplication
		foreach ( $images as $i => $image ) {
243
			if ( ! in_array( $image['src'], $inserted_images, true ) ) {
244
				unset( $images[ $i ] );
245
			}
246
		}
247
248
		return $images;
249
	}
250
251
	/**
252
	 * Check if a Featured Image is set for this post, and return it in a similar
253
	 * format to the other images?_from_*() methods.
254
	 *
255
	 * @param int $post_id The post ID to check.
256
	 * @param int $width   Image width.
257
	 * @param int $height  Image height.
258
	 *
259
	 * @return array containing details of the Featured Image, or empty array if none.
260
	 */
261
	public static function from_thumbnail( $post_id, $width = 200, $height = 200 ) {
262
		$images = array();
263
264
		$post = get_post( $post_id );
265
266
		if ( ! empty( $post->post_password ) ) {
267
			return $images;
268
		}
269
270 View Code Duplication
		if ( 'attachment' === get_post_type( $post ) && wp_attachment_is_image( $post ) ) {
271
			$thumb = $post_id;
272
		} else {
273
			$thumb = get_post_thumbnail_id( $post );
274
		}
275
276 View Code Duplication
		if ( $thumb ) {
277
			$meta = wp_get_attachment_metadata( $thumb );
278
			// Must be larger than requested minimums.
279
			if ( ! isset( $meta['width'] ) || $meta['width'] < $width ) {
280
				return $images;
281
			}
282
			if ( ! isset( $meta['height'] ) || $meta['height'] < $height ) {
283
				return $images;
284
			}
285
286
			$too_big = ( ( ! empty( $meta['width'] ) && $meta['width'] > 1200 ) || ( ! empty( $meta['height'] ) && $meta['height'] > 1200 ) );
287
288
			if (
289
				$too_big &&
290
				(
291
					( class_exists( 'Jetpack' ) && \Jetpack::is_module_active( 'photon' ) )
292
					|| ( defined( 'IS_WPCOM' ) && \IS_WPCOM )
293
				)
294
			) {
295
				$img_src = wp_get_attachment_image_src( $thumb, array( 1200, 1200 ) );
296
			} else {
297
				$img_src = wp_get_attachment_image_src( $thumb, 'full' );
298
			}
299
			if ( ! is_array( $img_src ) ) {
300
				// If wp_get_attachment_image_src returns false but we know that there should be an image that could be used.
301
				// we try a bit harder and user the data that we have.
302
				$thumb_post_data = get_post( $thumb );
303
				$img_src         = array( $thumb_post_data->guid, $meta['width'], $meta['height'] );
304
			}
305
306
			$url    = $img_src[0];
307
			$images = array(
308
				array( // Other methods below all return an array of arrays.
309
					'type'       => 'image',
310
					'from'       => 'thumbnail',
311
					'src'        => $url,
312
					'src_width'  => $img_src[1],
313
					'src_height' => $img_src[2],
314
					'href'       => get_permalink( $thumb ),
315
					'alt_text'   => self::get_alt_text( $thumb ),
316
				),
317
			);
318
319
		}
320
321 View Code Duplication
		if ( empty( $images ) && ( defined( 'IS_WPCOM' ) && \IS_WPCOM ) ) {
322
			$meta_thumbnail = get_post_meta( $post_id, '_jetpack_post_thumbnail', true );
323
			if ( ! empty( $meta_thumbnail ) ) {
324
				if ( ! isset( $meta_thumbnail['width'] ) || $meta_thumbnail['width'] < $width ) {
325
					return $images;
326
				}
327
328
				if ( ! isset( $meta_thumbnail['height'] ) || $meta_thumbnail['height'] < $height ) {
329
					return $images;
330
				}
331
332
				$images = array(
333
					array( // Other methods below all return an array of arrays.
334
						'type'       => 'image',
335
						'from'       => 'thumbnail',
336
						'src'        => $meta_thumbnail['URL'],
337
						'src_width'  => $meta_thumbnail['width'],
338
						'src_height' => $meta_thumbnail['height'],
339
						'href'       => $meta_thumbnail['URL'],
340
						'alt_text'   => self::get_alt_text( $thumb ),
341
					),
342
				);
343
			}
344
		}
345
346
		return $images;
347
	}
348
349
	/**
350
	 * Get images from Gutenberg Image blocks.
351
	 *
352
	 * @since 6.9.0
353
	 *
354
	 * @param mixed $html_or_id The HTML string to parse for images, or a post id.
355
	 * @param int   $width      Minimum Image width.
356
	 * @param int   $height     Minimum Image height.
357
	 */
358
	public static function from_blocks( $html_or_id, $width = 200, $height = 200 ) {
359
		$images = array();
360
361
		$html_info = self::get_post_html( $html_or_id );
362
363
		if ( empty( $html_info['html'] ) ) {
364
			return $images;
365
		}
366
367
		// Look for block information in the HTML.
368
		$blocks = parse_blocks( $html_info['html'] );
369
		if ( empty( $blocks ) ) {
370
			return $images;
371
		}
372
373
		/*
374
		 * Let's loop through our blocks.
375
		 * Some blocks may include some other blocks. Let's go 2 levels deep to look for blocks
376
		 * that we support and that may include images (see get_images_from_block)
377
		 *
378
		 * @to-do: instead of looping manually (that's a lot of if and loops), search recursively instead.
379
		 */
380 View Code Duplication
		foreach ( $blocks as $block ) {
381
			if ( ! self::is_nested_block( $block ) || 'core/media-text' === $block['blockName'] ) {
382
				$images = self::get_images_from_block( $images, $block, $html_info, $width, $height );
0 ignored issues
show
Bug introduced by
It seems like $html_info defined by self::get_post_html($html_or_id) on line 361 can also be of type string; however, Automattic\Jetpack\Post_...get_images_from_block() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
383
			} else {
384
				foreach ( $block['innerBlocks'] as $inner_block ) {
385
					if ( ! self::is_nested_block( $inner_block ) ) {
386
						$images = self::get_images_from_block( $images, $inner_block, $html_info, $width, $height );
0 ignored issues
show
Bug introduced by
It seems like $html_info defined by self::get_post_html($html_or_id) on line 361 can also be of type string; however, Automattic\Jetpack\Post_...get_images_from_block() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
387
					} else {
388
						foreach ( $inner_block['innerBlocks'] as $inner_inner_block ) {
389
							$images = self::get_images_from_block( $images, $inner_inner_block, $html_info, $width, $height );
0 ignored issues
show
Bug introduced by
It seems like $html_info defined by self::get_post_html($html_or_id) on line 361 can also be of type string; however, Automattic\Jetpack\Post_...get_images_from_block() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
390
						}
391
					}
392
				}
393
			}
394
		}
395
396
		/**
397
		 * Returning a filtered array because get_attachment_data returns false
398
		 * for unsuccessful attempts.
399
		 */
400
		return array_filter( $images );
401
	}
402
403
	/**
404
	 * Very raw -- just parse the HTML and pull out any/all img tags and return their src
405
	 *
406
	 * @param mixed $html_or_id The HTML string to parse for images, or a post id.
407
	 * @param int   $width      Minimum Image width.
408
	 * @param int   $height     Minimum Image height.
409
	 *
410
	 * @uses DOMDocument
411
	 *
412
	 * @return array containing images
413
	 */
414
	public static function from_html( $html_or_id, $width = 200, $height = 200 ) {
415
		$images = array();
416
417
		$html_info = self::get_post_html( $html_or_id );
418
419
		if ( empty( $html_info['html'] ) ) {
420
			return $images;
421
		}
422
423
		// Do not go any further if DOMDocument is disabled on the server.
424
		if ( ! class_exists( 'DOMDocument' ) ) {
425
			return $images;
426
		}
427
428
		// Let's grab all image tags from the HTML.
429
		$dom_doc = new \DOMDocument();
430
431
		// The @ is not enough to suppress errors when dealing with libxml,
432
		// we have to tell it directly how we want to handle errors.
433
		libxml_use_internal_errors( true );
434
		@$dom_doc->loadHTML( $html_info['html'] ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
435
		libxml_use_internal_errors( false );
436
437
		$image_tags = $dom_doc->getElementsByTagName( 'img' );
438
439
		// For each image Tag, make sure it can be added to the $images array, and add it.
440 View Code Duplication
		foreach ( $image_tags as $image_tag ) {
441
			$img_src = $image_tag->getAttribute( 'src' );
442
443
			if ( empty( $img_src ) ) {
444
				continue;
445
			}
446
447
			// Do not grab smiley images that were automatically created by WP when entering text smilies.
448
			if ( stripos( $img_src, '/smilies/' ) ) {
449
				continue;
450
			}
451
452
			$meta = array(
453
				'width'    => (int) $image_tag->getAttribute( 'width' ),
454
				'height'   => (int) $image_tag->getAttribute( 'height' ),
455
				'alt_text' => $image_tag->getAttribute( 'alt' ),
456
			);
457
458
			/**
459
			 * Filters the switch to ignore minimum image size requirements. Can be used
460
			 * to add custom logic to image dimensions, like only enforcing one of the dimensions,
461
			 * or disabling it entirely.
462
			 *
463
			 * @since 6.4.0
464
			 *
465
			 * @param bool $ignore Should the image dimensions be ignored?
466
			 * @param array $meta Array containing image dimensions parsed from the markup.
467
			 */
468
			$ignore_dimensions = apply_filters( 'jetpack_postimages_ignore_minimum_dimensions', false, $meta );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $meta.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
469
470
			// Must be larger than 200x200 (or user-specified).
471
			if (
472
				! $ignore_dimensions
473
				&& (
474
					empty( $meta['width'] )
475
					|| empty( $meta['height'] )
476
					|| $meta['width'] < $width
477
					|| $meta['height'] < $height
478
				)
479
			) {
480
				continue;
481
			}
482
483
			$images[] = array(
484
				'type'       => 'image',
485
				'from'       => 'html',
486
				'src'        => $img_src,
487
				'src_width'  => $meta['width'],
488
				'src_height' => $meta['height'],
489
				'href'       => $html_info['post_url'],
490
				'alt_text'   => $meta['alt_text'],
491
			);
492
		}
493
		return $images;
494
	}
495
496
	/**
497
	 * Look for a representative image from a Blavatar (WordPress.com only, old site icon).
498
	 *
499
	 * @param    int $post_id The post ID to check.
500
	 * @param    int $size    Icon dimensions.
501
	 *
502
	 * @return array containing details of the image, or empty array if none.
503
	 */
504
	public static function from_blavatar( $post_id, $size = 96 ) {
505
506
		$permalink = get_permalink( $post_id );
507
508 View Code Duplication
		if ( function_exists( 'blavatar_domain' ) && function_exists( 'blavatar_exists' ) && function_exists( 'blavatar_url' ) ) {
509
			$domain = \blavatar_domain( $permalink );
510
511
			if ( ! \blavatar_exists( $domain ) ) {
512
				return array();
513
			}
514
515
			$url = \blavatar_url( $domain, 'img', $size );
516
		} else {
517
			$url = get_site_icon_url( $size );
518
			if ( ! $url ) {
519
				return array();
520
			}
521
		}
522
523
		return array(
524
			array(
525
				'type'       => 'image',
526
				'from'       => 'blavatar',
527
				'src'        => $url,
528
				'src_width'  => $size,
529
				'src_height' => $size,
530
				'href'       => $permalink,
531
				'alt_text'   => '',
532
			),
533
		);
534
	}
535
536
	/**
537
	 * Gets a post image from the author avatar.
538
	 *
539
	 * @param int    $post_id The post ID to check.
540
	 * @param int    $size The size of the avatar to get.
541
	 * @param string $default The default image to use.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $default not be false|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
542
	 *
543
	 * @return array containing details of the image, or empty array if none.
544
	 */
545
	public static function from_gravatar( $post_id, $size = 96, $default = false ) {
546
		$post      = get_post( $post_id );
547
		$permalink = get_permalink( $post_id );
548
549 View Code Duplication
		if ( function_exists( 'wpcom_get_avatar_url' ) ) {
550
			$url = \wpcom_get_avatar_url( $post->post_author, $size, $default, true );
551
			if ( $url && is_array( $url ) ) {
552
				$url = $url[0];
553
			}
554
		} else {
555
			$url = get_avatar_url(
556
				$post->post_author,
557
				array(
558
					'size'    => $size,
559
					'default' => $default,
560
				)
561
			);
562
		}
563
564
		return array(
565
			array(
566
				'type'       => 'image',
567
				'from'       => 'gravatar',
568
				'src'        => $url,
569
				'src_width'  => $size,
570
				'src_height' => $size,
571
				'href'       => $permalink,
572
				'alt_text'   => '',
573
			),
574
		);
575
	}
576
577
	/**
578
	 * Run through the different methods that we have available to try to find a single good
579
	 * display image for this post.
580
	 *
581
	 * @param int   $post_id Post ID to check.
582
	 * @param array $args    Other arguments (currently width and height required for images where possible to determine).
583
	 *
584
	 * @return array containing details of the best image to be used
585
	 */
586
	public static function get_image( $post_id, $args = array() ) {
587
		$image = '';
588
589
		/**
590
		 * Fires before we find a single good image for a specific post.
591
		 *
592
		 * @since 2.2.0
593
		 *
594
		 * @param int $post_id Post ID.
595
		 */
596
		do_action( 'jetpack_postimages_pre_get_image', $post_id );
597
		$media = self::get_images( $post_id, $args );
598
599 View Code Duplication
		if ( is_array( $media ) ) {
600
			foreach ( $media as $item ) {
601
				if ( 'image' === $item['type'] ) {
602
					$image = $item;
603
					break;
604
				}
605
			}
606
		}
607
608
		/**
609
		 * Fires after we find a single good image for a specific post.
610
		 *
611
		 * @since 2.2.0
612
		 *
613
		 * @param int $post_id Post ID.
614
		 */
615
		do_action( 'jetpack_postimages_post_get_image', $post_id );
616
617
		return $image;
618
	}
619
620
	/**
621
	 * Get an array containing a collection of possible images for this post, stopping once we hit a method
622
	 * that returns something useful.
623
	 *
624
	 * @param int   $post_id Post ID to check.
625
	 * @param array $args    Optional args, see defaults list for details.
626
	 *
627
	 * @return array containing images that would be good for representing this post
628
	 */
629
	public static function get_images( $post_id, $args = array() ) {
630
		// Figure out which image to attach to this post.
631
		$media = false;
632
633
		/**
634
		 * Filters the array of images that would be good for a specific post.
635
		 * This filter is applied before options ($args) filter the original array.
636
		 *
637
		 * @since 2.0.0
638
		 *
639
		 * @param array $media Array of images that would be good for a specific post.
640
		 * @param int $post_id Post ID.
641
		 * @param array $args Array of options to get images.
642
		 */
643
		$media = apply_filters( 'jetpack_images_pre_get_images', $media, $post_id, $args );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
644
		if ( $media ) {
645
			return $media;
646
		}
647
648
		$defaults = array(
649
			'width'               => 200, // Required minimum width (if possible to determine).
650
			'height'              => 200, // Required minimum height (if possible to determine).
651
652
			'fallback_to_avatars' => false, // Optionally include Blavatar and Gravatar (in that order) in the image stack.
653
			'avatar_size'         => 96, // Used for both Grav and Blav.
654
			'gravatar_default'    => false, // Default image to use if we end up with no Gravatar.
655
656
			'from_thumbnail'      => true, // Use these flags to specify which methods to use to find an image.
657
			'from_slideshow'      => true,
658
			'from_gallery'        => true,
659
			'from_attachment'     => true,
660
			'from_blocks'         => true,
661
			'from_html'           => true,
662
663
			'html_content'        => '', // HTML string to pass to from_html().
664
		);
665
		$args     = wp_parse_args( $args, $defaults );
0 ignored issues
show
Documentation introduced by
$defaults is of type array<string,integer|boo...tml_content":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
666
667
		$media = false;
668
		if ( $args['from_thumbnail'] ) {
669
			$media = self::from_thumbnail( $post_id, $args['width'], $args['height'] );
670
		}
671 View Code Duplication
		if ( ! $media && $args['from_slideshow'] ) {
672
			$media = self::from_slideshow( $post_id, $args['width'], $args['height'] );
673
		}
674
		if ( ! $media && $args['from_gallery'] ) {
675
			$media = self::from_gallery( $post_id );
676
		}
677 View Code Duplication
		if ( ! $media && $args['from_attachment'] ) {
678
			$media = self::from_attachment( $post_id, $args['width'], $args['height'] );
679
		}
680 View Code Duplication
		if ( ! $media && $args['from_blocks'] ) {
681
			if ( empty( $args['html_content'] ) ) {
682
				// Use the post_id, which will load the content.
683
				$media = self::from_blocks( $post_id, $args['width'], $args['height'] );
684
			} else {
685
				// If html_content is provided, use that.
686
				$media = self::from_blocks( $args['html_content'], $args['width'], $args['height'] );
687
			}
688
		}
689 View Code Duplication
		if ( ! $media && $args['from_html'] ) {
690
			if ( empty( $args['html_content'] ) ) {
691
				// Use the post_id, which will load the content.
692
				$media = self::from_html( $post_id, $args['width'], $args['height'] );
693
			} else {
694
				// If html_content is provided, use that.
695
				$media = self::from_html( $args['html_content'], $args['width'], $args['height'] );
696
			}
697
		}
698
699 View Code Duplication
		if ( ! $media && $args['fallback_to_avatars'] ) {
700
			$media = self::from_blavatar( $post_id, $args['avatar_size'] );
701
			if ( ! $media ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $media of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
702
				$media = self::from_gravatar( $post_id, $args['avatar_size'], $args['gravatar_default'] );
703
			}
704
		}
705
706
		/**
707
		 * Filters the array of images that would be good for a specific post.
708
		 * This filter is applied after options ($args) filter the original array.
709
		 *
710
		 * @since 2.0.0
711
		 *
712
		 * @param array $media Array of images that would be good for a specific post.
713
		 * @param int $post_id Post ID.
714
		 * @param array $args Array of options to get images.
715
		 */
716
		return apply_filters( 'jetpack_images_get_images', $media, $post_id, $args );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
717
	}
718
719
	/**
720
	 * Takes an image URL and pixel dimensions then returns a URL for the
721
	 * resized and cropped image.
722
	 *
723
	 * @param string $src    Image source.
724
	 * @param int    $width  Image width.
725
	 * @param int    $height Image height.
726
	 *
727
	 * @return string            Transformed image URL
728
	 */
729
	public static function fit_image_url( $src, $width, $height ) {
730
		$width  = (int) $width;
731
		$height = (int) $height;
732
733
		if ( $width < 1 || $height < 1 ) {
734
			return $src;
735
		}
736
737
		// See if we should bypass WordPress.com SaaS resizing.
738
		if ( has_filter( 'jetpack_images_fit_image_url_override' ) ) {
739
			/**
740
			 * Filters the image URL used after dimensions are set by Photon.
741
			 *
742
			 * @since 3.3.0
743
			 *
744
			 * @param string $src Image URL.
745
			 * @param int $width Image width.
746
			 * @param int $width Image height.
747
			 */
748
			return apply_filters( 'jetpack_images_fit_image_url_override', $src, $width, $height );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $width.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
749
		}
750
751
		// If WPCOM hosted image use native transformations.
752
		$img_host = wp_parse_url( $src, PHP_URL_HOST );
753 View Code Duplication
		if ( '.files.wordpress.com' === substr( $img_host, -20 ) ) {
754
			return add_query_arg(
755
				array(
756
					'w'    => $width,
757
					'h'    => $height,
758
					'crop' => 1,
759
				),
760
				set_url_scheme( $src )
761
			);
762
		}
763
764
		// Use Photon magic.
765
		if ( function_exists( 'jetpack_photon_url' ) ) {
766
			return \jetpack_photon_url( $src, array( 'resize' => "$width,$height" ) );
767
		}
768
769
		// Arg... no way to resize image using WordPress.com infrastructure!
770
		return $src;
771
	}
772
773
	/**
774
	 * Get HTML from given post content.
775
	 *
776
	 * @since 6.9.0
777
	 *
778
	 * @param mixed $html_or_id The HTML string to parse for images, or a post id.
779
	 *
780
	 * @return array $html_info {
781
	 * @type string $html     Post content.
782
	 * @type string $post_url Post URL.
783
	 * }
784
	 */
785
	public static function get_post_html( $html_or_id ) {
786 View Code Duplication
		if ( is_numeric( $html_or_id ) ) {
787
			$post = get_post( $html_or_id );
788
789
			if ( empty( $post ) || ! empty( $post->post_password ) ) {
790
				return '';
791
			}
792
793
			$html_info = array(
794
				'html'     => $post->post_content, // DO NOT apply the_content filters here, it will cause loops.
795
				'post_url' => get_permalink( $post->ID ),
796
			);
797
		} else {
798
			$html_info = array(
799
				'html'     => $html_or_id,
800
				'post_url' => '',
801
			);
802
		}
803
		return $html_info;
804
	}
805
806
	/**
807
	 * Get info about a WordPress attachment.
808
	 *
809
	 * @since 6.9.0
810
	 *
811
	 * @param int    $attachment_id Attachment ID.
812
	 * @param string $post_url      URL of the post, if we have one.
813
	 * @param int    $width         Minimum Image width.
814
	 * @param int    $height        Minimum Image height.
815
	 * @return array|bool           Image data or false if unavailable.
816
	 */
817
	public static function get_attachment_data( $attachment_id, $post_url, $width, $height ) {
818
		if ( empty( $attachment_id ) ) {
819
			return false;
820
		}
821
822
		$meta = wp_get_attachment_metadata( $attachment_id );
823
824
		if ( empty( $meta ) ) {
825
			return false;
826
		}
827
828 View Code Duplication
		if ( ! empty( $meta['videopress'] ) ) {
829
			// Use poster image for VideoPress videos.
830
			$url         = $meta['videopress']['poster'];
831
			$meta_width  = $meta['videopress']['width'];
832
			$meta_height = $meta['videopress']['height'];
833
		} elseif ( ! empty( $meta['thumb'] ) ) {
834
			// On WordPress.com, VideoPress videos have a 'thumb' property with the
835
			// poster image filename instead.
836
			$media_url   = wp_get_attachment_url( $attachment_id );
837
			$url         = str_replace( wp_basename( $media_url ), $meta['thumb'], $media_url );
838
			$meta_width  = $meta['width'];
839
			$meta_height = $meta['height'];
840
		} elseif ( wp_attachment_is( 'video', $attachment_id ) ) {
841
			// We don't have thumbnail images for non-VideoPress videos - skip them.
842
			return false;
843
		} else {
844
			if ( ! isset( $meta['width'] ) || ! isset( $meta['height'] ) ) {
845
				return false;
846
			}
847
			$url         = wp_get_attachment_url( $attachment_id );
848
			$meta_width  = $meta['width'];
849
			$meta_height = $meta['height'];
850
		}
851
852
		if ( $meta_width < $width || $meta_height < $height ) {
853
			return false;
854
		}
855
856
		return array(
857
			'type'       => 'image',
858
			'from'       => 'attachment',
859
			'src'        => $url,
860
			'src_width'  => $meta_width,
861
			'src_height' => $meta_height,
862
			'href'       => $post_url,
863
			'alt_text'   => self::get_alt_text( $attachment_id ),
864
		);
865
	}
866
867
	/**
868
	 * Get the alt text for an image or other media from the Media Library.
869
	 *
870
	 * @since 7.1
871
	 *
872
	 * @param int $attachment_id The Post ID of the media.
873
	 * @return string The alt text value or an emptry string.
874
	 */
875
	public static function get_alt_text( $attachment_id ) {
876
		return get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
877
	}
878
879
	/**
880
	 * Get an image from a block.
881
	 *
882
	 * @since 7.8.0
883
	 *
884
	 * @param array $images    Images found.
885
	 * @param array $block     Block and its attributes.
886
	 * @param array $html_info Info about the post where the block is found.
887
	 * @param int   $width     Desired image width.
888
	 * @param int   $height    Desired image height.
889
	 *
890
	 * @return array Array of images found.
891
	 */
892
	private static function get_images_from_block( $images, $block, $html_info, $width, $height ) {
893
		/**
894
		 * Parse content from Core Image blocks.
895
		 * If it is an image block for an image hosted on our site, it will have an ID.
896
		 * If it does not have an ID, let `from_html` parse that content later,
897
		 * and extract an image if it has size parameters.
898
		 */
899 View Code Duplication
		if (
900
			'core/image' === $block['blockName']
901
			&& ! empty( $block['attrs']['id'] )
902
		) {
903
			$images[] = self::get_attachment_data( $block['attrs']['id'], $html_info['post_url'], $width, $height );
904
		} elseif (
905
			'core/media-text' === $block['blockName']
906
			&& ! empty( $block['attrs']['mediaId'] )
907
		) {
908
			$images[] = self::get_attachment_data( $block['attrs']['mediaId'], $html_info['post_url'], $width, $height );
909
		} elseif (
910
			/**
911
			 * Parse content from Core Gallery blocks as well from Jetpack's Tiled Gallery and Slideshow blocks.
912
			 * Gallery blocks include the ID of each one of the images in the gallery.
913
			 */
914
			in_array( $block['blockName'], array( 'core/gallery', 'jetpack/tiled-gallery', 'jetpack/slideshow' ), true )
915
			&& ! empty( $block['attrs']['ids'] )
916
		) {
917
			foreach ( $block['attrs']['ids'] as $img_id ) {
918
				$images[] = self::get_attachment_data( $img_id, $html_info['post_url'], $width, $height );
919
			}
920
		} elseif (
921
			/**
922
			 * Parse content from Jetpack's Story block.
923
			 */
924
			'jetpack/story' === $block['blockName']
925
			&& ! empty( $block['attrs']['mediaFiles'] )
926
		) {
927
			foreach ( $block['attrs']['mediaFiles'] as $media_file ) {
928
				if ( ! empty( $media_file['id'] ) ) {
929
					$images[] = self::get_attachment_data( $media_file['id'], $html_info['post_url'], $width, $height );
930
				}
931
			}
932
		}
933
934
		return $images;
935
	}
936
937
	/**
938
	 * Check if a block has inner blocks.
939
	 *
940
	 * @since 7.8.0
941
	 *
942
	 * @param array $block Block and its attributes.
943
	 *
944
	 * @return bool
945
	 */
946
	private static function is_nested_block( $block ) {
947
		if ( ! empty( $block['innerBlocks'] ) ) {
948
			return true;
949
		}
950
951
		return false;
952
	}
953
}
954