Completed
Push — add/search-toggle-sort-control ( cad92c...011550 )
by
unknown
68:46 queued 61:10
created

Jetpack_PostImages::from_slideshow()   C

Complexity

Conditions 13
Paths 11

Size

Total Lines 76

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
nc 11
nop 3
dl 0
loc 76
rs 5.8169
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
/**
4
 * Useful for finding an image to display alongside/in representation of a specific post.
5
 *
6
 * Includes a few different methods, all of which return a similar-format array containing
7
 * details of any images found. Everything can (should) be called statically, it's just a
8
 * function-bucket. You can also call Jetpack_PostImages::get_image() to cycle through all of the methods until
9
 * one of them finds something useful.
10
 *
11
 * This file is included verbatim in Jetpack
12
 */
13
class Jetpack_PostImages {
14
	/**
15
	 * If a slideshow is embedded within a post, then parse out the images involved and return them
16
	 */
17
	static function from_slideshow( $post_id, $width = 200, $height = 200 ) {
18
		$images = array();
19
20
		$post = get_post( $post_id );
21
22
		if ( ! $post ) {
23
			return $images;
24
		}
25
26
		if ( ! empty( $post->post_password ) ) {
27
			return $images;
28
		}
29
30
		if ( false === has_shortcode( $post->post_content, 'slideshow' ) ) {
31
			return $images; // no slideshow - bail
32
		}
33
34
		$permalink = get_permalink( $post->ID );
35
36
		// Mechanic: Somebody set us up the bomb
37
		$old_post                  = $GLOBALS['post'];
38
		$GLOBALS['post']           = $post;
39
		$old_shortcodes            = $GLOBALS['shortcode_tags'];
40
		$GLOBALS['shortcode_tags'] = array( 'slideshow' => $old_shortcodes['slideshow'] );
41
42
		// Find all the slideshows
43
		preg_match_all( '/' . get_shortcode_regex() . '/sx', $post->post_content, $slideshow_matches, PREG_SET_ORDER );
44
45
		ob_start(); // The slideshow shortcode handler calls wp_print_scripts and wp_print_styles... not too happy about that
46
47
		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...
48
			$slideshow = do_shortcode_tag( $slideshow_match );
49
			if ( false === $pos = stripos( $slideshow, 'jetpack-slideshow' ) ) { // must be something wrong - or we changed the output format in which case none of the following will work
50
				continue;
51
			}
52
			$start       = strpos( $slideshow, '[', $pos );
53
			$end         = strpos( $slideshow, ']', $start );
54
			$post_images = json_decode( wp_specialchars_decode( str_replace( "'", '"', substr( $slideshow, $start, $end - $start + 1 ) ), ENT_QUOTES ) ); // parse via JSON
55
			// If the JSON didn't decode don't try and act on it.
56
			if ( is_array( $post_images ) ) {
57
				foreach ( $post_images as $post_image ) {
58
					if ( ! $post_image_id = absint( $post_image->id ) ) {
59
						continue;
60
					}
61
62
					$meta = wp_get_attachment_metadata( $post_image_id );
63
64
					// Must be larger than 200x200 (or user-specified)
65
					if ( ! isset( $meta['width'] ) || $meta['width'] < $width ) {
66
						continue;
67
					}
68
					if ( ! isset( $meta['height'] ) || $meta['height'] < $height ) {
69
						continue;
70
					}
71
72
					$url = wp_get_attachment_url( $post_image_id );
73
74
					$images[] = array(
75
						'type'       => 'image',
76
						'from'       => 'slideshow',
77
						'src'        => $url,
78
						'src_width'  => $meta['width'],
79
						'src_height' => $meta['height'],
80
						'href'       => $permalink,
81
					);
82
				}
83
			}
84
		}
85
		ob_end_clean();
86
87
		// Operator: Main screen turn on
88
		$GLOBALS['shortcode_tags'] = $old_shortcodes;
89
		$GLOBALS['post']           = $old_post;
90
91
		return $images;
92
	}
93
94
	/**
95
	 * If a gallery is detected, then get all the images from it.
96
	 */
97
	static function from_gallery( $post_id ) {
98
		$images = array();
99
100
		$post = get_post( $post_id );
101
102
		if ( ! $post ) {
103
			return $images;
104
		}
105
106
		if ( ! empty( $post->post_password ) ) {
107
			return $images;
108
		}
109
110
		$permalink = get_permalink( $post->ID );
111
112
		/**
113
		 *  Juggle global post object because the gallery shortcode uses the
114
		 *  global object.
115
		 *
116
		 *  See core ticket:
117
		 *  https://core.trac.wordpress.org/ticket/39304
118
		 */
119
		if ( isset( $GLOBALS['post'] ) ) {
120
			$juggle_post     = $GLOBALS['post'];
121
			$GLOBALS['post'] = $post;
122
			$galleries       = get_post_galleries( $post->ID, false );
123
			$GLOBALS['post'] = $juggle_post;
124
		} else {
125
			$GLOBALS['post'] = $post;
126
			$galleries       = get_post_galleries( $post->ID, false );
127
			unset( $GLOBALS['post'] );
128
		}
129
130
		foreach ( $galleries as $gallery ) {
131
			if ( isset( $gallery['type'] ) && 'slideshow' === $gallery['type'] && ! empty( $gallery['ids'] ) ) {
132
				$image_ids  = explode( ',', $gallery['ids'] );
133
				$image_size = isset( $gallery['size'] ) ? $gallery['size'] : 'thumbnail';
134
				foreach ( $image_ids as $image_id ) {
135
					$image = wp_get_attachment_image_src( $image_id, $image_size );
136 View Code Duplication
					if ( ! empty( $image[0] ) ) {
137
						list( $raw_src ) = explode( '?', $image[0] ); // pull off any Query string (?w=250)
138
						$raw_src         = wp_specialchars_decode( $raw_src ); // rawify it
139
						$raw_src         = esc_url_raw( $raw_src ); // clean it
140
						$images[]        = array(
141
							'type' => 'image',
142
							'from' => 'gallery',
143
							'src'  => $raw_src,
144
							'href' => $permalink,
145
						);
146
					}
147
				}
148 View Code Duplication
			} elseif ( ! empty( $gallery['src'] ) ) {
149
				foreach ( $gallery['src'] as $src ) {
150
					list( $raw_src ) = explode( '?', $src ); // pull off any Query string (?w=250)
151
					$raw_src         = wp_specialchars_decode( $raw_src ); // rawify it
152
					$raw_src         = esc_url_raw( $raw_src ); // clean it
153
					$images[]        = array(
154
						'type' => 'image',
155
						'from' => 'gallery',
156
						'src'  => $raw_src,
157
						'href' => $permalink,
158
					);
159
				}
160
			}
161
		}
162
163
		return $images;
164
	}
165
166
	/**
167
	 * Get attachment images for a specified post and return them. Also make sure
168
	 * their dimensions are at or above a required minimum.
169
	 */
170
	static function from_attachment( $post_id, $width = 200, $height = 200 ) {
171
		$images = array();
172
173
		$post = get_post( $post_id );
174
175
		if ( ! empty( $post->post_password ) ) {
176
			return $images;
177
		}
178
179
		$post_images = get_posts(
180
			array(
181
				'post_parent'      => $post_id,   // Must be children of post
182
				'numberposts'      => 5,          // No more than 5
183
				'post_type'        => 'attachment', // Must be attachments
184
				'post_mime_type'   => 'image', // Must be images
185
				'suppress_filters' => false,
186
			)
187
		);
188
189
		if ( ! $post_images ) {
190
			return $images;
191
		}
192
193
		$permalink = get_permalink( $post_id );
194
195
		foreach ( $post_images as $post_image ) {
196
			$current_image = self::get_attachment_data( $post_image->ID, $permalink, $width, $height );
197
			if ( false !== $current_image ) {
198
				$images[] = $current_image;
199
			}
200
		}
201
202
		/*
203
		* We only want to pass back attached images that were actually inserted.
204
		* We can load up all the images found in the HTML source and then
205
		* compare URLs to see if an image is attached AND inserted.
206
		*/
207
		$html_images     = self::from_html( $post_id );
208
		$inserted_images = array();
209
210
		foreach ( $html_images as $html_image ) {
211
			$src = wp_parse_url( $html_image['src'] );
212
			// strip off any query strings from src
213
			if ( ! empty( $src['scheme'] ) && ! empty( $src['host'] ) ) {
214
				$inserted_images[] = $src['scheme'] . '://' . $src['host'] . $src['path'];
215
			} elseif ( ! empty( $src['host'] ) ) {
216
				$inserted_images[] = set_url_scheme( 'http://' . $src['host'] . $src['path'] );
217
			} else {
218
				$inserted_images[] = site_url( '/' ) . $src['path'];
219
			}
220
		}
221
		foreach ( $images as $i => $image ) {
222
			if ( ! in_array( $image['src'], $inserted_images ) ) {
223
				unset( $images[ $i ] );
224
			}
225
		}
226
227
		return $images;
228
	}
229
230
	/**
231
	 * Check if a Featured Image is set for this post, and return it in a similar
232
	 * format to the other images?_from_*() methods.
233
	 *
234
	 * @param  int $post_id The post ID to check
235
	 * @return array containing details of the Featured Image, or empty array if none.
236
	 */
237
	static function from_thumbnail( $post_id, $width = 200, $height = 200 ) {
238
		$images = array();
239
240
		$post = get_post( $post_id );
241
242
		if ( ! empty( $post->post_password ) ) {
243
			return $images;
244
		}
245
246
		if ( 'attachment' === get_post_type( $post ) && wp_attachment_is_image( $post ) ) {
247
			$thumb = $post_id;
248
		} else {
249
			$thumb = get_post_thumbnail_id( $post );
250
		}
251
252
		if ( $thumb ) {
253
			$meta = wp_get_attachment_metadata( $thumb );
254
			// Must be larger than requested minimums
255
			if ( ! isset( $meta['width'] ) || $meta['width'] < $width ) {
256
				return $images;
257
			}
258
			if ( ! isset( $meta['height'] ) || $meta['height'] < $height ) {
259
				return $images;
260
			}
261
262
			$too_big = ( ( ! empty( $meta['width'] ) && $meta['width'] > 1200 ) || ( ! empty( $meta['height'] ) && $meta['height'] > 1200 ) );
263
264
			if (
265
				$too_big &&
266
				(
267
					( method_exists( 'Jetpack', 'is_module_active' ) && Jetpack::is_module_active( 'photon' ) ) ||
268
					( defined( 'IS_WPCOM' ) && IS_WPCOM )
269
				)
270
			) {
271
				$img_src = wp_get_attachment_image_src( $thumb, array( 1200, 1200 ) );
272
			} else {
273
				$img_src = wp_get_attachment_image_src( $thumb, 'full' );
274
			}
275
			if ( ! is_array( $img_src ) ) {
276
				// If wp_get_attachment_image_src returns false but we know that there should be an image that could be used.
277
				// we try a bit harder and user the data that we have.
278
				$thumb_post_data = get_post( $thumb );
279
				$img_src         = array( $thumb_post_data->guid, $meta['width'], $meta['height'] );
280
			}
281
282
			$url    = $img_src[0];
283
			$images = array(
284
				array( // Other methods below all return an array of arrays
285
					'type'       => 'image',
286
					'from'       => 'thumbnail',
287
					'src'        => $url,
288
					'src_width'  => $img_src[1],
289
					'src_height' => $img_src[2],
290
					'href'       => get_permalink( $thumb ),
291
					'alt_text'   => self::get_alt_text( $thumb ),
292
				),
293
			);
294
295
		}
296
297
		if ( empty( $images ) && ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
298
			$meta_thumbnail = get_post_meta( $post_id, '_jetpack_post_thumbnail', true );
299
			if ( ! empty( $meta_thumbnail ) ) {
300
				if ( ! isset( $meta_thumbnail['width'] ) || $meta_thumbnail['width'] < $width ) {
301
					return $images;
302
				}
303
304
				if ( ! isset( $meta_thumbnail['height'] ) || $meta_thumbnail['height'] < $height ) {
305
					return $images;
306
				}
307
308
				$images = array(
309
					array( // Other methods below all return an array of arrays
310
						'type'       => 'image',
311
						'from'       => 'thumbnail',
312
						'src'        => $meta_thumbnail['URL'],
313
						'src_width'  => $meta_thumbnail['width'],
314
						'src_height' => $meta_thumbnail['height'],
315
						'href'       => $meta_thumbnail['URL'],
316
						'alt_text'   => self::get_alt_text( $thumb ),
317
					),
318
				);
319
			}
320
		}
321
322
		return $images;
323
	}
324
325
	/**
326
	 * Get images from Gutenberg Image blocks.
327
	 *
328
	 * @since 6.9.0
329
	 *
330
	 * @param mixed $html_or_id The HTML string to parse for images, or a post id.
331
	 * @param int   $width      Minimum Image width.
332
	 * @param int   $height     Minimum Image height.
333
	 */
334
	public static function from_blocks( $html_or_id, $width = 200, $height = 200 ) {
335
		$images = array();
336
337
		$html_info = self::get_post_html( $html_or_id );
338
339
		if ( empty( $html_info['html'] ) ) {
340
			return $images;
341
		}
342
343
		// Look for block information in the HTML.
344
		$blocks = parse_blocks( $html_info['html'] );
345
		if ( empty( $blocks ) ) {
346
			return $images;
347
		}
348
349
		/*
350
		 * Let's loop through our blocks.
351
		 * Some blocks may include some other blocks. Let's go 2 levels deep to look for blocks
352
		 * that we support and that may include images (see get_images_from_block)
353
		 *
354
		 * @to-do: instead of looping manually (that's a lot of if and loops), search recursively instead.
355
		 */
356
		foreach ( $blocks as $block ) {
357
			if ( ! self::is_nested_block( $block ) || 'core/media-text' === $block['blockName'] ) {
358
				$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 337 can also be of type string; however, Jetpack_PostImages::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...
359
			} else {
360
				foreach ( $block['innerBlocks'] as $inner_block ) {
361
					if ( ! self::is_nested_block( $inner_block ) ) {
362
						$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 337 can also be of type string; however, Jetpack_PostImages::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...
363
					} else {
364
						foreach ( $inner_block['innerBlocks'] as $inner_inner_block ) {
365
							$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 337 can also be of type string; however, Jetpack_PostImages::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...
366
						}
367
					}
368
				}
369
			}
370
		}
371
372
		/**
373
		 * Returning a filtered array because get_attachment_data returns false
374
		 * for unsuccessful attempts.
375
		 */
376
		return array_filter( $images );
377
	}
378
379
	/**
380
	 * Very raw -- just parse the HTML and pull out any/all img tags and return their src
381
	 *
382
	 * @param mixed $html_or_id The HTML string to parse for images, or a post id.
383
	 * @param int   $width      Minimum Image width.
384
	 * @param int   $height     Minimum Image height.
385
	 *
386
	 * @uses DOMDocument
387
	 *
388
	 * @return array containing images
389
	 */
390
	static function from_html( $html_or_id, $width = 200, $height = 200 ) {
391
		$images = array();
392
393
		$html_info = self::get_post_html( $html_or_id );
394
395
		if ( empty( $html_info['html'] ) ) {
396
			return $images;
397
		}
398
399
		// Do not go any further if DOMDocument is disabled on the server.
400
		if ( ! class_exists( 'DOMDocument' ) ) {
401
			return $images;
402
		}
403
404
		// Let's grab all image tags from the HTML.
405
		$dom_doc = new DOMDocument();
406
407
		// The @ is not enough to suppress errors when dealing with libxml,
408
		// we have to tell it directly how we want to handle errors.
409
		libxml_use_internal_errors( true );
410
		@$dom_doc->loadHTML( $html_info['html'] );
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...
411
		libxml_use_internal_errors( false );
412
413
		$image_tags = $dom_doc->getElementsByTagName( 'img' );
414
415
		// For each image Tag, make sure it can be added to the $images array, and add it.
416
		foreach ( $image_tags as $image_tag ) {
417
			$img_src = $image_tag->getAttribute( 'src' );
418
419
			if ( empty( $img_src ) ) {
420
				continue;
421
			}
422
423
			// Do not grab smiley images that were automatically created by WP when entering text smilies.
424
			if ( stripos( $img_src, '/smilies/' ) ) {
425
				continue;
426
			}
427
428
			$meta = array(
429
				'width'    => (int) $image_tag->getAttribute( 'width' ),
430
				'height'   => (int) $image_tag->getAttribute( 'height' ),
431
				'alt_text' => $image_tag->getAttribute( 'alt' ),
432
			);
433
434
			/**
435
			 * Filters the switch to ignore minimum image size requirements. Can be used
436
			 * to add custom logic to image dimensions, like only enforcing one of the dimensions,
437
			 * or disabling it entirely.
438
			 *
439
			 * @since 6.4.0
440
			 *
441
			 * @param bool $ignore Should the image dimensions be ignored?
442
			 * @param array $meta Array containing image dimensions parsed from the markup.
443
			 */
444
			$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...
445
446
			// Must be larger than 200x200 (or user-specified).
447
			if (
448
				! $ignore_dimensions
449
				&& (
450
					empty( $meta['width'] )
451
					|| empty( $meta['height'] )
452
					|| $meta['width'] < $width
453
					|| $meta['height'] < $height
454
				)
455
			) {
456
				continue;
457
			}
458
459
			$images[] = array(
460
				'type'       => 'image',
461
				'from'       => 'html',
462
				'src'        => $img_src,
463
				'src_width'  => $meta['width'],
464
				'src_height' => $meta['height'],
465
				'href'       => $html_info['post_url'],
466
				'alt_text'   => $meta['alt_text'],
467
			);
468
		}
469
		return $images;
470
	}
471
472
	/**
473
	 * @param    int $post_id The post ID to check
474
	 * @param    int $size
475
	 * @return array containing details of the image, or empty array if none.
476
	 */
477
	static function from_blavatar( $post_id, $size = 96 ) {
478
479
		$permalink = get_permalink( $post_id );
480
481
		if ( function_exists( 'blavatar_domain' ) && function_exists( 'blavatar_exists' ) && function_exists( 'blavatar_url' ) ) {
482
			$domain = blavatar_domain( $permalink );
483
484
			if ( ! blavatar_exists( $domain ) ) {
485
				return array();
486
			}
487
488
			$url = blavatar_url( $domain, 'img', $size );
489
		} else {
490
			$url = get_site_icon_url( $size );
491
			if ( ! $url ) {
492
				return array();
493
			}
494
		}
495
496
		return array(
497
			array(
498
				'type'       => 'image',
499
				'from'       => 'blavatar',
500
				'src'        => $url,
501
				'src_width'  => $size,
502
				'src_height' => $size,
503
				'href'       => $permalink,
504
				'alt_text'   => '',
505
			),
506
		);
507
	}
508
509
	/**
510
	 * Gets a post image from the author avatar.
511
	 *
512
	 * @param int    $post_id The post ID to check.
513
	 * @param int    $size The size of the avatar to get.
514
	 * @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...
515
	 * @return array containing details of the image, or empty array if none.
516
	 */
517
	static function from_gravatar( $post_id, $size = 96, $default = false ) {
518
		$post      = get_post( $post_id );
519
		$permalink = get_permalink( $post_id );
520
521
		if ( function_exists( 'wpcom_get_avatar_url' ) ) {
522
			$url = wpcom_get_avatar_url( $post->post_author, $size, $default, true );
523
			if ( $url && is_array( $url ) ) {
524
				$url = $url[0];
525
			}
526
		} else {
527
			$url = get_avatar_url(
528
				$post->post_author,
529
				array(
530
					'size'    => $size,
531
					'default' => $default,
532
				)
533
			);
534
		}
535
536
		return array(
537
			array(
538
				'type'       => 'image',
539
				'from'       => 'gravatar',
540
				'src'        => $url,
541
				'src_width'  => $size,
542
				'src_height' => $size,
543
				'href'       => $permalink,
544
				'alt_text'   => '',
545
			),
546
		);
547
	}
548
549
	/**
550
	 * Run through the different methods that we have available to try to find a single good
551
	 * display image for this post.
552
	 *
553
	 * @param  int   $post_id
554
	 * @param array $args Other arguments (currently width and height required for images where possible to determine)
555
	 * @return array containing details of the best image to be used
556
	 */
557
	static function get_image( $post_id, $args = array() ) {
558
		$image = '';
559
560
		/**
561
		 * Fires before we find a single good image for a specific post.
562
		 *
563
		 * @since 2.2.0
564
		 *
565
		 * @param int $post_id Post ID.
566
		 */
567
		do_action( 'jetpack_postimages_pre_get_image', $post_id );
568
		$media = self::get_images( $post_id, $args );
569
570
		if ( is_array( $media ) ) {
571
			foreach ( $media as $item ) {
572
				if ( 'image' == $item['type'] ) {
573
					$image = $item;
574
					break;
575
				}
576
			}
577
		}
578
579
		/**
580
		 * Fires after we find a single good image for a specific post.
581
		 *
582
		 * @since 2.2.0
583
		 *
584
		 * @param int $post_id Post ID.
585
		 */
586
		do_action( 'jetpack_postimages_post_get_image', $post_id );
587
588
		return $image;
589
	}
590
591
	/**
592
	 * Get an array containing a collection of possible images for this post, stopping once we hit a method
593
	 * that returns something useful.
594
	 *
595
	 * @param  int   $post_id
596
	 * @param  array $args Optional args, see defaults list for details
597
	 * @return array containing images that would be good for representing this post
598
	 */
599
	static function get_images( $post_id, $args = array() ) {
600
		// Figure out which image to attach to this post.
601
		$media = false;
602
603
		/**
604
		 * Filters the array of images that would be good for a specific post.
605
		 * This filter is applied before options ($args) filter the original array.
606
		 *
607
		 * @since 2.0.0
608
		 *
609
		 * @param array $media Array of images that would be good for a specific post.
610
		 * @param int $post_id Post ID.
611
		 * @param array $args Array of options to get images.
612
		 */
613
		$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...
614
		if ( $media ) {
615
			return $media;
616
		}
617
618
		$defaults = array(
619
			'width'               => 200, // Required minimum width (if possible to determine)
620
			'height'              => 200, // Required minimum height (if possible to determine)
621
622
			'fallback_to_avatars' => false, // Optionally include Blavatar and Gravatar (in that order) in the image stack
623
			'avatar_size'         => 96, // Used for both Grav and Blav
624
			'gravatar_default'    => false, // Default image to use if we end up with no Gravatar
625
626
			'from_thumbnail'      => true, // Use these flags to specify which methods to use to find an image
627
			'from_slideshow'      => true,
628
			'from_gallery'        => true,
629
			'from_attachment'     => true,
630
			'from_blocks'         => true,
631
			'from_html'           => true,
632
633
			'html_content'        => '', // HTML string to pass to from_html()
634
		);
635
		$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...
636
637
		$media = false;
638
		if ( $args['from_thumbnail'] ) {
639
			$media = self::from_thumbnail( $post_id, $args['width'], $args['height'] );
640
		}
641 View Code Duplication
		if ( ! $media && $args['from_slideshow'] ) {
642
			$media = self::from_slideshow( $post_id, $args['width'], $args['height'] );
643
		}
644
		if ( ! $media && $args['from_gallery'] ) {
645
			$media = self::from_gallery( $post_id );
646
		}
647 View Code Duplication
		if ( ! $media && $args['from_attachment'] ) {
648
			$media = self::from_attachment( $post_id, $args['width'], $args['height'] );
649
		}
650 View Code Duplication
		if ( ! $media && $args['from_blocks'] ) {
651
			if ( empty( $args['html_content'] ) ) {
652
				$media = self::from_blocks( $post_id, $args['width'], $args['height'] ); // Use the post_id, which will load the content
653
			} else {
654
				$media = self::from_blocks( $args['html_content'], $args['width'], $args['height'] ); // If html_content is provided, use that
655
			}
656
		}
657 View Code Duplication
		if ( ! $media && $args['from_html'] ) {
658
			if ( empty( $args['html_content'] ) ) {
659
				$media = self::from_html( $post_id, $args['width'], $args['height'] ); // Use the post_id, which will load the content
660
			} else {
661
				$media = self::from_html( $args['html_content'], $args['width'], $args['height'] ); // If html_content is provided, use that
662
			}
663
		}
664
665
		if ( ! $media && $args['fallback_to_avatars'] ) {
666
			$media = self::from_blavatar( $post_id, $args['avatar_size'] );
667
			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...
668
				$media = self::from_gravatar( $post_id, $args['avatar_size'], $args['gravatar_default'] );
669
			}
670
		}
671
672
		/**
673
		 * Filters the array of images that would be good for a specific post.
674
		 * This filter is applied after options ($args) filter the original array.
675
		 *
676
		 * @since 2.0.0
677
		 *
678
		 * @param array $media Array of images that would be good for a specific post.
679
		 * @param int $post_id Post ID.
680
		 * @param array $args Array of options to get images.
681
		 */
682
		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...
683
	}
684
685
	/**
686
	 * Takes an image URL and pixel dimensions then returns a URL for the
687
	 * resized and cropped image.
688
	 *
689
	 * @param  string $src
690
	 * @param  int    $dimension
0 ignored issues
show
Bug introduced by
There is no parameter named $dimension. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
691
	 * @return string            Transformed image URL
692
	 */
693
	static function fit_image_url( $src, $width, $height ) {
694
		$width  = (int) $width;
695
		$height = (int) $height;
696
697
		if ( $width < 1 || $height < 1 ) {
698
			return $src;
699
		}
700
701
		// See if we should bypass WordPress.com SaaS resizing
702
		if ( has_filter( 'jetpack_images_fit_image_url_override' ) ) {
703
			/**
704
			 * Filters the image URL used after dimensions are set by Photon.
705
			 *
706
			 * @since 3.3.0
707
			 *
708
			 * @param string $src Image URL.
709
			 * @param int $width Image width.
710
			 * @param int $width Image height.
711
			 */
712
			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...
713
		}
714
715
		// If WPCOM hosted image use native transformations
716
		$img_host = wp_parse_url( $src, PHP_URL_HOST );
0 ignored issues
show
Unused Code introduced by
The call to wp_parse_url() has too many arguments starting with PHP_URL_HOST.

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
		if ( '.files.wordpress.com' == substr( $img_host, -20 ) ) {
718
			return add_query_arg(
719
				array(
720
					'w'    => $width,
721
					'h'    => $height,
722
					'crop' => 1,
723
				),
724
				set_url_scheme( $src )
725
			);
726
		}
727
728
		// Use Photon magic
729
		if ( function_exists( 'jetpack_photon_url' ) ) {
730
			return jetpack_photon_url( $src, array( 'resize' => "$width,$height" ) );
731
		}
732
733
		// Arg... no way to resize image using WordPress.com infrastructure!
734
		return $src;
735
	}
736
737
	/**
738
	 * Get HTML from given post content.
739
	 *
740
	 * @since 6.9.0
741
	 *
742
	 * @param mixed $html_or_id The HTML string to parse for images, or a post id.
743
	 *
744
	 * @return array $html_info {
745
	 * @type string $html     Post content.
746
	 * @type string $post_url Post URL.
747
	 * }
748
	 */
749
	static function get_post_html( $html_or_id ) {
750
		if ( is_numeric( $html_or_id ) ) {
751
			$post = get_post( $html_or_id );
752
753
			if ( empty( $post ) || ! empty( $post->post_password ) ) {
754
				return '';
755
			}
756
757
			$html_info = array(
758
				'html'     => $post->post_content, // DO NOT apply the_content filters here, it will cause loops.
759
				'post_url' => get_permalink( $post->ID ),
760
			);
761
		} else {
762
			$html_info = array(
763
				'html'     => $html_or_id,
764
				'post_url' => '',
765
			);
766
		}
767
		return $html_info;
768
	}
769
770
	/**
771
	 * Get info about a WordPress attachment.
772
	 *
773
	 * @since 6.9.0
774
	 *
775
	 * @param int    $attachment_id Attachment ID.
776
	 * @param string $post_url      URL of the post, if we have one.
777
	 * @param int    $width         Minimum Image width.
778
	 * @param int    $height        Minimum Image height.
779
	 * @return array|bool           Image data or false if unavailable.
780
	 */
781
	public static function get_attachment_data( $attachment_id, $post_url = '', $width, $height ) {
782
		if ( empty( $attachment_id ) ) {
783
			return false;
784
		}
785
786
		$meta = wp_get_attachment_metadata( $attachment_id );
787
788
		// The image must be larger than 200x200.
789
		if ( ! isset( $meta['width'] ) || $meta['width'] < $width ) {
790
			return false;
791
		}
792
		if ( ! isset( $meta['height'] ) || $meta['height'] < $height ) {
793
			return false;
794
		}
795
796
		$url = wp_get_attachment_url( $attachment_id );
797
798
		return array(
799
			'type'       => 'image',
800
			'from'       => 'attachment',
801
			'src'        => $url,
802
			'src_width'  => $meta['width'],
803
			'src_height' => $meta['height'],
804
			'href'       => $post_url,
805
			'alt_text'   => self::get_alt_text( $attachment_id ),
806
		);
807
	}
808
809
	/**
810
	 * Get the alt text for an image or other media from the Media Library.
811
	 *
812
	 * @since 7.1
813
	 *
814
	 * @param int $attachment_id The Post ID of the media.
815
	 * @return string The alt text value or an emptry string.
816
	 */
817
	public static function get_alt_text( $attachment_id ) {
818
		return get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
819
	}
820
821
	/**
822
	 * Get an image from a block.
823
	 *
824
	 * @since 7.8.0
825
	 *
826
	 * @param array $images    Images found.
827
	 * @param array $block     Block and its attributes.
828
	 * @param array $html_info Info about the post where the block is found.
829
	 * @param int   $width     Desired image width.
830
	 * @param int   $height    Desired image height.
831
	 *
832
	 * @return array Array of images found.
833
	 */
834
	private static function get_images_from_block( $images, $block, $html_info, $width, $height ) {
835
		/**
836
		 * Parse content from Core Image blocks.
837
		 * If it is an image block for an image hosted on our site, it will have an ID.
838
		 * If it does not have an ID, let `from_html` parse that content later,
839
		 * and extract an image if it has size parameters.
840
		 */
841
		if (
842
			'core/image' === $block['blockName']
843
			&& ! empty( $block['attrs']['id'] )
844
		) {
845
			$images[] = self::get_attachment_data( $block['attrs']['id'], $html_info['post_url'], $width, $height );
846
		} elseif (
847
			'core/media-text' === $block['blockName']
848
			&& ! empty( $block['attrs']['mediaId'] )
849
		) {
850
			$images[] = self::get_attachment_data( $block['attrs']['mediaId'], $html_info['post_url'], $width, $height );
851
		} elseif (
852
			/**
853
			 * Parse content from Core Gallery blocks as well from Jetpack's Tiled Gallery and Slideshow blocks.
854
			 * Gallery blocks include the ID of each one of the images in the gallery.
855
			 */
856
			in_array( $block['blockName'], array( 'core/gallery', 'jetpack/tiled-gallery', 'jetpack/slideshow' ), true )
857
			&& ! empty( $block['attrs']['ids'] )
858
		) {
859
			foreach ( $block['attrs']['ids'] as $img_id ) {
860
				$images[] = self::get_attachment_data( $img_id, $html_info['post_url'], $width, $height );
861
			}
862
		}
863
864
		return $images;
865
	}
866
867
	/**
868
	 * Check if a block has inner blocks.
869
	 *
870
	 * @since 7.8.0
871
	 *
872
	 * @param array $block Block and its attributes.
873
	 *
874
	 * @return bool
875
	 */
876
	private static function is_nested_block( $block ) {
877
		if ( ! empty( $block['innerBlocks'] ) ) {
878
			return true;
879
		}
880
881
		return false;
882
	}
883
}
884