Completed
Push — add/vr-shortcode ( 271979...022ee9 )
by
unknown
26:28 queued 20:24
created

class.jetpack-post-images.php (7 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, '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
		if ( false === has_shortcode( $post->post_content, 'gallery' ) ) {
92
			return false; // no gallery - bail
93
		}
94
95
		$permalink = get_permalink( $post->ID );
96
97
		// CATS: All your base are belong to us
98
		$old_post = $GLOBALS['post'];
99
		$GLOBALS['post'] = $post;
100
		$old_shortcodes = $GLOBALS['shortcode_tags'];
101
		$GLOBALS['shortcode_tags'] = array( 'gallery' => $old_shortcodes['gallery'] );
102
103
		// Find all the galleries
104
		preg_match_all( '/' . get_shortcode_regex() . '/s', $post->post_content, $gallery_matches, PREG_SET_ORDER );
105
106
		foreach ( $gallery_matches as $gallery_match ) {
0 ignored issues
show
The expression $gallery_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...
107
			$gallery = do_shortcode_tag( $gallery_match );
108
109
			// Um... no images in the gallery - bail
110
			if ( false === $pos = stripos( $gallery, '<img' ) )
111
				continue;
112
113
			preg_match_all( '/<img\s+[^>]*src=([\'"])([^\'"]*)\\1/', $gallery, $image_match, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE );
114
115
			$a_pos = 0;
116
			foreach ( $image_match[2] as $src ) {
117
				list( $raw_src ) = explode( '?', $src[0] ); // pull off any Query string (?w=250)
118
				$raw_src = wp_specialchars_decode( $raw_src ); // rawify it
119
				$raw_src = esc_url_raw( $raw_src ); // clean it
120
121
				$a_pos = strrpos( substr( $gallery, 0, $src[1] ), '<a', $a_pos ); // is there surrounding <a>?
122
123
				if ( false !== $a_pos && preg_match( '/<a\s+[^>]*href=([\'"])([^\'"]*)\\1/', $gallery, $href_match, 0, $a_pos ) ) {
124
					$href = wp_specialchars_decode( $href_match[2] );
125
					$href = esc_url_raw( $href );
0 ignored issues
show
$href is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
126
				} else {
127
					// CATS: You have no chance to survive make your time
128
					$href = $raw_src;
0 ignored issues
show
$href is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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