Completed
Push — add/sync-progress-api-endpoint... ( 2c9698...9cda41 )
by
unknown
82:24 queued 72:44
created

class.jetpack-post-images.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
		if ( !empty( $post->post_password ) )
22
			return $images;
23
24
		if ( false === has_shortcode( $post->post_content, 'slideshow' ) ) {
25
			return false; // no slideshow - bail
26
		}
27
28
		$permalink = get_permalink( $post->ID );
29
30
		// Mechanic: Somebody set us up the bomb
31
		$old_post = $GLOBALS['post'];
32
		$GLOBALS['post'] = $post;
33
		$old_shortcodes = $GLOBALS['shortcode_tags'];
34
		$GLOBALS['shortcode_tags'] = array( 'slideshow' => $old_shortcodes['slideshow'] );
35
36
		// Find all the slideshows
37
		preg_match_all( '/' . get_shortcode_regex() . '/sx', $post->post_content, $slideshow_matches, PREG_SET_ORDER );
38
39
		ob_start(); // The slideshow shortcode handler calls wp_print_scripts and wp_print_styles... not too happy about that
40
41
		foreach ( $slideshow_matches as $slideshow_match ) {
0 ignored issues
show
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...
42
			$slideshow = do_shortcode_tag( $slideshow_match );
43
			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
44
				continue;
45
			$start = strpos( $slideshow, '[', $pos );
46
			$end = strpos( $slideshow, ']', $start );
47
			$post_images = json_decode( wp_specialchars_decode( str_replace( "'", '"', substr( $slideshow, $start, $end - $start + 1 ) ), ENT_QUOTES ) ); // parse via JSON
48
			foreach ( $post_images as $post_image ) {
49
				if ( !$post_image_id = absint( $post_image->id ) )
50
					continue;
51
52
				$meta = wp_get_attachment_metadata( $post_image_id );
53
54
				// Must be larger than 200x200 (or user-specified)
55
				if ( !isset( $meta['width'] ) || $meta['width'] < $width )
56
					continue;
57
				if ( !isset( $meta['height'] ) || $meta['height'] < $height )
58
					continue;
59
60
				$url = wp_get_attachment_url( $post_image_id );
61
62
				$images[] = array(
63
					'type'       => 'image',
64
					'from'       => 'slideshow',
65
					'src'        => $url,
66
					'src_width'  => $meta['width'],
67
					'src_height' => $meta['height'],
68
					'href'       => $permalink,
69
				);
70
			}
71
		}
72
		ob_end_clean();
73
74
		// Operator: Main screen turn on
75
		$GLOBALS['shortcode_tags'] = $old_shortcodes;
76
		$GLOBALS['post'] = $old_post;
77
78
		return $images;
79
	}
80
81
	/**
82
	 * If a gallery is detected, then get all the images from it.
83
	 */
84
	static function from_gallery( $post_id ) {
85
		$images = array();
86
87
		$post = get_post( $post_id );
88
		if ( ! empty( $post->post_password ) ) {
89
			return $images;
90
		}
91
92
		$permalink = get_permalink( $post->ID );
93
94
		$galleries = get_post_galleries( $post->ID, false );
95
96
		foreach ( $galleries as $gallery ) {
97
			if ( isset( $gallery['type'] ) && 'slideshow' === $gallery['type'] && ! empty( $gallery['ids'] ) ) {
98
				$image_ids = explode( ',', $gallery['ids'] );
99
				$image_size = isset( $gallery['size'] ) ? $gallery['size'] : 'thumbnail';
100
				foreach ( $image_ids as $image_id ) {
101
					$image = wp_get_attachment_image_src( $image_id, $image_size );
102 View Code Duplication
					if ( ! empty( $image[0] ) ) {
103
						list( $raw_src ) = explode( '?', $image[0] ); // pull off any Query string (?w=250)
104
						$raw_src = wp_specialchars_decode( $raw_src ); // rawify it
105
						$raw_src = esc_url_raw( $raw_src ); // clean it
106
						$images[] = array(
107
							'type'  => 'image',
108
							'from'  => 'gallery',
109
							'src'   => $raw_src,
110
							'href'  => $permalink,
111
						);
112
					}
113
				}
114 View Code Duplication
			} elseif ( ! empty( $gallery['src'] ) ) {
115
				foreach ( $gallery['src'] as $src ) {
116
					list( $raw_src ) = explode( '?', $src ); // pull off any Query string (?w=250)
117
					$raw_src = wp_specialchars_decode( $raw_src ); // rawify it
118
					$raw_src = esc_url_raw( $raw_src ); // clean it
119
					$images[] = array(
120
						'type'  => 'image',
121
						'from'  => 'gallery',
122
						'src'   => $raw_src,
123
						'href'  => $permalink,
124
					);
125
				}
126
			}
127
		}
128
129
		return $images;
130
	}
131
132
	/**
133
	 * Get attachment images for a specified post and return them. Also make sure
134
	 * their dimensions are at or above a required minimum.
135
	 */
136
	static function from_attachment( $post_id, $width = 200, $height = 200 ) {
137
		$images = array();
138
139
		$post = get_post( $post_id );
140
		if ( !empty( $post->post_password ) )
141
			return $images;
142
143
		$post_images = get_posts( array(
144
			'post_parent' => $post_id,   // Must be children of post
145
			'numberposts' => 5,          // No more than 5
146
			'post_type' => 'attachment', // Must be attachments
147
			'post_mime_type' => 'image', // Must be images
148
		) );
149
150
		if ( !$post_images )
151
			return false;
152
153
		$permalink = get_permalink( $post_id );
154
155
		foreach ( $post_images as $post_image ) {
156
			$meta = wp_get_attachment_metadata( $post_image->ID );
157
			// Must be larger than 200x200
158
			if ( !isset( $meta['width'] ) || $meta['width'] < $width )
159
				continue;
160
			if ( !isset( $meta['height'] ) || $meta['height'] < $height )
161
				continue;
162
163
			$url = wp_get_attachment_url( $post_image->ID );
164
165
			$images[] = array(
166
				'type'       => 'image',
167
				'from'       => 'attachment',
168
				'src'        => $url,
169
				'src_width'  => $meta['width'],
170
				'src_height' => $meta['height'],
171
				'href'       => $permalink,
172
			);
173
		}
174
175
		/*
176
		* We only want to pass back attached images that were actually inserted.
177
		* We can load up all the images found in the HTML source and then
178
		* compare URLs to see if an image is attached AND inserted.
179
		*/
180
		$html_images = self::from_html( $post_id );
181
		$inserted_images = array();
182
183
		foreach( $html_images as $html_image ) {
184
			$src = parse_url( $html_image['src'] );
185
			// strip off any query strings from src
186
			if( ! empty( $src['scheme'] ) && ! empty( $src['host'] ) ) {
187
				$inserted_images[] = $src['scheme'] . '://' . $src['host'] . $src['path'];
188
			} elseif( ! empty( $src['host'] ) ) {
189
				$inserted_images[] = set_url_scheme( 'http://' . $src['host'] . $src['path'] );
190
			} else {
191
				$inserted_images[] = site_url( '/' ) . $src['path'];
192
			}
193
		}
194
		foreach( $images as $i => $image ) {
195
			if ( !in_array( $image['src'], $inserted_images ) )
196
				unset( $images[$i] );
197
		}
198
199
		return $images;
200
	}
201
202
	/**
203
	 * Check if a Featured Image is set for this post, and return it in a similar
204
	 * format to the other images?_from_*() methods.
205
	 * @param  int $post_id The post ID to check
206
	 * @return Array containing details of the Featured Image, or empty array if none.
207
	 */
208
	static function from_thumbnail( $post_id, $width = 200, $height = 200 ) {
209
		$images = array();
210
211
		$post = get_post( $post_id );
212
		if ( !empty( $post->post_password ) )
213
			return $images;
214
215
		if ( !function_exists( 'get_post_thumbnail_id' ) )
216
			return $images;
217
218
		$thumb = get_post_thumbnail_id( $post_id );
219
220
		if ( $thumb ) {
221
			$meta = wp_get_attachment_metadata( $thumb );
222
223
			// Must be larger than requested minimums
224
			if ( !isset( $meta['width'] ) || $meta['width'] < $width )
225
				return $images;
226
			if ( !isset( $meta['height'] ) || $meta['height'] < $height )
227
				return $images;
228
229
			$too_big = ( ( ! empty( $meta['width'] ) && $meta['width'] > 1200 ) || ( ! empty( $meta['height'] ) && $meta['height'] > 1200 ) );
230
231
			if (
232
				$too_big &&
233
				(
234
					( method_exists( 'Jetpack', 'is_module_active' ) && Jetpack::is_module_active( 'photon' ) ) ||
235
					( defined( 'WPCOM' ) && IS_WPCOM )
236
				)
237
			) {
238
				$img_src = wp_get_attachment_image_src( $thumb, array( 1200, 1200 ) );
239
			} else {
240
				$img_src = wp_get_attachment_image_src( $thumb, 'full' );
241
			}
242
243
			$url = $img_src[0];
244
245
			$images = array( array( // Other methods below all return an array of arrays
246
				'type'       => 'image',
247
				'from'       => 'thumbnail',
248
				'src'        => $url,
249
				'src_width'  => $img_src[1],
250
				'src_height' => $img_src[2],
251
				'href'       => get_permalink( $thumb ),
252
			) );
253
		}
254
255
		if ( empty( $images ) && ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
256
			$meta_thumbnail = get_post_meta( $post_id, '_jetpack_post_thumbnail', true );
257
			if ( ! empty( $meta_thumbnail ) ) {
258
				if ( ! isset( $meta_thumbnail['width'] ) || $meta_thumbnail['width'] < $width ) {
259
					return $images;
260
				}
261
262
				if ( ! isset( $meta_thumbnail['height'] ) || $meta_thumbnail['height'] < $height ) {
263
					return $images;
264
				}
265
266
				$images = array( array( // Other methods below all return an array of arrays
267
					'type'       => 'image',
268
					'from'       => 'thumbnail',
269
					'src'        => $meta_thumbnail['URL'],
270
					'src_width'  => $meta_thumbnail['width'],
271
					'src_height' => $meta_thumbnail['height'],
272
					'href'       => $meta_thumbnail['URL'],
273
				) );
274
			}
275
		}
276
277
		return $images;
278
	}
279
280
	/**
281
	 * Very raw -- just parse the HTML and pull out any/all img tags and return their src
282
	 * @param  mixed $html_or_id The HTML string to parse for images, or a post id
283
	 * @return Array containing images
284
	 */
285
	static function from_html( $html_or_id ) {
286
		$images = array();
287
288
		if ( is_numeric( $html_or_id ) ) {
289
			$post = get_post( $html_or_id );
290
			if ( empty( $post ) || !empty( $post->post_password ) )
291
				return $images;
292
293
			$html = $post->post_content; // DO NOT apply the_content filters here, it will cause loops
294
		} else {
295
			$html = $html_or_id;
296
		}
297
298
		if ( !$html )
299
			return $images;
300
301
		preg_match_all( '!<img.*src=[\'"]([^"]+)[\'"].*/?>!iUs', $html, $matches );
302
		if ( !empty( $matches[1] ) ) {
303
			foreach ( $matches[1] as $match ) {
304
				if ( stristr( $match, '/smilies/' ) )
305
					continue;
306
307
				$images[] = array(
308
					'type'  => 'image',
309
					'from'  => 'html',
310
					'src'   => html_entity_decode( $match ),
311
					'href'  => '', // No link to apply to these. Might potentially parse for that as well, but not for now
312
				);
313
			}
314
		}
315
316
		return $images;
317
	}
318
319
	/**
320
	 * @param    int $post_id The post ID to check
321
	 * @param    int $size
322
	 * @return Array containing details of the image, or empty array if none.
323
	 */
324
	static function from_blavatar( $post_id, $size = 96 ) {
325
326
		$permalink = get_permalink( $post_id );
327
328
		if ( function_exists( 'blavatar_domain' ) && function_exists( 'blavatar_exists' ) && function_exists( 'blavatar_url' ) ) {
329
			$domain = blavatar_domain( $permalink );
330
331
			if ( ! blavatar_exists( $domain ) ) {
332
				return array();
333
			}
334
335
			$url = blavatar_url( $domain, 'img', $size );
336
		} elseif ( function_exists( 'jetpack_has_site_icon' ) && jetpack_has_site_icon() ) {
337
			$url = jetpack_site_icon_url( null, $size, $default = false );
338
		} else {
339
			return array();
340
		}
341
342
		return array( array(
343
			'type'       => 'image',
344
			'from'       => 'blavatar',
345
			'src'        => $url,
346
			'src_width'  => $size,
347
			'src_height' => $size,
348
			'href'       => $permalink,
349
		) );
350
	}
351
352
	/**
353
	 * @param    int $post_id The post ID to check
354
	 * @param    int $size
355
	 * @param string $default The default image to use.
0 ignored issues
show
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...
356
	 * @return Array containing details of the image, or empty array if none.
357
	 */
358
	static function from_gravatar( $post_id, $size = 96, $default = false ) {
359
		$post = get_post( $post_id );
360
		$permalink = get_permalink( $post_id );
361
362
		if ( function_exists( 'wpcom_get_avatar_url' ) ) {
363
			$url = wpcom_get_avatar_url( $post->post_author, $size, $default, true );
364
			if ( $url && is_array( $url ) ) {
365
				$url = $url[0];
366
			}
367
		} else {
368
			$has_filter = has_filter( 'pre_option_show_avatars', '__return_true' );
369
			if ( !$has_filter ) {
370
				add_filter( 'pre_option_show_avatars', '__return_true' );
371
			}
372
			$avatar = get_avatar( $post->post_author, $size, $default );
373
			if ( !$has_filter ) {
374
				remove_filter( 'pre_option_show_avatars', '__return_true' );
375
			}
376
377
			if ( !$avatar ) {
378
				return array();
379
			}
380
381
			if ( !preg_match( '/src=["\']([^"\']+)["\']/', $avatar, $matches ) ) {
382
				return array();
383
			}
384
385
			$url = wp_specialchars_decode( $matches[1], ENT_QUOTES );
386
		}
387
388
		return array( array(
389
			'type'       => 'image',
390
			'from'       => 'gravatar',
391
			'src'        => $url,
392
			'src_width'  => $size,
393
			'src_height' => $size,
394
			'href'       => $permalink,
395
		) );
396
	}
397
398
	/**
399
	 * Run through the different methods that we have available to try to find a single good
400
	 * display image for this post.
401
	 * @param  int $post_id
402
	 * @param array $args Other arguments (currently width and height required for images where possible to determine)
403
	 * @return Array containing details of the best image to be used
404
	 */
405
	static function get_image( $post_id, $args = array() ) {
406
		$image = '';
407
408
		/**
409
		 * Fires before we find a single good image for a specific post.
410
		 *
411
		 * @since 2.2.0
412
		 *
413
		 * @param int $post_id Post ID.
414
		 */
415
		do_action( 'jetpack_postimages_pre_get_image', $post_id );
416
		$media = self::get_images( $post_id, $args );
417
418
419
		if ( is_array( $media ) ) {
420
			foreach ( $media as $item ) {
421
				if ( 'image' == $item['type'] ) {
422
					$image = $item;
423
					break;
424
				}
425
			}
426
		}
427
428
		/**
429
		 * Fires after we find a single good image for a specific post.
430
		 *
431
		 * @since 2.2.0
432
		 *
433
		 * @param int $post_id Post ID.
434
		 */
435
		do_action( 'jetpack_postimages_post_get_image', $post_id );
436
437
		return $image;
438
	}
439
440
	/**
441
	 * Get an array containing a collection of possible images for this post, stopping once we hit a method
442
	 * that returns something useful.
443
	 * @param  int $post_id
444
	 * @param  array  $args Optional args, see defaults list for details
445
	 * @return Array containing images that would be good for representing this post
446
	 */
447
	static function get_images( $post_id, $args = array() ) {
448
		// Figure out which image to attach to this post.
449
		$media = false;
450
451
		/**
452
		 * Filters the array of images that would be good for a specific post.
453
		 * This filter is applied before options ($args) filter the original array.
454
		 *
455
		 * @since 2.0.0
456
		 *
457
		 * @param array $media Array of images that would be good for a specific post.
458
		 * @param int $post_id Post ID.
459
		 * @param array $args Array of options to get images.
460
		 */
461
		$media = apply_filters( 'jetpack_images_pre_get_images', $media, $post_id, $args );
462
		if ( $media )
463
			return $media;
464
465
		$defaults = array(
466
			'width'               => 200, // Required minimum width (if possible to determine)
467
			'height'              => 200, // Required minimum height (if possible to determine)
468
469
			'fallback_to_avatars' => false, // Optionally include Blavatar and Gravatar (in that order) in the image stack
470
			'avatar_size'         => 96, // Used for both Grav and Blav
471
			'gravatar_default'    => false, // Default image to use if we end up with no Gravatar
472
473
			'from_thumbnail'      => true, // Use these flags to specify which methods to use to find an image
474
			'from_slideshow'      => true,
475
			'from_gallery'        => true,
476
			'from_attachment'     => true,
477
			'from_html'           => true,
478
479
			'html_content'        => '' // HTML string to pass to from_html()
480
		);
481
		$args = wp_parse_args( $args, $defaults );
482
483
		$media = false;
484
		if ( $args['from_thumbnail'] )
485
			$media = self::from_thumbnail( $post_id, $args['width'], $args['height'] );
486 View Code Duplication
		if ( !$media && $args['from_slideshow'] )
487
			$media = self::from_slideshow( $post_id, $args['width'], $args['height'] );
488
		if ( !$media && $args['from_gallery'] )
489
			$media = self::from_gallery( $post_id );
490 View Code Duplication
		if ( !$media && $args['from_attachment'] )
491
			$media = self::from_attachment( $post_id, $args['width'], $args['height'] );
492
		if ( !$media && $args['from_html'] ) {
493
			if ( empty( $args['html_content'] ) )
494
				$media = self::from_html( $post_id ); // Use the post_id, which will load the content
495
			else
496
				$media = self::from_html( $args['html_content'] ); // If html_content is provided, use that
497
		}
498
499
		if ( !$media && $args['fallback_to_avatars'] ) {
500
			$media = self::from_blavatar( $post_id, $args['avatar_size'] );
501
			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...
502
				$media = self::from_gravatar( $post_id, $args['avatar_size'], $args['gravatar_default'] );
503
		}
504
505
		/**
506
		 * Filters the array of images that would be good for a specific post.
507
		 * This filter is applied after options ($args) filter the original array.
508
		 *
509
		 * @since 2.0.0
510
		 *
511
		 * @param array $media Array of images that would be good for a specific post.
512
		 * @param int $post_id Post ID.
513
		 * @param array $args Array of options to get images.
514
		 */
515
		return apply_filters( 'jetpack_images_get_images', $media, $post_id, $args );
516
	}
517
518
	/**
519
	 * Takes an image URL and pixel dimensions then returns a URL for the
520
	 * resized and croped image.
521
	 *
522
	 * @param  string $src
523
	 * @param  int    $dimension
0 ignored issues
show
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...
524
	 * @return string            Transformed image URL
525
	 */
526
	static function fit_image_url( $src, $width, $height ) {
527
		$width = (int) $width;
528
		$height = (int) $height;
529
530
		// Umm...
531
		if ( $width < 1 || $height < 1 ) {
532
			return $src;
533
		}
534
535
		// See if we should bypass WordPress.com SaaS resizing
536
		if ( has_filter( 'jetpack_images_fit_image_url_override' ) ) {
537
			/**
538
			 * Filters the image URL used after dimensions are set by Photon.
539
			 *
540
			 * @since 3.3.0
541
			 *
542
			 * @param string $src Image URL.
543
			 * @param int $width Image width.
544
			 * @param int $width Image height.
545
			 */
546
			return apply_filters( 'jetpack_images_fit_image_url_override', $src, $width, $height );
547
		}
548
549
		// If WPCOM hosted image use native transformations
550
		$img_host = parse_url( $src, PHP_URL_HOST );
551
		if ( '.files.wordpress.com' == substr( $img_host, -20 ) ) {
552
			return add_query_arg( array( 'w' => $width, 'h' => $height, 'crop' => 1 ), $src );
553
		}
554
555
		// Use Photon magic
556
		if( function_exists( 'jetpack_photon_url' ) ) {
557
			return jetpack_photon_url( $src, array( 'resize' => "$width,$height" ) );
558
		}
559
560
		// Arg... no way to resize image using WordPress.com infrastructure!
561
		return $src;
562
	}
563
}
564