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