PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.9.5
Jetpack – WP Security, Backup, Speed, & Growth v12.9.5
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 2 years ago core-api 2 years ago debugger 2 years ago markdown 2 years ago class-jetpack-ai-helper.php 2 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 2 years ago class-jetpack-podcast-helper.php 2 years ago class-jetpack-recommendations.php 2 years ago class-jetpack-tweetstorm-helper.php 2 years ago class-jetpack-wizard.php 5 years ago class.color.php 2 years ago class.core-rest-api-endpoints.php 2 years ago class.jetpack-automatic-install-skin.php 2 years ago class.jetpack-iframe-embed.php 3 years ago class.jetpack-keyring-service-helper.php 3 years ago class.jetpack-password-checker.php 3 years ago class.jetpack-search-performance-logger.php 4 years ago class.media-extractor.php 3 years ago class.media-summary.php 2 years ago class.media.php 2 years ago components.php 3 years ago debugger.php 4 years ago functions.wp-notify.php 3 years ago icalendar-reader.php 2 years ago markdown.php 3 years ago plans.php 4 years ago plugins.php 4 years ago tonesque.php 2 years ago widgets.php 2 years ago
class.media-summary.php
462 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 use Automattic\Jetpack\Image_CDN\Image_CDN_Core;
9
10 /**
11 * Class Jetpack_Media_Summary
12 *
13 * Priority: embed [video] > gallery > image > text
14 */
15 class Jetpack_Media_Summary {
16
17 /**
18 * Media cache.
19 *
20 * @var array
21 */
22 private static $cache = array();
23
24 /**
25 * Get media summary for a post.
26 *
27 * @param int $post_id Post ID.
28 * @param int $blog_id Blog ID, if applicable.
29 * @param array $args {
30 * Optional. An array of arguments.
31 * @type int $max_words Maximum number of words.
32 * @type int $max_chars Maximum number of characters.
33 * }
34 *
35 * @return array|mixed|void
36 */
37 public static function get( $post_id, $blog_id = 0, $args = array() ) {
38 // @todo: Use type hinting in the line above when at PHP 7.0+.
39 $post_id = (int) $post_id;
40 $blog_id = (int) $blog_id;
41
42 $defaults = array(
43 'max_words' => 16,
44 'max_chars' => 256,
45 );
46 $args = wp_parse_args( $args, $defaults );
47
48 $switched = false;
49 if ( ! empty( $blog_id ) && get_current_blog_id() !== $blog_id && function_exists( 'switch_to_blog' ) ) {
50 switch_to_blog( $blog_id );
51 $switched = true;
52 } else {
53 $blog_id = get_current_blog_id();
54 }
55
56 $cache_key = "{$blog_id}_{$post_id}_{$args['max_words']}_{$args['max_chars']}";
57 if ( isset( self::$cache[ $cache_key ] ) ) {
58 if ( $switched ) {
59 restore_current_blog();
60 }
61 return self::$cache[ $cache_key ];
62 }
63
64 if ( ! class_exists( 'Jetpack_Media_Meta_Extractor' ) ) {
65 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.media-extractor.php';
66 }
67
68 $post = get_post( $post_id );
69 $permalink = get_permalink( $post_id );
70
71 $return = array(
72 'type' => 'standard',
73 'permalink' => $permalink,
74 'image' => '',
75 'excerpt' => '',
76 'word_count' => 0,
77 'secure' => array(
78 'image' => '',
79 ),
80 'count' => array(
81 'image' => 0,
82 'video' => 0,
83 'word' => 0,
84 'link' => 0,
85 ),
86 );
87
88 if ( $post instanceof WP_Post && empty( $post->post_password ) ) {
89 $return['excerpt'] = self::get_excerpt( $post->post_content, $post->post_excerpt, $args['max_words'], $args['max_chars'], $post );
90 $return['count']['word'] = self::get_word_count( $post->post_content );
91 $return['count']['word_remaining'] = self::get_word_remaining_count( $post->post_content, $return['excerpt'] );
92 $return['count']['link'] = self::get_link_count( $post->post_content );
93 }
94
95 $extract = Jetpack_Media_Meta_Extractor::extract( $blog_id, $post_id, Jetpack_Media_Meta_Extractor::ALL );
96
97 if ( empty( $extract['has'] ) ) {
98 if ( $switched ) {
99 restore_current_blog();
100 }
101 self::$cache[ $cache_key ] = $return;
102 return $return;
103 }
104
105 // Prioritize [some] video embeds.
106 if ( ! empty( $extract['has']['shortcode'] ) ) {
107 foreach ( $extract['shortcode'] as $type => $data ) {
108 switch ( $type ) {
109 case 'videopress':
110 case 'wpvideo':
111 if ( 0 === $return['count']['video'] ) {
112 // If there is no id on the video, then let's just skip this.
113 if ( ! isset( $data['id'][0] ) ) {
114 break;
115 }
116
117 $guid = $data['id'][0];
118 $video_info = videopress_get_video_details( $guid );
119
120 // Only add the video tags if the guid returns a valid videopress object.
121 if ( $video_info instanceof stdClass ) {
122 // Continue early if we can't find a Video slug.
123 if ( empty( $video_info->files->std->mp4 ) ) {
124 break;
125 }
126
127 $url = sprintf(
128 'https://videos.files.wordpress.com/%1$s/%2$s',
129 $guid,
130 $video_info->files->std->mp4
131 );
132
133 $thumbnail = $video_info->poster;
134 if ( ! empty( $thumbnail ) ) {
135 $return['image'] = $thumbnail;
136 $return['secure']['image'] = $thumbnail;
137 }
138
139 $return['type'] = 'video';
140 $return['video'] = esc_url_raw( $url );
141 $return['video_type'] = 'video/mp4';
142 $return['secure']['video'] = $return['video'];
143 }
144 }
145 ++$return['count']['video'];
146 break;
147 case 'youtube':
148 if ( 0 === $return['count']['video'] ) {
149 $return['type'] = 'video';
150 $return['video'] = esc_url_raw( 'http://www.youtube.com/watch?feature=player_embedded&v=' . $extract['shortcode']['youtube']['id'][0] );
151 $return['image'] = self::get_video_poster( 'youtube', $extract['shortcode']['youtube']['id'][0] );
152 $return['secure']['video'] = self::https( $return['video'] );
153 $return['secure']['image'] = self::https( $return['image'] );
154 }
155 ++$return['count']['video'];
156 break;
157 case 'vimeo':
158 if ( 0 === $return['count']['video'] ) {
159 $return['type'] = 'video';
160 $return['video'] = esc_url_raw( 'http://vimeo.com/' . $extract['shortcode']['vimeo']['id'][0] );
161 $return['secure']['video'] = self::https( $return['video'] );
162
163 $poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true );
164 if ( ! empty( $poster_image ) ) {
165 $return['image'] = $poster_image;
166 $poster_url_parts = wp_parse_url( $poster_image );
167 $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path'];
168 }
169 }
170 ++$return['count']['video'];
171 break;
172 }
173 }
174 }
175
176 if ( ! empty( $extract['has']['embed'] ) ) {
177 foreach ( $extract['embed']['url'] as $embed ) {
178 if ( preg_match( '/((youtube|vimeo|dailymotion)\.com|youtu.be)/', $embed ) ) {
179 if ( 0 === $return['count']['video'] ) {
180 $return['type'] = 'video';
181 $return['video'] = 'http://' . $embed;
182 $return['secure']['video'] = self::https( $return['video'] );
183 if ( str_contains( $embed, 'youtube' ) ) {
184 $return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) );
185 $return['secure']['image'] = self::https( $return['image'] );
186 } elseif ( str_contains( $embed, 'youtu.be' ) ) {
187 $youtube_id = jetpack_get_youtube_id( $return['video'] );
188 $return['video'] = 'http://youtube.com/watch?v=' . $youtube_id . '&feature=youtu.be';
189 $return['secure']['video'] = self::https( $return['video'] );
190 $return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) );
191 $return['secure']['image'] = self::https( $return['image'] );
192 } elseif ( str_contains( $embed, 'vimeo' ) ) {
193 $poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true );
194 if ( ! empty( $poster_image ) ) {
195 $return['image'] = $poster_image;
196 $poster_url_parts = wp_parse_url( $poster_image );
197 $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path'];
198 }
199 } elseif ( str_contains( $embed, 'dailymotion' ) ) {
200 $return['image'] = str_replace( 'dailymotion.com/video/', 'dailymotion.com/thumbnail/video/', $embed );
201 $return['image'] = wp_parse_url( $return['image'], PHP_URL_SCHEME ) === null ? 'http://' . $return['image'] : $return['image'];
202 $return['secure']['image'] = self::https( $return['image'] );
203 }
204 }
205 ++$return['count']['video'];
206 }
207 }
208 }
209
210 // Do we really want to make the video the primary focus of the post?
211 if ( 'video' === $return['type'] ) {
212 $content = wpautop( wp_strip_all_tags( $post->post_content ) );
213 $paragraphs = explode( '</p>', $content );
214 $number_of_paragraphs = 0;
215
216 foreach ( $paragraphs as $i => $paragraph ) {
217 // Don't include blank lines as a paragraph.
218 if ( '' === trim( $paragraph ) ) {
219 unset( $paragraphs[ $i ] );
220 continue;
221 }
222 ++$number_of_paragraphs;
223 }
224
225 $number_of_paragraphs = $number_of_paragraphs - $return['count']['video']; // subtract amount for videos.
226
227 // More than 2 paragraph? The video is not the primary focus so we can do some more analysis.
228 if ( $number_of_paragraphs > 2 ) {
229 $return['type'] = 'standard';
230 }
231 }
232
233 // If we don't have any prioritized embed...
234 if ( 'standard' === $return['type'] ) {
235 if ( ( ! empty( $extract['has']['gallery'] ) || ! empty( $extract['shortcode']['gallery']['count'] ) ) && ! empty( $extract['image'] ) ) {
236 // ... Then we prioritize galleries first (multiple images returned)
237 $return['type'] = 'gallery';
238 $return['images'] = $extract['image'];
239 foreach ( $return['images'] as $image ) {
240 $return['secure']['images'][] = array( 'url' => self::ssl_img( $image['url'] ) );
241 ++$return['count']['image'];
242 }
243 } elseif ( ! empty( $extract['has']['image'] ) ) {
244 // ... Or we try and select a single image that would make sense.
245 $content = wpautop( wp_strip_all_tags( $post->post_content ) );
246 $paragraphs = explode( '</p>', $content );
247 $number_of_paragraphs = 0;
248
249 foreach ( $paragraphs as $i => $paragraph ) {
250 // Don't include 'actual' captions as a paragraph.
251 if ( str_contains( $paragraph, '[caption' ) ) {
252 unset( $paragraphs[ $i ] );
253 continue;
254 }
255 // Don't include blank lines as a paragraph.
256 if ( '' === trim( $paragraph ) ) {
257 unset( $paragraphs[ $i ] );
258 continue;
259 }
260 ++$number_of_paragraphs;
261 }
262
263 $return['image'] = $extract['image'][0]['url'];
264 $return['secure']['image'] = self::ssl_img( $return['image'] );
265 ++$return['count']['image'];
266
267 if ( $number_of_paragraphs <= 2 && is_countable( $extract['image'] ) && 1 === count( $extract['image'] ) ) {
268 // If we have lots of text or images, let's not treat it as an image post, but return its first image.
269 $return['type'] = 'image';
270 }
271 }
272 }
273
274 if ( $switched ) {
275 restore_current_blog();
276 }
277
278 /**
279 * Allow a theme or plugin to inspect and ultimately change the media summary.
280 *
281 * @since 4.4.0
282 *
283 * @param array $data The calculated media summary data.
284 * @param int $post_id The id of the post this data applies to.
285 */
286 $return = apply_filters( 'jetpack_media_summary_output', $return, $post_id );
287
288 self::$cache[ $cache_key ] = $return;
289
290 return $return;
291 }
292
293 /**
294 * Converts http to https://
295 *
296 * @param string $str URL.
297 *
298 * @return string URL.
299 */
300 public static function https( $str ) {
301 return str_replace( 'http://', 'https://', $str );
302 }
303
304 /**
305 * Returns a Photonized version of the URL.
306 *
307 * @param string $url URL.
308 *
309 * @return string URL.
310 */
311 public static function ssl_img( $url ) {
312 if ( str_contains( $url, 'files.wordpress.com' ) ) {
313 return self::https( $url );
314 } else {
315 return self::https( Image_CDN_Core::cdn_url( $url ) );
316 }
317 }
318
319 /**
320 * Get the video poster.
321 *
322 * @param string $type Video service.
323 * @param string $id Video ID for the service.
324 *
325 * @return string URL of image thumbnail for the video.
326 */
327 public static function get_video_poster( $type, $id ) {
328 if ( 'videopress' === $type ) {
329 if ( function_exists( 'video_get_highest_resolution_image_url' ) ) {
330 return video_get_highest_resolution_image_url( $id );
331 } elseif ( class_exists( 'VideoPress_Video' ) ) {
332 $video = new VideoPress_Video( $id );
333 return $video->poster_frame_uri;
334 }
335 } elseif ( 'youtube' === $type ) {
336 return 'http://img.youtube.com/vi/' . $id . '/0.jpg';
337 }
338 }
339
340 /**
341 * Clean text of shortcodes and tags.
342 *
343 * @param string $text Dirty text.
344 *
345 * @return string Clean text.
346 */
347 public static function clean_text( $text ) {
348 return trim(
349 preg_replace(
350 '/[\s]+/',
351 ' ',
352 preg_replace(
353 '@https?://[\S]+@',
354 '',
355 strip_shortcodes(
356 wp_strip_all_tags(
357 $text
358 )
359 )
360 )
361 )
362 );
363 }
364
365 /**
366 * Retrieve an excerpt for the post summary.
367 *
368 * This function works around a suspected problem with Core. If resolved, this function should be simplified.
369 *
370 * @link https://github.com/Automattic/jetpack/pull/8510
371 * @link https://core.trac.wordpress.org/ticket/42814
372 *
373 * @param string $post_content The post's content.
374 * @param string $post_excerpt The post's excerpt. Empty if none was explicitly set.
375 * @param int $max_words Maximum number of words for the excerpt. Used on wp.com. Default 16.
376 * @param int $max_chars Maximum characters in the excerpt. Used on wp.com. Default 256.
377 * @param WP_Post $requested_post The post object.
378 * @return string Post excerpt.
379 **/
380 public static function get_excerpt( $post_content, $post_excerpt, $max_words = 16, $max_chars = 256, $requested_post = null ) {
381 global $post;
382 $original_post = $post; // Saving the global for later use.
383 if ( empty( $post_excerpt ) && function_exists( 'wpcom_enhanced_excerpt_extract_excerpt' ) ) {
384 return self::clean_text(
385 wpcom_enhanced_excerpt_extract_excerpt(
386 array(
387 'text' => $post_content,
388 'excerpt_only' => true,
389 'show_read_more' => false,
390 'max_words' => $max_words,
391 'max_chars' => $max_chars,
392 'read_more_threshold' => 25,
393 )
394 )
395 );
396 } elseif ( $requested_post instanceof WP_Post ) {
397 // @todo Refactor to not need to override the global.
398 // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited
399 $post = $requested_post; // setup_postdata does not set the global.
400 setup_postdata( $post );
401 /** This filter is documented in core/src/wp-includes/post-template.php */
402 $post_excerpt = apply_filters( 'get_the_excerpt', $post_excerpt, $post );
403 // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited
404 $post = $original_post; // wp_reset_postdata uses the $post global.
405 wp_reset_postdata();
406 return self::clean_text( $post_excerpt );
407 }
408 return '';
409 }
410
411 /**
412 * Split a string into an array of words.
413 *
414 * @param string $text Post content or excerpt.
415 *
416 * @return array Array of words.
417 */
418 public static function split_content_in_words( $text ) {
419 $words = preg_split( '/[\s!?;,.]+/', $text, -1, PREG_SPLIT_NO_EMPTY );
420
421 // Return an empty array if the split above fails.
422 return $words ? $words : array();
423 }
424
425 /**
426 * Get the word count.
427 *
428 * @param string $post_content Post content.
429 *
430 * @return int Word count.
431 */
432 public static function get_word_count( $post_content ) {
433 return (int) count( self::split_content_in_words( self::clean_text( $post_content ) ) );
434 }
435
436 /**
437 * Get remainder word count (after the excerpt).
438 *
439 * @param string $post_content Post content.
440 * @param string $excerpt_content Excerpt content.
441 *
442 * @return int Number of words after the excerpt.
443 */
444 public static function get_word_remaining_count( $post_content, $excerpt_content ) {
445 $content_word_count = count( self::split_content_in_words( self::clean_text( $post_content ) ) );
446 $excerpt_word_count = count( self::split_content_in_words( self::clean_text( $excerpt_content ) ) );
447
448 return (int) $content_word_count - $excerpt_word_count;
449 }
450
451 /**
452 * Counts the number of links in a post.
453 *
454 * @param string $post_content Post content.
455 *
456 * @return false|int Number of links.
457 */
458 public static function get_link_count( $post_content ) {
459 return preg_match_all( '/\<a[\> ]/', $post_content, $matches );
460 }
461 }
462