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