PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.7.2
Jetpack – WP Security, Backup, Speed, & Growth v13.7.2
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 2 years ago class-jetpack-google-drive-helper.php 2 years ago class-jetpack-instagram-gallery-helper.php 2 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-top-posts-helper.php 2 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 2 years ago class.jetpack-password-checker.php 2 years ago class.jetpack-search-performance-logger.php 4 years ago class.media-extractor.php 2 years ago class.media-summary.php 2 years ago class.media.php 2 years ago components.php 3 years ago debugger.php 2 years ago functions.wp-notify.php 2 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
465 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 if ( ! isset( $extract['shortcode']['youtube']['id'][0] ) ) {
150 break;
151 }
152 $return['type'] = 'video';
153 $return['video'] = esc_url_raw( 'http://www.youtube.com/watch?feature=player_embedded&v=' . $extract['shortcode']['youtube']['id'][0] );
154 $return['image'] = self::get_video_poster( 'youtube', $extract['shortcode']['youtube']['id'][0] );
155 $return['secure']['video'] = self::https( $return['video'] );
156 $return['secure']['image'] = self::https( $return['image'] );
157 }
158 ++$return['count']['video'];
159 break;
160 case 'vimeo':
161 if ( 0 === $return['count']['video'] ) {
162 $return['type'] = 'video';
163 $return['video'] = esc_url_raw( 'http://vimeo.com/' . $extract['shortcode']['vimeo']['id'][0] );
164 $return['secure']['video'] = self::https( $return['video'] );
165
166 $poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true );
167 if ( ! empty( $poster_image ) ) {
168 $return['image'] = $poster_image;
169 $poster_url_parts = wp_parse_url( $poster_image );
170 $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path'];
171 }
172 }
173 ++$return['count']['video'];
174 break;
175 }
176 }
177 }
178
179 if ( ! empty( $extract['has']['embed'] ) ) {
180 foreach ( $extract['embed']['url'] as $embed ) {
181 if ( preg_match( '/((youtube|vimeo|dailymotion)\.com|youtu.be)/', $embed ) ) {
182 if ( 0 === $return['count']['video'] ) {
183 $return['type'] = 'video';
184 $return['video'] = 'http://' . $embed;
185 $return['secure']['video'] = self::https( $return['video'] );
186 if ( str_contains( $embed, 'youtube' ) ) {
187 $return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) );
188 $return['secure']['image'] = self::https( $return['image'] );
189 } elseif ( str_contains( $embed, 'youtu.be' ) ) {
190 $youtube_id = jetpack_get_youtube_id( $return['video'] );
191 $return['video'] = 'http://youtube.com/watch?v=' . $youtube_id . '&feature=youtu.be';
192 $return['secure']['video'] = self::https( $return['video'] );
193 $return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) );
194 $return['secure']['image'] = self::https( $return['image'] );
195 } elseif ( str_contains( $embed, 'vimeo' ) ) {
196 $poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true );
197 if ( ! empty( $poster_image ) ) {
198 $return['image'] = $poster_image;
199 $poster_url_parts = wp_parse_url( $poster_image );
200 $return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path'];
201 }
202 } elseif ( str_contains( $embed, 'dailymotion' ) ) {
203 $return['image'] = str_replace( 'dailymotion.com/video/', 'dailymotion.com/thumbnail/video/', $embed );
204 $return['image'] = wp_parse_url( $return['image'], PHP_URL_SCHEME ) === null ? 'http://' . $return['image'] : $return['image'];
205 $return['secure']['image'] = self::https( $return['image'] );
206 }
207 }
208 ++$return['count']['video'];
209 }
210 }
211 }
212
213 // Do we really want to make the video the primary focus of the post?
214 if ( 'video' === $return['type'] ) {
215 $content = wpautop( wp_strip_all_tags( $post->post_content ) );
216 $paragraphs = explode( '</p>', $content );
217 $number_of_paragraphs = 0;
218
219 foreach ( $paragraphs as $i => $paragraph ) {
220 // Don't include blank lines as a paragraph.
221 if ( '' === trim( $paragraph ) ) {
222 unset( $paragraphs[ $i ] );
223 continue;
224 }
225 ++$number_of_paragraphs;
226 }
227
228 $number_of_paragraphs = $number_of_paragraphs - $return['count']['video']; // subtract amount for videos.
229
230 // More than 2 paragraph? The video is not the primary focus so we can do some more analysis.
231 if ( $number_of_paragraphs > 2 ) {
232 $return['type'] = 'standard';
233 }
234 }
235
236 // If we don't have any prioritized embed...
237 if ( 'standard' === $return['type'] ) {
238 if ( ( ! empty( $extract['has']['gallery'] ) || ! empty( $extract['shortcode']['gallery']['count'] ) ) && ! empty( $extract['image'] ) ) {
239 // ... Then we prioritize galleries first (multiple images returned)
240 $return['type'] = 'gallery';
241 $return['images'] = $extract['image'];
242 foreach ( $return['images'] as $image ) {
243 $return['secure']['images'][] = array( 'url' => self::ssl_img( $image['url'] ) );
244 ++$return['count']['image'];
245 }
246 } elseif ( ! empty( $extract['has']['image'] ) ) {
247 // ... Or we try and select a single image that would make sense.
248 $content = wpautop( wp_strip_all_tags( $post->post_content ) );
249 $paragraphs = explode( '</p>', $content );
250 $number_of_paragraphs = 0;
251
252 foreach ( $paragraphs as $i => $paragraph ) {
253 // Don't include 'actual' captions as a paragraph.
254 if ( str_contains( $paragraph, '[caption' ) ) {
255 unset( $paragraphs[ $i ] );
256 continue;
257 }
258 // Don't include blank lines as a paragraph.
259 if ( '' === trim( $paragraph ) ) {
260 unset( $paragraphs[ $i ] );
261 continue;
262 }
263 ++$number_of_paragraphs;
264 }
265
266 $return['image'] = $extract['image'][0]['url'];
267 $return['secure']['image'] = self::ssl_img( $return['image'] );
268 ++$return['count']['image'];
269
270 if ( $number_of_paragraphs <= 2 && is_countable( $extract['image'] ) && 1 === count( $extract['image'] ) ) {
271 // If we have lots of text or images, let's not treat it as an image post, but return its first image.
272 $return['type'] = 'image';
273 }
274 }
275 }
276
277 if ( $switched ) {
278 restore_current_blog();
279 }
280
281 /**
282 * Allow a theme or plugin to inspect and ultimately change the media summary.
283 *
284 * @since 4.4.0
285 *
286 * @param array $data The calculated media summary data.
287 * @param int $post_id The id of the post this data applies to.
288 */
289 $return = apply_filters( 'jetpack_media_summary_output', $return, $post_id );
290
291 self::$cache[ $cache_key ] = $return;
292
293 return $return;
294 }
295
296 /**
297 * Converts http to https://
298 *
299 * @param string $str URL.
300 *
301 * @return string URL.
302 */
303 public static function https( $str ) {
304 return str_replace( 'http://', 'https://', $str );
305 }
306
307 /**
308 * Returns a Photonized version of the URL.
309 *
310 * @param string $url URL.
311 *
312 * @return string URL.
313 */
314 public static function ssl_img( $url ) {
315 if ( str_contains( $url, 'files.wordpress.com' ) ) {
316 return self::https( $url );
317 } else {
318 return self::https( Image_CDN_Core::cdn_url( $url ) );
319 }
320 }
321
322 /**
323 * Get the video poster.
324 *
325 * @param string $type Video service.
326 * @param string $id Video ID for the service.
327 *
328 * @return string URL of image thumbnail for the video.
329 */
330 public static function get_video_poster( $type, $id ) {
331 if ( 'videopress' === $type ) {
332 if ( function_exists( 'video_get_highest_resolution_image_url' ) ) {
333 return video_get_highest_resolution_image_url( $id );
334 } elseif ( class_exists( 'VideoPress_Video' ) ) {
335 $video = new VideoPress_Video( $id );
336 return $video->poster_frame_uri;
337 }
338 } elseif ( 'youtube' === $type ) {
339 return 'http://img.youtube.com/vi/' . $id . '/0.jpg';
340 }
341 }
342
343 /**
344 * Clean text of shortcodes and tags.
345 *
346 * @param string $text Dirty text.
347 *
348 * @return string Clean text.
349 */
350 public static function clean_text( $text ) {
351 return trim(
352 preg_replace(
353 '/[\s]+/',
354 ' ',
355 preg_replace(
356 '@https?://[\S]+@',
357 '',
358 strip_shortcodes(
359 wp_strip_all_tags(
360 $text
361 )
362 )
363 )
364 )
365 );
366 }
367
368 /**
369 * Retrieve an excerpt for the post summary.
370 *
371 * This function works around a suspected problem with Core. If resolved, this function should be simplified.
372 *
373 * @link https://github.com/Automattic/jetpack/pull/8510
374 * @link https://core.trac.wordpress.org/ticket/42814
375 *
376 * @param string $post_content The post's content.
377 * @param string $post_excerpt The post's excerpt. Empty if none was explicitly set.
378 * @param int $max_words Maximum number of words for the excerpt. Used on wp.com. Default 16.
379 * @param int $max_chars Maximum characters in the excerpt. Used on wp.com. Default 256.
380 * @param WP_Post $requested_post The post object.
381 * @return string Post excerpt.
382 **/
383 public static function get_excerpt( $post_content, $post_excerpt, $max_words = 16, $max_chars = 256, $requested_post = null ) {
384 global $post;
385 $original_post = $post; // Saving the global for later use.
386 if ( empty( $post_excerpt ) && function_exists( 'wpcom_enhanced_excerpt_extract_excerpt' ) ) {
387 return self::clean_text(
388 wpcom_enhanced_excerpt_extract_excerpt(
389 array(
390 'text' => $post_content,
391 'excerpt_only' => true,
392 'show_read_more' => false,
393 'max_words' => $max_words,
394 'max_chars' => $max_chars,
395 'read_more_threshold' => 25,
396 )
397 )
398 );
399 } elseif ( $requested_post instanceof WP_Post ) {
400 // @todo Refactor to not need to override the global.
401 // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited
402 $post = $requested_post; // setup_postdata does not set the global.
403 setup_postdata( $post );
404 /** This filter is documented in core/src/wp-includes/post-template.php */
405 $post_excerpt = apply_filters( 'get_the_excerpt', $post_excerpt, $post );
406 // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited
407 $post = $original_post; // wp_reset_postdata uses the $post global.
408 wp_reset_postdata();
409 return self::clean_text( $post_excerpt );
410 }
411 return '';
412 }
413
414 /**
415 * Split a string into an array of words.
416 *
417 * @param string $text Post content or excerpt.
418 *
419 * @return array Array of words.
420 */
421 public static function split_content_in_words( $text ) {
422 $words = preg_split( '/[\s!?;,.]+/', $text, -1, PREG_SPLIT_NO_EMPTY );
423
424 // Return an empty array if the split above fails.
425 return $words ? $words : array();
426 }
427
428 /**
429 * Get the word count.
430 *
431 * @param string $post_content Post content.
432 *
433 * @return int Word count.
434 */
435 public static function get_word_count( $post_content ) {
436 return (int) count( self::split_content_in_words( self::clean_text( $post_content ) ) );
437 }
438
439 /**
440 * Get remainder word count (after the excerpt).
441 *
442 * @param string $post_content Post content.
443 * @param string $excerpt_content Excerpt content.
444 *
445 * @return int Number of words after the excerpt.
446 */
447 public static function get_word_remaining_count( $post_content, $excerpt_content ) {
448 $content_word_count = count( self::split_content_in_words( self::clean_text( $post_content ) ) );
449 $excerpt_word_count = count( self::split_content_in_words( self::clean_text( $excerpt_content ) ) );
450
451 return (int) $content_word_count - $excerpt_word_count;
452 }
453
454 /**
455 * Counts the number of links in a post.
456 *
457 * @param string $post_content Post content.
458 *
459 * @return false|int Number of links.
460 */
461 public static function get_link_count( $post_content ) {
462 return preg_match_all( '/\<a[\> ]/', $post_content, $matches );
463 }
464 }
465