Completed
Push — add/sync-rest-comments ( 1da85c )
by
unknown
09:46
created

Jetpack_PostImages::from_gallery()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 28
Code Lines 18

Duplication

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