class.media-summary.php 12 KB

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