Completed
Push — add/settings-shortlinks ( 9f0532...4d1256 )
by
unknown
11:14 queued 04:24
created

Jetpack_Media_Summary::split_content_in_words()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Class Jetpack_Media_Summary
4
 *
5
 * embed [video] > gallery > image > text
6
 */
7
class Jetpack_Media_Summary {
8
9
	private static $cache = array();
10
11
	static function get( $post_id, $blog_id = 0, $args = array() ) {
12
13
		$defaults = array(
14
			'max_words' => 16,
15
			'max_chars' => 256,
16
		);
17
		$args = wp_parse_args( $args, $defaults );
18
19
		$switched = false;
20
		if ( !empty( $blog_id ) && $blog_id != get_current_blog_id() && function_exists( 'switch_to_blog' ) ) {
21
			switch_to_blog( $blog_id );
22
			$switched = true;
23
		} else {
24
			$blog_id = get_current_blog_id();
25
		}
26
27
		$cache_key = "{$blog_id}_{$post_id}_{$args['max_words']}_{$args['max_chars']}";
28
		if ( isset( self::$cache[ $cache_key ] ) ) {
29
			return self::$cache[ $cache_key ];
30
		}
31
32
		if ( ! class_exists( 'Jetpack_Media_Meta_Extractor' ) ) {
33
			if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
34
				jetpack_require_lib( 'class.wpcom-media-meta-extractor' );
35
			} else {
36
				jetpack_require_lib( 'class.media-extractor' );
37
			}
38
		}
39
40
		$post      = get_post( $post_id );
41
		$permalink = get_permalink( $post_id );
42
43
		$return = array(
44
			'type'       => 'standard',
45
			'permalink'  => $permalink,
46
			'image'      => '',
47
			'excerpt'    => '',
48
			'word_count' => 0,
49
			'secure'     => array(
50
				'image'  => '',
51
			),
52
			'count'       => array(
53
				'image' => 0,
54
				'video' => 0,
55
				'word'  => 0,
56
				'link'  => 0,
57
			),
58
		);
59
60
		if ( empty( $post->post_password ) ) {
61
			$return['excerpt']       = self::get_excerpt( $post->post_content, $post->post_excerpt, $args['max_words'], $args['max_chars'] , $post);
62
			$return['count']['word'] = self::get_word_count( $post->post_content );
63
			$return['count']['word_remaining'] = self::get_word_remaining_count( $post->post_content, $return['excerpt'] );
64
			$return['count']['link'] = self::get_link_count( $post->post_content );
65
		}
66
67
		$extract = Jetpack_Media_Meta_Extractor::extract( $blog_id, $post_id, Jetpack_Media_Meta_Extractor::ALL );
68
69
		if ( empty( $extract['has'] ) )
70
			return $return;
71
72
		// Prioritize [some] video embeds
73
		if ( !empty( $extract['has']['shortcode'] ) ) {
74
			foreach ( $extract['shortcode'] as $type => $data ) {
75
				switch ( $type ) {
76
					case 'videopress':
77
					case 'wpvideo':
78
						if ( 0 == $return['count']['video'] ) {
79
							// If there is no id on the video, then let's just skip this
80
							if ( ! isset ( $data['id'][0] ) ) {
81
								break;
82
							}
83
84
							$guid = $data['id'][0];
85
							$video_info = videopress_get_video_details( $guid );
86
87
							// Only add the video tags if the guid returns a valid videopress object.
88
							if ( $video_info instanceof stdClass ) {
89
								// Continue early if we can't find a Video slug.
90
								if ( empty( $video_info->files->std->mp4 ) ) {
91
									break;
92
								}
93
94
								$url = sprintf(
95
									'https://videos.files.wordpress.com/%1$s/%2$s',
96
									$guid,
97
									$video_info->files->std->mp4
98
								);
99
100
								$thumbnail = $video_info->poster;
101
								if ( ! empty( $thumbnail ) ) {
102
									$return['image'] = $thumbnail;
103
									$return['secure']['image'] = $thumbnail;
104
								}
105
106
								$return['type'] = 'video';
107
								$return['video'] = esc_url_raw( $url );
108
								$return['video_type'] = 'video/mp4';
109
								$return['secure']['video'] = $return['video'];
110
							}
111
112
						}
113
						$return['count']['video']++;
114
						break;
115
					case 'youtube':
116
						if ( 0 == $return['count']['video'] ) {
117
							$return['type'] = 'video';
118
							$return['video'] = esc_url_raw( 'http://www.youtube.com/watch?feature=player_embedded&v=' . $extract['shortcode']['youtube']['id'][0] );
119
							$return['image'] = self::get_video_poster( 'youtube', $extract['shortcode']['youtube']['id'][0] );
120
							$return['secure']['video'] = self::https( $return['video'] );
121
							$return['secure']['image'] = self::https( $return['image'] );
122
						}
123
						$return['count']['video']++;
124
						break;
125
					case 'vimeo':
126
						if ( 0 == $return['count']['video'] ) {
127
							$return['type'] = 'video';
128
							$return['video'] = esc_url_raw( 'http://vimeo.com/' . $extract['shortcode']['vimeo']['id'][0] );
129
							$return['secure']['video'] = self::https( $return['video'] );
130
131
							$poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true );
132 View Code Duplication
							if ( !empty( $poster_image ) ) {
133
								$return['image'] = $poster_image;
134
								$poster_url_parts = parse_url( $poster_image );
135
								$return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path'];
136
							}
137
						}
138
						$return['count']['video']++;
139
						break;
140
				}
141
			}
142
143
		}
144
145
		if ( !empty( $extract['has']['embed'] ) ) {
146
			foreach( $extract['embed']['url'] as $embed ) {
147
				if ( preg_match( '/((youtube|vimeo|dailymotion)\.com|youtu.be)/', $embed ) ) {
148
					if ( 0 == $return['count']['video'] ) {
149
						$return['type']   = 'video';
150
						$return['video']  = 'http://' .  $embed;
151
						$return['secure']['video'] = self::https( $return['video'] );
152
						if ( false !== strpos( $embed, 'youtube' ) ) {
153
							$return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) );
154
							$return['secure']['image'] = self::https( $return['image'] );
155
						} else if ( false !== strpos( $embed, 'youtu.be' ) ) {
156
							$youtube_id = jetpack_get_youtube_id( $return['video'] );
157
							$return['video'] = 'http://youtube.com/watch?v=' . $youtube_id . '&feature=youtu.be';
158
							$return['secure']['video'] = self::https( $return['video'] );
159
							$return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) );
160
							$return['secure']['image'] = self::https( $return['image'] );
161
						} else if ( false !== strpos( $embed, 'vimeo' ) ) {
162
							$poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true );
163 View Code Duplication
							if ( !empty( $poster_image ) ) {
164
								$return['image'] = $poster_image;
165
								$poster_url_parts = parse_url( $poster_image );
166
								$return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path'];
167
							}
168
						} else if ( false !== strpos( $embed, 'dailymotion' ) ) {
169
							$return['image'] = str_replace( 'dailymotion.com/video/','dailymotion.com/thumbnail/video/', $embed );
170
							$return['image'] = parse_url( $return['image'], PHP_URL_SCHEME ) === null ? 'http://' . $return['image'] : $return['image'];
171
							$return['secure']['image'] = self::https( $return['image'] );
172
						}
173
174
					}
175
					$return['count']['video']++;
176
				}
177
			}
178
		}
179
180
		// Do we really want to make the video the primary focus of the post?
181
		if ( 'video' == $return['type'] ) {
182
			$content = wpautop( strip_tags( $post->post_content ) );
183
			$paragraphs = explode( '</p>', $content );
184
			$number_of_paragraphs = 0;
185
186
			foreach ( $paragraphs as $i => $paragraph ) {
187
				// Don't include blank lines as a paragraph
188
				if ( '' == trim( $paragraph ) ) {
189
					unset( $paragraphs[$i] );
190
					continue;
191
				}
192
				$number_of_paragraphs++;
193
			}
194
195
			$number_of_paragraphs = $number_of_paragraphs - $return['count']['video']; // subtract amount for videos..
196
197
			// More than 2 paragraph? The video is not the primary focus so we can do some more analysis
198
			if ( $number_of_paragraphs > 2 )
199
				$return['type'] = 'standard';
200
		}
201
202
		// If we don't have any prioritized embed...
203
		if ( 'standard' == $return['type'] ) {
204
			if ( ( ! empty( $extract['has']['gallery'] ) || ! empty( $extract['shortcode']['gallery']['count'] ) ) && ! empty( $extract['image'] ) ) {
205
				//... Then we prioritize galleries first (multiple images returned)
206
				$return['type']   = 'gallery';
207
				$return['images'] = $extract['image'];
208
				foreach ( $return['images'] as $image ) {
209
					$return['secure']['images'][] = array( 'url' => self::ssl_img( $image['url'] ) );
210
					$return['count']['image']++;
211
				}
212
			} else if ( ! empty( $extract['has']['image'] ) ) {
213
				// ... Or we try and select a single image that would make sense
214
				$content = wpautop( strip_tags( $post->post_content ) );
215
				$paragraphs = explode( '</p>', $content );
216
				$number_of_paragraphs = 0;
217
218
				foreach ( $paragraphs as $i => $paragraph ) {
219
					// Don't include 'actual' captions as a paragraph
220
					if ( false !== strpos( $paragraph, '[caption' ) ) {
221
						unset( $paragraphs[$i] );
222
						continue;
223
					}
224
					// Don't include blank lines as a paragraph
225
					if ( '' == trim( $paragraph ) ) {
226
						unset( $paragraphs[$i] );
227
						continue;
228
					}
229
					$number_of_paragraphs++;
230
				}
231
232
				$return['image'] = $extract['image'][0]['url'];
233
				$return['secure']['image'] = self::ssl_img( $return['image'] );
234
				$return['count']['image']++;
235
236
				if ( $number_of_paragraphs <= 2 && 1 == count( $extract['image'] ) ) {
237
					// If we have lots of text or images, let's not treat it as an image post, but return its first image
238
					$return['type']  = 'image';
239
				}
240
			}
241
		}
242
243
		if ( $switched ) {
244
			restore_current_blog();
245
		}
246
247
		/**
248
		 * Allow a theme or plugin to inspect and ultimately change the media summary.
249
		 *
250
		 * @since 4.4.0
251
		 *
252
		 * @param array $data The calculated media summary data.
253
		 * @param int $post_id The id of the post this data applies to.
254
		 */
255
		$return = apply_filters( 'jetpack_media_summary_output', $return, $post_id );
256
257
		self::$cache[ $cache_key ] = $return;
258
259
		return $return;
260
	}
261
262
	static function https( $str ) {
263
		return str_replace( 'http://', 'https://', $str );
264
	}
265
266
	static function ssl_img( $url ) {
267
		if ( false !== strpos( $url, 'files.wordpress.com' ) ) {
268
			return self::https( $url );
269
		} else {
270
			return self::https( jetpack_photon_url( $url ) );
271
		}
272
	}
273
274
	static function get_video_poster( $type, $id ) {
275
		if ( 'videopress' == $type ) {
276
			if ( function_exists( 'video_get_highest_resolution_image_url' ) ) {
277
				return video_get_highest_resolution_image_url( $id );
278
			} else if ( class_exists( 'VideoPress_Video' ) ) {
279
				$video = new VideoPress_Video( $id );
280
				return $video->poster_frame_uri;
281
			}
282
		} else if ( 'youtube' == $type ) {
283
			return  'http://img.youtube.com/vi/'.$id.'/0.jpg';
284
		}
285
	}
286
287
	static function clean_text( $text ) {
288
		return trim(
289
			preg_replace(
290
				'/[\s]+/',
291
				' ',
292
				preg_replace(
293
					'@https?://[\S]+@',
294
					'',
295
					strip_shortcodes(
296
						strip_tags(
297
							$text
298
						)
299
					)
300
				)
301
			)
302
		);
303
	}
304
305
	/**
306
	 * Retrieve an excerpt for the post summary.
307
	 *
308
	 * This function works around a suspected problem with Core. If resolved, this function should be simplified.
309
	 * @link https://github.com/Automattic/jetpack/pull/8510
310
	 * @link https://core.trac.wordpress.org/ticket/42814
311
	 *
312
	 * @param  string  $post_content The post's content.
313
	 * @param  string  $post_excerpt The post's excerpt. Empty if none was explicitly set.
314
	 * @param  int     $max_words Maximum number of words for the excerpt. Used on wp.com. Default 16.
315
	 * @param  int     $max_chars Maximum characters in the excerpt. Used on wp.com. Default 256.
316
	 * @param  WP_Post $requested_post The post object.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $requested_post not be WP_Post|null?

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...
317
	 * @return string Post excerpt.
318
	 **/
319
	static function get_excerpt( $post_content, $post_excerpt, $max_words = 16, $max_chars = 256, $requested_post = null ) {
320
		global $post;
321
		$original_post = $post; // Saving the global for later use.
322
		if ( function_exists( 'wpcom_enhanced_excerpt_extract_excerpt' ) ) {
323
			return self::clean_text( wpcom_enhanced_excerpt_extract_excerpt( array(
324
				'text'                => $post_content,
325
				'excerpt_only'        => true,
326
				'show_read_more'      => false,
327
				'max_words'           => $max_words,
328
				'max_chars'           => $max_chars,
329
				'read_more_threshold' => 25,
330
			) ) );
331
		} elseif ( $requested_post instanceof WP_Post ) {
0 ignored issues
show
Bug introduced by
The class WP_Post does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
332
			$post = $requested_post; // setup_postdata does not set the global.
333
			setup_postdata( $post );
334
			/** This filter is documented in core/src/wp-includes/post-template.php */
335
			$post_excerpt = apply_filters( 'get_the_excerpt', $post_excerpt, $post );
336
			$post         = $original_post; // wp_reset_postdata uses the $post global.
337
			wp_reset_postdata();
338
			return self::clean_text( $post_excerpt );
339
		}
340
		return '';
341
	}
342
343
	/**
344
	 * Split a string into an array of words.
345
	 *
346
	 * @param string $text Post content or excerpt.
347
	 */
348
	static function split_content_in_words( $text ) {
349
		$words = preg_split( '/[\s!?;,.]+/', $text, null, PREG_SPLIT_NO_EMPTY );
350
351
		// Return an empty array if the split above fails. 
352
		return $words ? $words : array();	
353
	}
354
355
	static function get_word_count( $post_content ) {
356
		return (int) count( self::split_content_in_words( self::clean_text( $post_content ) ) );
357
	}
358
359
	static function get_word_remaining_count( $post_content, $excerpt_content ) {
360
		$content_word_count = count( self::split_content_in_words( self::clean_text( $post_content ) ) );
361
		$excerpt_word_count = count( self::split_content_in_words( self::clean_text( $excerpt_content ) ) ); 
362
363
		return (int) $content_word_count - $excerpt_word_count;
364
	}
365
366
	static function get_link_count( $post_content ) {
367
		return preg_match_all( '/\<a[\> ]/', $post_content, $matches );
368
	}
369
}
370