PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.0.3
Jetpack – WP Security, Backup, Speed, & Growth v12.0.3
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / _inc / lib / class.media-summary.php
jetpack / _inc / lib Last commit date
admin-pages 3 years ago core-api 3 years ago debugger 3 years ago markdown 4 years ago class-jetpack-ai-helper.php 3 years ago class-jetpack-currencies.php 5 years ago class-jetpack-google-drive-helper.php 3 years ago class-jetpack-instagram-gallery-helper.php 3 years ago class-jetpack-mapbox-helper.php 3 years ago class-jetpack-podcast-feed-locator.php 5 years ago class-jetpack-podcast-helper.php 3 years ago class-jetpack-recommendations.php 4 years ago class-jetpack-tweetstorm-helper.php 3 years ago class-jetpack-wizard.php 5 years ago class.color.php 4 years ago class.core-rest-api-endpoints.php 3 years ago class.jetpack-automatic-install-skin.php 4 years ago class.jetpack-iframe-embed.php 4 years ago class.jetpack-keyring-service-helper.php 4 years ago class.jetpack-password-checker.php 3 years ago class.jetpack-photon-image-sizes.php 3 years ago class.jetpack-photon-image.php 4 years ago class.jetpack-search-performance-logger.php 4 years ago class.media-extractor.php 3 years ago class.media-summary.php 3 years ago class.media.php 3 years ago components.php 3 years ago debugger.php 4 years ago functions.wp-notify.php 4 years ago icalendar-reader.php 3 years ago markdown.php 3 years ago plans.php 4 years ago plugins.php 4 years ago tonesque.php 3 years ago widgets.php 4 years ago
class.media-summary.php
460 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Provides media summary of a post.
4 *
5 * @package automattic/jetpack
6 */
7
8 /**
9 * Class Jetpack_Media_Summary
10 *
11 * Priority: embed [video] > gallery > image > text
12 */
13 class Jetpack_Media_Summary {
14
15 /**
16 * Media cache.
17 *
18 * @var array
19 */
20 private static $cache = array();
21
22 /**
23 * Get media summary for a post.
24 *
25 * @param int $post_id Post ID.
26 * @param int $blog_id Blog ID, if applicable.
27 * @param array $args {
28 * Optional. An array of arguments.
29 * @type int $max_words Maximum number of words.
30 * @type int $max_chars Maximum number of characters.
31 * }
32 *
33 * @return array|mixed|void
34 */
35 public static function get( $post_id, $blog_id = 0, $args = array() ) {
36 // @todo: Use type hinting in the line above when at PHP 7.0+.
37 $post_id = (int) $post_id;
38 $blog_id = (int) $blog_id;
39
40 $defaults = array(
41 'max_words' => 16,
42 'max_chars' => 256,
43 );
44 $args = wp_parse_args( $args, $defaults );
45
46 $switched = false;
47 if ( ! empty( $blog_id ) && get_current_blog_id() !== $blog_id && function_exists( 'switch_to_blog' ) ) {
48 switch_to_blog( $blog_id );
49 $switched = true;
50 } else {
51 $blog_id = get_current_blog_id();
52 }
53
54 $cache_key = "{$blog_id}_{$post_id}_{$args['max_words']}_{$args['max_chars']}";
55 if ( isset( self::$cache[ $cache_key ] ) ) {
56 if ( $switched ) {
57 restore_current_blog();
58 }
59 return self::$cache[ $cache_key ];
60 }
61
62 if ( ! class_exists( 'Jetpack_Media_Meta_Extractor' ) ) {
63 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.media-extractor.php';
64 }
65
66 $post = get_post( $post_id );
67 $permalink = get_permalink( $post_id );
68
69 $return = array(
70 'type' => 'standard',
71 'permalink' => $permalink,
72 'image' => '',
73 'excerpt' => '',
74 'word_count' => 0,
75 'secure' => array(
76 'image' => '',
77 ),
78 'count' => array(
79 'image' => 0,
80 'video' => 0,
81 'word' => 0,
82 'link' => 0,
83 ),
84 );
85
86 if ( $post instanceof WP_Post && empty( $post->post_password ) ) {
87 $return['excerpt'] = self::get_excerpt( $post->post_content, $post->post_excerpt, $args['max_words'], $args['max_chars'], $post );
88 $return['count']['word'] = self::get_word_count( $post->post_content );
89 $return['count']['word_remaining'] = self::get_word_remaining_count( $post->post_content, $return['excerpt'] );
90 $return['count']['link'] = self::get_link_count( $post->post_content );
91 }
92
93 $extract = Jetpack_Media_Meta_Extractor::extract( $blog_id, $post_id, Jetpack_Media_Meta_Extractor::ALL );
94
95 if ( empty( $extract['has'] ) ) {
96 if ( $switched ) {
97 restore_current_blog();
98 }
99 self::$cache[ $cache_key ] = $return;
100 return $return;
101 }
102
103 // Prioritize [some] video embeds.
104 if ( ! empty( $extract['has']['shortcode'] ) ) {
105 foreach ( $extract['shortcode'] as $type => $data ) {
106 switch ( $type ) {
107 case 'videopress':
108 case 'wpvideo':
109 if ( 0 === $return['count']['video'] ) {
110 // If there is no id on the video, then let's just skip this.
111 if ( ! isset( $data['id'][0] ) ) {
112 break;
113 }
114
115 $guid = $data['id'][0];
116 $video_info = videopress_get_video_details( $guid );
117
118 // Only add the video tags if the guid returns a valid videopress object.
119 if ( $video_info instanceof stdClass ) {
120 // Continue early if we can't find a Video slug.
121 if ( empty( $video_info->files->std->mp4 ) ) {
122 break;
123 }
124
125 $url = sprintf(
126 'https://videos.files.wordpress.com/%1$s/%2$s',
127 $guid,
128 $video_info->files->std->mp4
129 );
130
131 $thumbnail = $video_info->poster;
132 if ( ! empty( $thumbnail ) ) {
133 $return['image'] = $thumbnail;
134 $return['secure']['image'] = $thumbnail;
135 }
136
137 $return['type'] = 'video';
138 $return['video'] = esc_url_raw( $url );
139 $return['video_type'] = 'video/mp4';
140 $return['secure']['video'] = $return['video'];
141 }
142 }
143 ++$return['count']['video'];
144 break;
145 case 'youtube':
146 if ( 0 === $return['count']['video'] ) {
147 $return['type'] = 'video';
148 $return['video'] = esc_url_raw( 'http://www.youtube.com/watch?feature=player_embedded&v=' . $extract['shortcode']['youtube']['id'][0] );
149 $return['image'] = self::get_video_poster( 'youtube', $extract['shortcode']['youtube']['id'][0] );
150 $return['secure']['video'] = self::https( $return['video'] );
151 $return['secure']['image'] = self::https( $return['image'] );
152 }
153 ++$return['count']['video'];
154 break;
155 case 'vimeo':
156 if ( 0 === $return['count']['video'] ) {
157 $return['type'] = 'video';
158 $return['video'] = esc_url_raw( 'http://vimeo.com/' . $extract['shortcode']['vimeo']['id'][0] );
159 $return['secure']['video'] = self::https( $return['video'] );
160
161 $poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true );
162 if ( ! empty( $poster_image ) ) {
163 $return['image'] = $poster_image;
164 $poster_url_parts = wp_parse_url( $poster_image );
165 $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path'];
166 }
167 }
168 ++$return['count']['video'];
169 break;
170 }
171 }
172 }
173
174 if ( ! empty( $extract['has']['embed'] ) ) {
175 foreach ( $extract['embed']['url'] as $embed ) {
176 if ( preg_match( '/((youtube|vimeo|dailymotion)\.com|youtu.be)/', $embed ) ) {
177 if ( 0 === $return['count']['video'] ) {
178 $return['type'] = 'video';
179 $return['video'] = 'http://' . $embed;
180 $return['secure']['video'] = self::https( $return['video'] );
181 if ( false !== strpos( $embed, 'youtube' ) ) {
182 $return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) );
183 $return['secure']['image'] = self::https( $return['image'] );
184 } elseif ( false !== strpos( $embed, 'youtu.be' ) ) {
185 $youtube_id = jetpack_get_youtube_id( $return['video'] );
186 $return['video'] = 'http://youtube.com/watch?v=' . $youtube_id . '&feature=youtu.be';
187 $return['secure']['video'] = self::https( $return['video'] );
188 $return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) );
189 $return['secure']['image'] = self::https( $return['image'] );
190 } elseif ( false !== strpos( $embed, 'vimeo' ) ) {
191 $poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true );
192 if ( ! empty( $poster_image ) ) {
193 $return['image'] = $poster_image;
194 $poster_url_parts = wp_parse_url( $poster_image );
195 $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path'];
196 }
197 } elseif ( false !== strpos( $embed, 'dailymotion' ) ) {
198 $return['image'] = str_replace( 'dailymotion.com/video/', 'dailymotion.com/thumbnail/video/', $embed );
199 $return['image'] = wp_parse_url( $return['image'], PHP_URL_SCHEME ) === null ? 'http://' . $return['image'] : $return['image'];
200 $return['secure']['image'] = self::https( $return['image'] );
201 }
202 }
203 ++$return['count']['video'];
204 }
205 }
206 }
207
208 // Do we really want to make the video the primary focus of the post?
209 if ( 'video' === $return['type'] ) {
210 $content = wpautop( wp_strip_all_tags( $post->post_content ) );
211 $paragraphs = explode( '</p>', $content );
212 $number_of_paragraphs = 0;
213
214 foreach ( $paragraphs as $i => $paragraph ) {
215 // Don't include blank lines as a paragraph.
216 if ( '' === trim( $paragraph ) ) {
217 unset( $paragraphs[ $i ] );
218 continue;
219 }
220 ++$number_of_paragraphs;
221 }
222
223 $number_of_paragraphs = $number_of_paragraphs - $return['count']['video']; // subtract amount for videos.
224
225 // More than 2 paragraph? The video is not the primary focus so we can do some more analysis.
226 if ( $number_of_paragraphs > 2 ) {
227 $return['type'] = 'standard';
228 }
229 }
230
231 // If we don't have any prioritized embed...
232 if ( 'standard' === $return['type'] ) {
233 if ( ( ! empty( $extract['has']['gallery'] ) || ! empty( $extract['shortcode']['gallery']['count'] ) ) && ! empty( $extract['image'] ) ) {
234 // ... Then we prioritize galleries first (multiple images returned)
235 $return['type'] = 'gallery';
236 $return['images'] = $extract['image'];
237 foreach ( $return['images'] as $image ) {
238 $return['secure']['images'][] = array( 'url' => self::ssl_img( $image['url'] ) );
239 ++$return['count']['image'];
240 }
241 } elseif ( ! empty( $extract['has']['image'] ) ) {
242 // ... Or we try and select a single image that would make sense.
243 $content = wpautop( wp_strip_all_tags( $post->post_content ) );
244 $paragraphs = explode( '</p>', $content );
245 $number_of_paragraphs = 0;
246
247 foreach ( $paragraphs as $i => $paragraph ) {
248 // Don't include 'actual' captions as a paragraph.
249 if ( false !== strpos( $paragraph, '[caption' ) ) {
250 unset( $paragraphs[ $i ] );
251 continue;
252 }
253 // Don't include blank lines as a paragraph.
254 if ( '' === trim( $paragraph ) ) {
255 unset( $paragraphs[ $i ] );
256 continue;
257 }
258 ++$number_of_paragraphs;
259 }
260
261 $return['image'] = $extract['image'][0]['url'];
262 $return['secure']['image'] = self::ssl_img( $return['image'] );
263 ++$return['count']['image'];
264
265 if ( $number_of_paragraphs <= 2 && 1 === count( $extract['image'] ) ) {
266 // If we have lots of text or images, let's not treat it as an image post, but return its first image.
267 $return['type'] = 'image';
268 }
269 }
270 }
271
272 if ( $switched ) {
273 restore_current_blog();
274 }
275
276 /**
277 * Allow a theme or plugin to inspect and ultimately change the media summary.
278 *
279 * @since 4.4.0
280 *
281 * @param array $data The calculated media summary data.
282 * @param int $post_id The id of the post this data applies to.
283 */
284 $return = apply_filters( 'jetpack_media_summary_output', $return, $post_id );
285
286 self::$cache[ $cache_key ] = $return;
287
288 return $return;
289 }
290
291 /**
292 * Converts http to https://
293 *
294 * @param string $str URL.
295 *
296 * @return string URL.
297 */
298 public static function https( $str ) {
299 return str_replace( 'http://', 'https://', $str );
300 }
301
302 /**
303 * Returns a Photonized version of the URL.
304 *
305 * @param string $url URL.
306 *
307 * @return string URL.
308 */
309 public static function ssl_img( $url ) {
310 if ( false !== strpos( $url, 'files.wordpress.com' ) ) {
311 return self::https( $url );
312 } else {
313 return self::https( jetpack_photon_url( $url ) );
314 }
315 }
316
317 /**
318 * Get the video poster.
319 *
320 * @param string $type Video service.
321 * @param string $id Video ID for the service.
322 *
323 * @return string URL of image thumbnail for the video.
324 */
325 public static function get_video_poster( $type, $id ) {
326 if ( 'videopress' === $type ) {
327 if ( function_exists( 'video_get_highest_resolution_image_url' ) ) {
328 return video_get_highest_resolution_image_url( $id );
329 } elseif ( class_exists( 'VideoPress_Video' ) ) {
330 $video = new VideoPress_Video( $id );
331 return $video->poster_frame_uri;
332 }
333 } elseif ( 'youtube' === $type ) {
334 return 'http://img.youtube.com/vi/' . $id . '/0.jpg';
335 }
336 }
337
338 /**
339 * Clean text of shortcodes and tags.
340 *
341 * @param string $text Dirty text.
342 *
343 * @return string Clean text.
344 */
345 public static function clean_text( $text ) {
346 return trim(
347 preg_replace(
348 '/[\s]+/',
349 ' ',
350 preg_replace(
351 '@https?://[\S]+@',
352 '',
353 strip_shortcodes(
354 wp_strip_all_tags(
355 $text
356 )
357 )
358 )
359 )
360 );
361 }
362
363 /**
364 * Retrieve an excerpt for the post summary.
365 *
366 * This function works around a suspected problem with Core. If resolved, this function should be simplified.
367 *
368 * @link https://github.com/Automattic/jetpack/pull/8510
369 * @link https://core.trac.wordpress.org/ticket/42814
370 *
371 * @param string $post_content The post's content.
372 * @param string $post_excerpt The post's excerpt. Empty if none was explicitly set.
373 * @param int $max_words Maximum number of words for the excerpt. Used on wp.com. Default 16.
374 * @param int $max_chars Maximum characters in the excerpt. Used on wp.com. Default 256.
375 * @param WP_Post $requested_post The post object.
376 * @return string Post excerpt.
377 **/
378 public static function get_excerpt( $post_content, $post_excerpt, $max_words = 16, $max_chars = 256, $requested_post = null ) {
379 global $post;
380 $original_post = $post; // Saving the global for later use.
381 if ( empty( $post_excerpt ) && function_exists( 'wpcom_enhanced_excerpt_extract_excerpt' ) ) {
382 return self::clean_text(
383 wpcom_enhanced_excerpt_extract_excerpt(
384 array(
385 'text' => $post_content,
386 'excerpt_only' => true,
387 'show_read_more' => false,
388 'max_words' => $max_words,
389 'max_chars' => $max_chars,
390 'read_more_threshold' => 25,
391 )
392 )
393 );
394 } elseif ( $requested_post instanceof WP_Post ) {
395 // @todo Refactor to not need to override the global.
396 // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited
397 $post = $requested_post; // setup_postdata does not set the global.
398 setup_postdata( $post );
399 /** This filter is documented in core/src/wp-includes/post-template.php */
400 $post_excerpt = apply_filters( 'get_the_excerpt', $post_excerpt, $post );
401 // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited
402 $post = $original_post; // wp_reset_postdata uses the $post global.
403 wp_reset_postdata();
404 return self::clean_text( $post_excerpt );
405 }
406 return '';
407 }
408
409 /**
410 * Split a string into an array of words.
411 *
412 * @param string $text Post content or excerpt.
413 *
414 * @return array Array of words.
415 */
416 public static function split_content_in_words( $text ) {
417 $words = preg_split( '/[\s!?;,.]+/', $text, -1, PREG_SPLIT_NO_EMPTY );
418
419 // Return an empty array if the split above fails.
420 return $words ? $words : array();
421 }
422
423 /**
424 * Get the word count.
425 *
426 * @param string $post_content Post content.
427 *
428 * @return int Word count.
429 */
430 public static function get_word_count( $post_content ) {
431 return (int) count( self::split_content_in_words( self::clean_text( $post_content ) ) );
432 }
433
434 /**
435 * Get remainder word count (after the excerpt).
436 *
437 * @param string $post_content Post content.
438 * @param string $excerpt_content Excerpt content.
439 *
440 * @return int Number of words after the excerpt.
441 */
442 public static function get_word_remaining_count( $post_content, $excerpt_content ) {
443 $content_word_count = count( self::split_content_in_words( self::clean_text( $post_content ) ) );
444 $excerpt_word_count = count( self::split_content_in_words( self::clean_text( $excerpt_content ) ) );
445
446 return (int) $content_word_count - $excerpt_word_count;
447 }
448
449 /**
450 * Counts the number of links in a post.
451 *
452 * @param string $post_content Post content.
453 *
454 * @return false|int Number of links.
455 */
456 public static function get_link_count( $post_content ) {
457 return preg_match_all( '/\<a[\> ]/', $post_content, $matches );
458 }
459 }
460