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