Ticket #6177: 6177.1.patch
File 6177.1.patch, 62.3 KB (added by , 10 years ago) |
---|
-
src/bp-activity/bp-activity-filters.php
diff --git a/src/bp-activity/bp-activity-filters.php b/src/bp-activity/bp-activity-filters.php index 82f992a..5dc0d4d 100644
a b function bp_activity_make_nofollow_filter( $text ) { 386 386 /** 387 387 * Truncate long activity entries when viewed in activity streams. 388 388 * 389 * This method can only be used inside the Activity loop. 390 * 389 391 * @since BuddyPress (1.5.0) 390 392 * 391 393 * @uses bp_is_single_activity() … … function bp_activity_make_nofollow_filter( $text ) { 402 404 function bp_activity_truncate_entry( $text ) { 403 405 global $activities_template; 404 406 407 /** 408 * Provides a filter that lets you choose whether to skip this filter on a per-activity basis. 409 * 410 * @param bool $maybe_truncate_text If true, text should be checked to see if it needs truncating. 411 * @since BuddyPress (2.3.0) 412 */ 413 $maybe_truncate_text = apply_filters( 'bp_activity_maybe_truncate_entry', 414 ! in_array( $activities_template->activity->type, array( 'new_blog_post', ), true ) 415 ); 416 405 417 // The full text of the activity update should always show on the single activity screen 406 if ( bp_is_single_activity() )418 if ( ! $maybe_truncate_text || bp_is_single_activity() ) { 407 419 return $text; 420 } 408 421 409 422 /** 410 423 * Filters the appended text for the activity excerpt. -
src/bp-activity/bp-activity-functions.php
diff --git a/src/bp-activity/bp-activity-functions.php b/src/bp-activity/bp-activity-functions.php index 756dc03..67c13ec 100644
a b function bp_activity_post_type_publish( $post_id = 0, $post = null, $user_id = 0 1869 1869 'recorded_time' => $post->post_date_gmt, 1870 1870 ); 1871 1871 1872 // Remove large images and replace them with just one image thumbnail.1873 if ( ! empty( $activity_args['content'] ) ) {1874 $activity_args['content'] = bp_activity_thumbnail_content_images( $activity_args['content'], $activity_args['primary_link'], $activity_args );1875 }1876 1877 1872 if ( ! empty( $activity_args['content'] ) ) { 1878 1873 // Create the excerpt. 1879 $activity_ excerpt = bp_create_excerpt( $activity_args['content']);1874 $activity_summary = bp_activity_create_summary( $activity_args['content'], $activity_args ); 1880 1875 1881 1876 // Backward compatibility filter for blog posts. 1882 1877 if ( 'blogs' == $activity_post_object->component_id ) { 1883 $activity_args['content'] = apply_filters( 'bp_blogs_record_activity_content', $activity_ excerpt, $activity_args['content'], $activity_args, $post->post_type );1878 $activity_args['content'] = apply_filters( 'bp_blogs_record_activity_content', $activity_summary, $activity_args['content'], $activity_args, $post->post_type ); 1884 1879 } else { 1885 $activity_args['content'] = $activity_ excerpt;1880 $activity_args['content'] = $activity_summary; 1886 1881 } 1887 1882 } 1888 1883 … … function bp_activity_post_type_update( $post = null ) { 1963 1958 $activity = new BP_Activity_Activity( $activity_id ); 1964 1959 1965 1960 if ( ! empty( $post->post_content ) ) { 1966 // Make sure to update the thumbnail image. 1967 $post_content = bp_activity_thumbnail_content_images( $post->post_content, $activity->primary_link, (array) $activity ); 1968 1969 // Generate an excerpt. 1970 $activity_excerpt = bp_create_excerpt( $post_content ); 1961 $activity_summary = bp_activity_create_summary( $post->post_content, (array) $activity ); 1971 1962 1972 1963 // Backward compatibility filter for the blogs component. 1973 1964 if ( 'blogs' == $activity_post_object->component_id ) { 1974 $activity->content = apply_filters( 'bp_blogs_record_activity_content', $activity_ excerpt, $post_content, (array) $activity, $post->post_type );1965 $activity->content = apply_filters( 'bp_blogs_record_activity_content', $activity_summary, $post->post_content, (array) $activity, $post->post_type ); 1975 1966 } else { 1976 $activity->content = $activity_ excerpt;1967 $activity->content = $activity_summary; 1977 1968 } 1978 1969 } 1979 1970 … … function bp_activity_hide_user_activity( $user_id ) { 2585 2576 * through the content, grabs the first image and converts it to a thumbnail, 2586 2577 * and removes the rest of the images from the string. 2587 2578 * 2579 * As of BuddyPress 2.3, this function is no longer in use. 2580 * 2588 2581 * @since BuddyPress (1.2.0) 2589 2582 * 2590 2583 * @uses esc_attr() … … function bp_activity_thumbnail_content_images( $content, $link = false, $args = 2656 2649 } 2657 2650 2658 2651 /** 2652 * Create a rich summary of an activity item for the activity stream. 2653 * 2654 * More than just a simple excerpt, the summary could contain oEmbeds and other types of media. 2655 * Currently, it's only used for blog post items, but it will probably be used for all types of 2656 * activity in the future. 2657 * 2658 * @param string $content The content of the activity item. 2659 * @param array $activity_args The data passed to bp_activity_add() or the values from an Activity obj. 2660 * @return string 2661 * @since BuddyPress (2.3.0) 2662 */ 2663 function bp_activity_create_summary( $content, $activity ) { 2664 $args = array( 2665 'width' => isset( $GLOBALS['content_width'] ) ? (int) $GLOBALS['content_width'] : 'medium', 2666 ); 2667 2668 // Get the WP_Post object if this activity type is a blog post. 2669 if ( $activity['type'] === 'new_blog_post' ) { 2670 $content = get_post( $activity['secondary_item_id'] ); 2671 } 2672 2673 2674 /** 2675 * Filter the class name of the media extractor when creating an Activity summary. 2676 * 2677 * Use this filter to change the media extractor used to extract media info for the activity item. 2678 * 2679 * @param string $extractor Class name. 2680 * @param string $content The content of the activity item. 2681 * @param array $activity The data passed to bp_activity_add() or the values from an Activity obj. 2682 * @since BuddyPress (2.3.0) 2683 */ 2684 $extractor = apply_filters( 'bp_activity_create_summary_extractor_class', 'BP_Media_Extractor', $content, $activity ); 2685 $extractor = new $extractor; 2686 2687 /** 2688 * Filter the arguments passed to the media extractor when creating an Activity summary. 2689 * 2690 * @param array $args Array of bespoke data for the media extractor. 2691 * @param string $content The content of the activity item. 2692 * @param array $activity The data passed to bp_activity_add() or the values from an Activity obj. 2693 * @param BP_Media_Extractor $extractor The media extractor object. 2694 * @since BuddyPress (2.3.0) 2695 */ 2696 $args = apply_filters( 'bp_activity_create_summary_extractor_args', $args, $content, $activity, $extractor ); 2697 2698 2699 // Extract media information from the $content. 2700 $media = $extractor->extract( $content, BP_Media_Extractor::ALL, $args ); 2701 2702 // If we converted $content to an object earlier, flip it back to a string. 2703 if ( is_a( $content, 'WP_Post' ) ) { 2704 $content = $content->post_content; 2705 } 2706 2707 $para_count = substr_count( strtolower( wpautop( $content ) ), '<p>' ); 2708 $has_audio = ! empty( $media['has']['audio'] ) && $media['has']['audio']; 2709 $has_videos = ! empty( $media['has']['videos'] ) && $media['has']['videos']; 2710 $has_feat_image = ! empty( $media['has']['featured_images'] ) && $media['has']['featured_images']; 2711 $has_galleries = ! empty( $media['has']['galleries'] ) && $media['has']['galleries']; 2712 $has_images = ! empty( $media['has']['images'] ) && $media['has']['images']; 2713 $has_embeds = false; 2714 2715 // Embeds must be subtracted from the paragraph count. 2716 if ( ! empty( $media['has']['embeds'] ) ) { 2717 $has_embeds = $media['has']['embeds'] > 0; 2718 $para_count -= count( $media['has']['embeds'] ); 2719 } 2720 2721 $extracted_media = array(); 2722 $use_media_type = ''; 2723 $image_source = ''; 2724 2725 // If it's a short article and there's an embed/audio/video, use it. 2726 if ( $para_count <= 3 ) { 2727 if ( $has_embeds ) { 2728 $use_media_type = 'embeds'; 2729 } elseif ( $has_audio ) { 2730 $use_media_type = 'audio'; 2731 } elseif ( $has_videos ) { 2732 $use_media_type = 'videos'; 2733 } 2734 } 2735 2736 // If not, or in any other situation, try to use an image. 2737 if ( ! $use_media_type && $has_images ) { 2738 $use_media_type = 'images'; 2739 $image_source = 'html'; 2740 2741 // Featured Image > Galleries > inline <img>. 2742 if ( $has_feat_image ) { 2743 $image_source = 'featured_images'; 2744 2745 } elseif ( $has_galleries ) { 2746 $image_source = 'galleries'; 2747 } 2748 } 2749 2750 // Extract an item from the $media results. 2751 if ( $use_media_type ) { 2752 if ( $use_media_type === 'images' ) { 2753 $extracted_media = wp_list_filter( $media[ $use_media_type ], array( 'source' => $image_source ) ); 2754 $extracted_media = array_shift( $extracted_media ); 2755 } else { 2756 $extracted_media = array_shift( $media[ $use_media_type ] ); 2757 } 2758 2759 /** 2760 * Filter the results of the media extractor when creating an Activity summary. 2761 * 2762 * @param array $extracted_media Extracted media item. See {@link BP_Media_Extractor::extract()} for format. 2763 * @param string $content Content of the activity item. 2764 * @param array $activity The data passed to bp_activity_add() or the values from an Activity obj. 2765 * @param array $media All results from the media extraction. See {@link BP_Media_Extractor::extract()} for format. 2766 * @param string $use_media_type The kind of media item that was preferentially extracted. 2767 * @param string $image_source If $use_media_type was "images", the preferential source of the image. 2768 * Otherwise empty. 2769 * @since BuddyPress (2.3.0) 2770 */ 2771 $extracted_media = apply_filters( 2772 'bp_activity_create_summary_extractor_result', 2773 $extracted_media, 2774 $content, 2775 $activity, 2776 $media, 2777 $use_media_type, 2778 $image_source 2779 ); 2780 } 2781 2782 // Generate a text excerpt for this activity item (and remove any oEmbeds URLs). 2783 $summary = strip_shortcodes( html_entity_decode( strip_tags( $content ) ) ); 2784 $summary = bp_create_excerpt( preg_replace( '#^\s*(https?://[^\s"]+)\s*$#im', '', $summary ) ); 2785 2786 if ( $use_media_type === 'embeds' ) { 2787 $summary .= PHP_EOL . PHP_EOL . $extracted_media['url']; 2788 } elseif ( $use_media_type === 'images' ) { 2789 $summary .= sprintf( ' <img src="%s">', esc_url( $extracted_media['url'] ) ); 2790 } elseif ( in_array( $use_media_type, array( 'audio', 'videos' ), true ) ) { 2791 $summary .= PHP_EOL . PHP_EOL . $extracted_media['original']; // Full shortcode. 2792 } 2793 2794 /** 2795 * Filters the newly-generated summary for the activity item. 2796 * 2797 * @param string $summary Activity summary HTML. 2798 * @param string $content $content Content of the activity item. 2799 * @param array $activity The data passed to bp_activity_add() or the values from an Activity obj. 2800 * @param array $extracted_media Media item extracted. See {@link BP_Media_Extractor::extract()} for format. 2801 * @since BuddyPress (2.3.0) 2802 */ 2803 return apply_filters( 'bp_activity_create_summary', $summary, $content, $activity, $extracted_media ); 2804 } 2805 2806 /** 2659 2807 * Fetch whether the current user is allowed to mark items as spam. 2660 2808 * 2661 2809 * @since BuddyPress (1.6.0) -
src/bp-blogs/bp-blogs-activity.php
diff --git a/src/bp-blogs/bp-blogs-activity.php b/src/bp-blogs/bp-blogs-activity.php index 422e149..c9b3115 100644
a b function bp_blogs_record_activity( $args = '' ) { 352 352 353 353 $r = wp_parse_args( $args, $defaults ); 354 354 355 // Remove large images and replace them with just one image thumbnail356 if ( ! empty( $r['content'] ) ) {357 $r['content'] = bp_activity_thumbnail_content_images( $r['content'], $r['primary_link'], $r );358 }359 360 355 if ( ! empty( $r['action'] ) ) { 361 356 362 357 /** … … function bp_blogs_record_activity( $args = '' ) { 376 371 * 377 372 * @since BuddyPress (1.2.0) 378 373 * 379 * @param string $value Generated excerptfrom content for the activity stream.374 * @param string $value Generated summary from content for the activity stream. 380 375 * @param string $value Content for the activity stream. 381 376 * @param array $r Array of arguments used for the activity stream item. 382 377 */ 383 $r['content'] = apply_filters( 'bp_blogs_record_activity_content', bp_ create_excerpt( $r['content']), $r['content'], $r );378 $r['content'] = apply_filters( 'bp_blogs_record_activity_content', bp_activity_create_summary( $r['content'], $r ), $r['content'], $r ); 384 379 } 385 380 386 381 // Check for an existing entry and update if one exists. -
src/bp-core/bp-core-classes.php
diff --git a/src/bp-core/bp-core-classes.php b/src/bp-core/bp-core-classes.php index b50325d..70d9084 100644
a b require dirname( __FILE__ ) . '/classes/class-bp-walker-nav-menu-checklist.php'; 20 20 require dirname( __FILE__ ) . '/classes/class-bp-suggestions.php'; 21 21 require dirname( __FILE__ ) . '/classes/class-bp-members-suggestions.php'; 22 22 require dirname( __FILE__ ) . '/classes/class-bp-recursive-query.php'; 23 require dirname( __FILE__ ) . '/classes/class-bp-media-extractor.php'; -
new file src/bp-core/classes/class-bp-media-extractor.php
diff --git a/src/bp-core/classes/class-bp-media-extractor.php b/src/bp-core/classes/class-bp-media-extractor.php new file mode 100644 index 0000000..ab40585
- + 1 <?php 2 /** 3 * Core component classes. 4 * 5 * @package BuddyPress 6 * @subpackage Core 7 */ 8 9 // Exit if accessed directly 10 defined( 'ABSPATH' ) || exit; 11 12 /** 13 * Extracts media from text. Use {@link extract()}. 14 * 15 * The supported types are links, mentions, images, shortcodes, embeds, audio, video, and "all". 16 * This is what each type extracts: 17 * 18 * Links: <a href="http://example.com"> 19 * Mentions: @name 20 * If the Activity component is enabled, we use it to parse out any @names. A consequence 21 * to note is that the "name" mentioned must match a real user account. If it's a made-up 22 * @name, then it isn't extracted. 23 * If the Activity component is disabled, any @name is extracted (both those matching 24 * real accounts, and those made-up). 25 * Images: <img src="image.gif">, [gallery], [gallery ids="2,3"], featured images (Post thumbnails). 26 * If an extracted image is in the Media Library, then its resolution will be included. 27 * Shortcodes: Extract information about any (registered) shortcodes. 28 * This includes any shortcodes indirectly covered by any of the other media extraction types. 29 * For example, [gallery]. 30 * Embeds: Extract any URL matching a registered oEmbed handler. 31 * Audio: <a href="*.mp3"">, [audio] 32 * See wp_get_audio_extensions() for supported audio formats. 33 * Video: [video] 34 * See wp_get_video_extensions() for supported video formats. 35 * 36 * @see BP_Media_Extractor::extract() Use this to extract media. 37 * @since BuddyPress (2.3.0) 38 */ 39 class BP_Media_Extractor { 40 /** 41 * Media type. 42 * 43 * @since BuddyPress (2.3.0) 44 * @var int 45 */ 46 const ALL = 255; 47 const LINKS = 1; 48 const MENTIONS = 2; 49 const IMAGES = 4; 50 const SHORTCODES = 8; 51 const EMBEDS = 16; 52 const AUDIO = 32; 53 const VIDEOS = 64; 54 55 56 /** 57 * Extract media from text. 58 * 59 * @param string|WP_Post $richtext Content to parse. 60 * @param int $what_to_extract Media type to extract (optional). 61 * @param array $extra_args Bespoke data for a particular extractor (optional). 62 * @return array { 63 * @type array $has Extracted media counts. { 64 * @type int $audio 65 * @type int $embeds 66 * @type int $images 67 * @type int $links 68 * @type int $mentions 69 * @type int $shortcodes 70 * @type int $video 71 * } 72 * @type array $audio Extracted audio. { 73 * Array of extracted media. 74 * 75 * @type string $source Media source. Either "html" or "shortcodes". 76 * @type string $url Link to audio. 77 * } 78 * @type array $embeds Extracted oEmbeds. { 79 * Array of extracted media. 80 * 81 * @type string $url oEmbed link. 82 * } 83 * @type array $images Extracted images. { 84 * Array of extracted media. 85 * 86 * @type int $gallery_id Gallery ID. Optional, not always set. 87 * @type int $height Width of image. If unknown, set to 0. 88 * @type string $source Media source. Either "html" or "galleries". 89 * @type string $url Link to image. 90 * @type int $width Width of image. If unknown, set to 0. 91 * } 92 * @type array $links Extracted URLs. { 93 * Array of extracted media. 94 * 95 * @type string $url Link. 96 * } 97 * @type array $mentions Extracted mentions. { 98 * Array of extracted media. 99 * 100 * @type string $name @mention. 101 * @type string $user_id User ID. Optional, only set if Activity component enabled. 102 * } 103 * @type array $shortcodes Extracted shortcodes. { 104 * Array of extracted media. 105 * 106 * @type array $attributes Key/value pairs of the shortcodes attributes (if any). 107 * @type string $content Text wrapped by the shortcode. 108 * @type string $type Shortcode type. 109 * @type string $original The entire shortcode. 110 * } 111 * @type array $videos Extracted video. { 112 * Array of extracted media. 113 * 114 * @type string $source Media source. Currently only "shortcodes". 115 * @type string $url Link to audio. 116 * } 117 * } 118 * @since BuddyPress (2.3.0) 119 */ 120 public function extract( $richtext, $what_to_extract = self::ALL, $extra_args = array() ) { 121 $media = array(); 122 123 // Support passing a WordPress Post for the $richtext parameter. 124 if ( is_a( $richtext, 'WP_Post' ) ) { 125 $extra_args['post'] = $richtext; 126 $richtext = $extra_args['post']->post_content; 127 } 128 129 $plaintext = $this->strip_markup( $richtext ); 130 131 132 // Extract links. 133 if ( self::LINKS & $what_to_extract ) { 134 $media = array_merge_recursive( $media, $this->extract_links( $richtext, $plaintext, $extra_args ) ); 135 } 136 137 // Extract mentions. 138 if ( self::MENTIONS & $what_to_extract ) { 139 $media = array_merge_recursive( $media, $this->extract_mentions( $richtext, $plaintext, $extra_args ) ); 140 } 141 142 // Extract images. 143 if ( self::IMAGES & $what_to_extract ) { 144 $media = array_merge_recursive( $media, $this->extract_images( $richtext, $plaintext, $extra_args ) ); 145 } 146 147 // Extract shortcodes. 148 if ( self::SHORTCODES & $what_to_extract ) { 149 $media = array_merge_recursive( $media, $this->extract_shortcodes( $richtext, $plaintext, $extra_args ) ); 150 } 151 152 // Extract oEmbeds. 153 if ( self::EMBEDS & $what_to_extract ) { 154 $media = array_merge_recursive( $media, $this->extract_embeds( $richtext, $plaintext, $extra_args ) ); 155 } 156 157 // Extract audio. 158 if ( self::AUDIO & $what_to_extract ) { 159 $media = array_merge_recursive( $media, $this->extract_audio( $richtext, $plaintext, $extra_args ) ); 160 } 161 162 // Extract video. 163 if ( self::VIDEOS & $what_to_extract ) { 164 $media = array_merge_recursive( $media, $this->extract_video( $richtext, $plaintext, $extra_args ) ); 165 } 166 167 /** 168 * Filters media extracted from text. 169 * 170 * @param array $media Extracted media. See {@link BP_Media_Extractor::extract()} for format. 171 * @param string $richtext Content to parse. 172 * @param int $what_to_extract Media type to extract. 173 * @param array $extra_args Bespoke data for a particular extractor. 174 * @param string $plaintext Copy of $richtext without any markup. 175 * @since BuddyPress (2.3.0) 176 */ 177 return apply_filters( 'bp_media_extractor_extract', $media, $richtext, $what_to_extract, $extra_args, $plaintext ); 178 } 179 180 181 /** 182 * Content type specific extraction methods. 183 * 184 * You shouldn't need to use these directly; just use {@link BP_Media_Extractor::extract()}. 185 */ 186 187 /** 188 * Extract `<a href>` tags from text. 189 * 190 * @param string $richtext Content to parse. 191 * @param string $plaintext Sanitized version of the content. 192 * @param array $extra_args Bespoke data for a particular extractor (optional). 193 * @return array { 194 * @type array $has Extracted media counts. { 195 * @type int $links 196 * } 197 * @type array $links Extracted URLs. { 198 * Array of extracted media. 199 * 200 * @type string $url Link. 201 * } 202 * } 203 * @since BuddyPress (2.3.0) 204 */ 205 protected function extract_links( $richtext, $plaintext, $extra_args = array() ) { 206 $data = array( 'has' => array( 'links' => 0 ), 'links' => array() ); 207 208 // Matches: href="text" and href='text' 209 if ( stripos( $richtext, 'href=' ) !== false ) { 210 preg_match_all( '#href=(["\'])([^"\']+)\1#i', $richtext, $matches ); 211 212 if ( ! empty( $matches[2] ) ) { 213 $matches[2] = array_unique( $matches[2] ); 214 215 foreach ( $matches[2] as $link_src ) { 216 $link_src = esc_url_raw( $link_src ); 217 218 if ( $link_src ) { 219 $data['links'][] = array( 'url' => $link_src ); 220 } 221 } 222 } 223 } 224 225 $data['has']['links'] = count( $data['links'] ); 226 227 /** 228 * Filters links extracted from text. 229 * 230 * @param array $data Extracted links. See {@link BP_Media_Extractor::extract_links()} for format. 231 * @param string $richtext Content to parse. 232 * @param string $plaintext Copy of $richtext without any markup. 233 * @param array $extra_args Bespoke data for a particular extractor. 234 * @since BuddyPress (2.3.0) 235 */ 236 return apply_filters( 'bp_media_extractor_links', $data, $richtext, $plaintext, $extra_args ); 237 } 238 239 /** 240 * Extract @mentions tags from text. 241 * 242 * If the Activity component is enabled, it is used to parse @mentions. 243 * The mentioned "name" must match a user account, otherwise it is discarded. 244 * 245 * If the Activity component is disabled, any @mentions are extracted. 246 * 247 * @param string $richtext Content to parse. 248 * @param string $plaintext Sanitized version of the content. 249 * @param array $extra_args Bespoke data for a particular extractor. 250 * @return array { 251 * @type array $has Extracted media counts. { 252 * @type int $mentions 253 * } 254 * @type array $mentions Extracted mentions. { 255 * Array of extracted media. 256 * 257 * @type string $name @mention. 258 * @type string $user_id User ID. Optional, only set if Activity component enabled. 259 * } 260 * } 261 * @since BuddyPress (2.3.0) 262 */ 263 protected function extract_mentions( $richtext, $plaintext, $extra_args = array() ) { 264 $data = array( 'has' => array( 'mentions' => 0 ), 'mentions' => array() ); 265 $mentions = array(); 266 267 // If the Activity component is active, use it to parse @mentions. 268 if ( bp_is_active( 'activity' ) ) { 269 $mentions = bp_activity_find_mentions( $plaintext ); 270 if ( ! $mentions ) { 271 $mentions = array(); 272 } 273 274 // If the Activity component is disabled, instead do a basic parse. 275 } else { 276 if ( strpos( $plaintext, '@' ) !== false ) { 277 preg_match_all( '/[@]+([A-Za-z0-9-_\.@]+)\b/', $plaintext, $matches ); 278 279 if ( ! empty( $matches[1] ) ) { 280 $mentions = array_unique( array_map( 'strtolower', $matches[1] ) ); 281 } 282 } 283 } 284 285 // Build results 286 foreach ( $mentions as $user_id => $mention_name ) { 287 $mention = array( 'name' => strtolower( $mention_name ) ); 288 289 // If the Activity component is active, store the User ID, too. 290 if ( bp_is_active( 'activity' ) ) { 291 $mention['user_id'] = (int) $user_id; 292 } 293 294 $data['mentions'][] = $mention; 295 } 296 297 $data['has']['mentions'] = count( $data['mentions'] ); 298 299 /** 300 * Filters @mentions extracted from text. 301 * 302 * @param array $data Extracted @mentions. See {@link BP_Media_Extractor::extract_mentions()} for format. 303 * @param string $richtext Content to parse. 304 * @param string $plaintext Copy of $richtext without any markup. 305 * @param array $extra_args Bespoke data for a particular extractor (optional). 306 * @since BuddyPress (2.3.0) 307 */ 308 return apply_filters( 'bp_media_extractor_mentions', $data, $richtext, $plaintext, $extra_args ); 309 } 310 311 /** 312 * Extract images from `<img src>` tags, [galleries], and featured images from a Post. 313 * 314 * If an image is in the Media Library, then its resolution is included in the results. 315 * 316 * @param string $richtext Content to parse. 317 * @param string $plaintext Sanitized version of the content. 318 * @param array $extra_args Bespoke data for a particular extractor (optional). 319 * @return array { 320 * @type array $has Extracted media counts. { 321 * @type int $images 322 * } 323 * @type array $images Extracted images. { 324 * Array of extracted media. 325 * 326 * @type int $gallery_id Gallery ID. Optional, not always set. 327 * @type int $height Width of image. If unknown, set to 0. 328 * @type string $source Media source. Either "html" or "galleries". 329 * @type string $url Link to image. 330 * @type int $width Width of image. If unknown, set to 0. 331 * } 332 * } 333 * @since BuddyPress (2.3.0) 334 */ 335 protected function extract_images( $richtext, $plaintext, $extra_args = array() ) { 336 $media = array( 'has' => array( 'images' => 0 ), 'images' => array() ); 337 338 $featured_image = $this->extract_images_from_featured_images( $richtext, $plaintext, $extra_args ); 339 $galleries = $this->extract_images_from_galleries( $richtext, $plaintext, $extra_args ); 340 341 342 // `<img src>` tags. 343 if ( stripos( $richtext, 'src=' ) !== false ) { 344 preg_match_all( '#src=(["\'])([^"\']+)\1#i', $richtext, $img_srcs ); // matches src="text" and src='text' 345 346 // <img>. 347 if ( ! empty( $img_srcs[2] ) ) { 348 $img_srcs[2] = array_unique( $img_srcs[2] ); 349 350 foreach ( $img_srcs[2] as $image_src ) { 351 // Skip data URIs. 352 if ( strtolower( substr( $image_src, 0, 5 ) ) === 'data:' ) { 353 continue; 354 } 355 356 $image_src = esc_url_raw( $image_src ); 357 if ( ! $image_src ) { 358 continue; 359 } 360 361 $media['images'][] = array( 362 'source' => 'html', 363 'url' => $image_src, 364 365 // The image resolution isn't available, but we need to set the keys anyway. 366 'height' => 0, 367 'width' => 0, 368 ); 369 } 370 } 371 } 372 373 // Galleries. 374 if ( ! empty( $galleries ) ) { 375 foreach ( $galleries as $gallery ) { 376 foreach ( $gallery as $image ) { 377 $image_url = esc_url_raw( $image['url'] ); 378 if ( ! $image_url ) { 379 continue; 380 } 381 382 $media['images'][] = array( 383 'gallery_id' => $image['gallery_id'], 384 'source' => 'galleries', 385 'url' => $image_url, 386 'width' => $image['width'], 387 'height' => $image['height'], 388 ); 389 } 390 } 391 392 $media['has']['galleries'] = count( $galleries ); 393 } 394 395 // Featured images (aka thumbnails). 396 if ( ! empty( $featured_image ) ) { 397 $image_url = esc_url_raw( $featured_image[0] ); 398 399 if ( $image_url ) { 400 $media['images'][] = array( 401 'source' => 'featured_images', 402 'url' => $image_url, 403 'width' => $featured_image[1], 404 'height' => $featured_image[2], 405 ); 406 407 $media['has']['featured_images'] = 1; 408 } 409 } 410 411 // Update image count. 412 $media['has']['images'] = count( $media['images'] ); 413 414 415 /** 416 * Filters images extracted from text. 417 * 418 * @param array $media Extracted images. See {@link BP_Media_Extractor::extract_images()} for format. 419 * @param string $richtext Content to parse. 420 * @param string $plaintext Copy of $richtext without any markup. 421 * @param array $extra_args Bespoke data for a particular extractor. 422 * @since BuddyPress (2.3.0) 423 */ 424 return apply_filters( 'bp_media_extractor_images', $media, $richtext, $plaintext, $extra_args ); 425 } 426 427 /** 428 * Extract shortcodes from text. 429 * 430 * This includes any shortcodes indirectly used by other media extraction types. 431 * For example, [gallery] and [audio]. 432 * 433 * @param string $richtext Content to parse. 434 * @param string $plaintext Sanitized version of the content. 435 * @param array $extra_args Bespoke data for a particular extractor (optional). 436 * @return array { 437 * @type array $has Extracted media counts. { 438 * @type int $shortcodes 439 * } 440 * @type array $shortcodes Extracted shortcodes. { 441 * Array of extracted media. 442 * 443 * @type array $attributes Key/value pairs of the shortcodes attributes (if any). 444 * @type string $content Text wrapped by the shortcode. 445 * @type string $type Shortcode type. 446 * @type string $original The entire shortcode. 447 * } 448 * } 449 * @since BuddyPress (2.3.0) 450 */ 451 protected function extract_shortcodes( $richtext, $plaintext, $extra_args = array() ) { 452 $data = array( 'has' => array( 'shortcodes' => 0 ), 'shortcodes' => array() ); 453 454 // Match any registered WordPress shortcodes. 455 if ( strpos( $richtext, '[' ) !== false ) { 456 preg_match_all( '/' . get_shortcode_regex() . '/s', $richtext, $matches ); 457 458 if ( ! empty( $matches[2] ) ) { 459 foreach ( $matches[2] as $i => $shortcode_name ) { 460 $attrs = shortcode_parse_atts( $matches[3][ $i ] ); 461 $attrs = ( ! $attrs ) ? array() : $attrs; 462 463 $shortcode = array(); 464 $shortcode['attributes'] = $attrs; // Attributes 465 $shortcode['content'] = $matches[5][ $i ]; // Content 466 $shortcode['type'] = $shortcode_name; // Shortcode 467 $shortcode['original'] = $matches[0][ $i ]; // Entire shortcode 468 469 $data['shortcodes'][] = $shortcode; 470 } 471 } 472 } 473 474 $data['has']['shortcodes'] = count( $data['shortcodes'] ); 475 476 /** 477 * Filters shortcodes extracted from text. 478 * 479 * @param array $data Extracted shortcodes. See {@link BP_Media_Extractor::extract_shortcodes()} for format. 480 * @param string $richtext Content to parse. 481 * @param string $plaintext Copy of $richtext without any markup. 482 * @param array $extra_args Bespoke data for a particular extractor. 483 * @since BuddyPress (2.3.0) 484 */ 485 return apply_filters( 'bp_media_extractor_shortcodes', $data, $richtext, $plaintext, $extra_args ); 486 } 487 488 /** 489 * Extract any URL, matching a registered oEmbed endpoint, from text. 490 * 491 * @param string $richtext Content to parse. 492 * @param string $plaintext Sanitized version of the content. 493 * @param array $extra_args Bespoke data for a particular extractor (optional). 494 * @return array { 495 * @type array $has Extracted media counts. { 496 * @type int $embeds 497 * } 498 * @type array $embeds Extracted oEmbeds. { 499 * Array of extracted media. 500 * 501 * @type string $url oEmbed link. 502 * } 503 * } 504 * @since BuddyPress (2.3.0) 505 */ 506 protected function extract_embeds( $richtext, $plaintext, $extra_args = array() ) { 507 $data = array( 'has' => array( 'embeds' => 0 ), 'embeds' => array() ); 508 $embeds = array(); 509 510 if ( ! function_exists( '_wp_oembed_get_object' ) ) { 511 require( ABSPATH . WPINC . '/class-oembed.php' ); 512 } 513 514 515 // Matches any links on their own lines. They may be oEmbeds. 516 if ( stripos( $richtext, 'http' ) !== false ) { 517 preg_match_all( '#^\s*(https?://[^\s"]+)\s*$#im', $richtext, $matches ); 518 519 if ( ! empty( $matches[1] ) ) { 520 $matches[1] = array_unique( $matches[1] ); 521 $oembed = _wp_oembed_get_object(); 522 523 foreach ( $matches[1] as $link ) { 524 // Skip data URIs. 525 if ( strtolower( substr( $link, 0, 5 ) ) === 'data:' ) { 526 continue; 527 } 528 529 foreach ( $oembed->providers as $matchmask => $oembed_data ) { 530 list( , $is_regex ) = $oembed_data; 531 532 // Turn asterisk-type provider URLs into regexs. 533 if ( ! $is_regex ) { 534 $matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i'; 535 $matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask ); 536 } 537 538 // Check whether this "link" is really an oEmbed. 539 if ( preg_match( $matchmask, $link ) ) { 540 $data['embeds'][] = array( 'url' => $link ); 541 542 break; 543 } 544 } 545 } 546 } 547 } 548 549 $data['has']['embeds'] = count( $data['embeds'] ); 550 551 /** 552 * Filters embeds extracted from text. 553 * 554 * @param array $data Extracted embeds. See {@link BP_Media_Extractor::extract_embeds()} for format. 555 * @param string $richtext Content to parse. 556 * @param string $plaintext Copy of $richtext without any markup. 557 * @param array $extra_args Bespoke data for a particular extractor. 558 * @since BuddyPress (2.3.0) 559 */ 560 return apply_filters( 'bp_media_extractor_embeds', $data, $richtext, $plaintext, $extra_args ); 561 } 562 563 /** 564 * Extract [audio] shortcodes and `<a href="*.mp3">` tags, from text. 565 * 566 * @param string $richtext Content to parse. 567 * @param string $plaintext Sanitized version of the content. 568 * @param array $extra_args Bespoke data for a particular extractor (optional). 569 * @return array { 570 * @type array $has Extracted media counts. { 571 * @type int $audio 572 * } 573 * @type array $audio Extracted audio. { 574 * Array of extracted media. 575 * 576 * @type string $original The entire shortcode. 577 * @type string $source Media source. Either "html" or "shortcodes". 578 * @type string $url Link to audio. 579 * } 580 * } 581 * @see wp_get_audio_extensions() for supported audio formats. 582 * @since BuddyPress (2.3.0) 583 */ 584 protected function extract_audio( $richtext, $plaintext, $extra_args = array() ) { 585 $data = array( 'has' => array( 'audio' => 0 ), 'audio' => array() ); 586 $audios = $this->extract_shortcodes( $richtext, $plaintext, $extra_args ); 587 $links = $this->extract_links( $richtext, $plaintext, $extra_args ); 588 589 $audio_types = wp_get_audio_extensions(); 590 591 592 // [audio] 593 $audios = wp_list_filter( $audios['shortcodes'], array( 'type' => 'audio' ) ); 594 foreach ( $audios as $audio ) { 595 596 // Media URL can appear as the first parameter inside the shortcode brackets. 597 if ( isset( $audio['attributes']['src'] ) ) { 598 $src_param = 'src'; 599 } elseif ( isset( $audio['attributes'][0] ) ) { 600 $src_param = 0; 601 } else { 602 continue; 603 } 604 605 $path = untrailingslashit( parse_url( $audio['attributes'][ $src_param ], PHP_URL_PATH ) ); 606 607 foreach ( $audio_types as $extension ) { 608 $extension = '.' . $extension; 609 610 // Check this URL's file extension matches that of an accepted audio format. 611 if ( ! $path || substr( $path, -4 ) !== $extension ) { 612 continue; 613 } 614 615 $data['audio'][] = array( 616 'original' => '[audio src="' . esc_url_raw( $audio['attributes'][ $src_param ] ) . '"]', 617 'source' => 'shortcodes', 618 'url' => esc_url_raw( $audio['attributes'][ $src_param ] ), 619 ); 620 } 621 } 622 623 // <a href="*.mp3"> tags 624 foreach ( $audio_types as $extension ) { 625 $extension = '.' . $extension; 626 627 foreach ( $links['links'] as $link ) { 628 $path = untrailingslashit( parse_url( $link['url'], PHP_URL_PATH ) ); 629 630 // Check this URL's file extension matches that of an accepted audio format. 631 if ( ! $path || substr( $path, -4 ) !== $extension ) { 632 continue; 633 } 634 635 $data['audio'][] = array( 636 'original' => '[audio src="' . esc_url_raw( $link['url'] ) . '"]', // Build an audio shortcode. 637 'source' => 'html', 638 'url' => esc_url_raw( $link['url'] ), 639 ); 640 } 641 } 642 643 $data['has']['audio'] = count( $data['audio'] ); 644 645 /** 646 * Filters audio extracted from text. 647 * 648 * @param array $data Extracted audio. See {@link BP_Media_Extractor::extract_audio()} for format. 649 * @param string $richtext Content to parse. 650 * @param string $plaintext Copy of $richtext without any markup. 651 * @param array $extra_args Bespoke data for a particular extractor. 652 * @since BuddyPress (2.3.0) 653 */ 654 return apply_filters( 'bp_media_extractor_audio', $data, $richtext, $plaintext, $extra_args ); 655 } 656 657 /** 658 * Extract [video] shortcodes from text. 659 * 660 * @param string $richtext Content to parse. 661 * @param string $plaintext Sanitized version of the content. 662 * @param array $extra_args Bespoke data for a particular extractor (optional). 663 * @return array { 664 * @type array $has Extracted media counts. { 665 * @type int $video 666 * } 667 * @type array $videos Extracted video. { 668 * Array of extracted media. 669 * 670 * @type string $source Media source. Currently only "shortcodes". 671 * @type string $url Link to audio. 672 * } 673 * } 674 * @see wp_get_video_extensions() for supported video formats. 675 * @since BuddyPress (2.3.0) 676 */ 677 protected function extract_video( $richtext, $plaintext, $extra_args = array() ) { 678 $data = array( 'has' => array( 'videos' => 0 ), 'videos' => array() ); 679 $videos = $this->extract_shortcodes( $richtext, $plaintext, $extra_args ); 680 681 $video_types = wp_get_video_extensions(); 682 683 684 // [video] 685 $videos = wp_list_filter( $videos['shortcodes'], array( 'type' => 'video' ) ); 686 foreach ( $videos as $video ) { 687 688 // Media URL can appear as the first parameter inside the shortcode brackets. 689 if ( isset( $video['attributes']['src'] ) ) { 690 $src_param = 'src'; 691 } elseif ( isset( $video['attributes'][0] ) ) { 692 $src_param = 0; 693 } else { 694 continue; 695 } 696 697 $path = untrailingslashit( parse_url( $video['attributes'][ $src_param ], PHP_URL_PATH ) ); 698 699 foreach ( $video_types as $extension ) { 700 $extension = '.' . $extension; 701 702 // Check this URL's file extension matches that of an accepted video format (-5 for webm). 703 if ( ! $path || ( substr( $path, -4 ) !== $extension && substr( $path, -5 ) !== $extension ) ) { 704 continue; 705 } 706 707 $data['videos'][] = array( 708 'original' => $video['original'], // Entire shortcode. 709 'source' => 'shortcodes', 710 'url' => esc_url_raw( $video['attributes'][ $src_param ] ), 711 ); 712 } 713 } 714 715 $data['has']['videos'] = count( $data['videos'] ); 716 717 /** 718 * Filters videos extracted from text. 719 * 720 * @param array $data Extracted videos. See {@link BP_Media_Extractor::extract_videos()} for format. 721 * @param string $richtext Content to parse. 722 * @param string $plaintext Copy of $richtext without any markup. 723 * @param array $extra_args Bespoke data for a particular extractor. 724 * @since BuddyPress (2.3.0) 725 */ 726 return apply_filters( 'bp_media_extractor_videos', $data, $richtext, $plaintext, $extra_args ); 727 } 728 729 730 /** 731 * Helpers and utility methods. 732 */ 733 734 /** 735 * Extract images in [galleries] shortcodes from text. 736 * 737 * @param string $richtext Content to parse. 738 * @param string $plaintext Sanitized version of the content. 739 * @param array $extra_args Bespoke data for a particular extractor (optional). 740 * @return array 741 * @since BuddyPress (2.3.0) 742 */ 743 protected function extract_images_from_galleries( $richtext, $plaintext, $extra_args = array() ) { 744 if ( ! isset( $extra_args['post'] ) || ! is_a( $extra_args['post'], 'WP_Post' ) ) { 745 $post = new WP_Post( (object) array( 'post_content' => $richtext ) ); 746 } else { 747 $post = $extra_args['post']; 748 } 749 750 // We're not using get_post_galleries_images() because it returns thumbnails; we want the original image. 751 $galleries = get_post_galleries( $post, false ); 752 $galleries_data = array(); 753 754 if ( ! empty( $galleries ) ) { 755 // Validate the size of the images requested. 756 if ( isset( $extra_args['width'] ) ) { 757 758 // A width was specified but not a height, so calculate it assuming a 4:3 ratio. 759 if ( ! isset( $extra_args['height'] ) && ctype_digit( $extra_args['width'] ) ) { 760 $extra_args['height'] = round( ( $extra_args['width'] / 4 ) * 3 ); 761 } 762 763 if ( ctype_digit( $extra_args['width'] ) ) { 764 $image_size = array( $extra_args['width'], $extra_args['height'] ); 765 } else { 766 $image_size = $extra_args['width']; // e.g. "thumb", "medium". 767 } 768 769 } else { 770 $image_size = 'full'; 771 } 772 773 /** 774 * There are two variants of gallery shortcode. 775 * 776 * One kind specifies the image (post) IDs via an `ids` parameter. 777 * The other gets the image IDs from post_type=attachment and post_parent=get_the_ID(). 778 */ 779 780 foreach ( $galleries as $gallery_id => $gallery ) { 781 $data = array(); 782 $images = array(); 783 784 // Gallery ids= variant. 785 if ( isset( $gallery['ids'] ) ) { 786 $images = wp_parse_id_list( $gallery['ids'] ); 787 788 // Gallery post_parent variant. 789 } elseif ( isset( $extra_args['post'] ) ) { 790 $images = wp_parse_id_list( 791 get_children( array( 792 'fields' => 'ids', 793 'order' => 'ASC', 794 'orderby' => 'menu_order ID', 795 'post_mime_type' => 'image', 796 'post_parent' => $extra_args['post']->ID, 797 'post_status' => 'inherit', 798 'post_type' => 'attachment', 799 ) ) 800 ); 801 } 802 803 // Extract the data we need from each image in this gallery. 804 foreach ( $images as $image_id ) { 805 $image = wp_get_attachment_image_src( $image_id, $image_size ); 806 $data[] = array( 807 'url' => $image[0], 808 'width' => $image[1], 809 'height' => $image[2], 810 811 'gallery_id' => 1 + $gallery_id, 812 ); 813 } 814 815 $galleries_data[] = $data; 816 } 817 } 818 819 /** 820 * Filters image galleries extracted from text. 821 * 822 * @param array $galleries_data Galleries. See {@link BP_Media_Extractor::extract_images_from_galleries()}. 823 * @param string $richtext Content to parse. 824 * @param string $plaintext Copy of $richtext without any markup. 825 * @param array $extra_args Bespoke data for a particular extractor. 826 * @since BuddyPress (2.3.0) 827 */ 828 return apply_filters( 'bp_media_extractor_galleries', $galleries_data, $richtext, $plaintext, $extra_args ); 829 } 830 831 /** 832 * Extract the featured image from a Post. 833 * 834 * @param string $richtext Content to parse. 835 * @param string $plaintext Sanitized version of the content. 836 * @param array $extra_args Contains data that an implementation might need beyond the defaults. 837 * @return array 838 * @since BuddyPress (2.3.0) 839 */ 840 protected function extract_images_from_featured_images( $richtext, $plaintext, $extra_args ) { 841 $image = array(); 842 $thumb = 0; 843 844 if ( isset( $extra_args['post'] ) ) { 845 $thumb = (int) get_post_thumbnail_id( $extra_args['post']->ID ); 846 } 847 848 if ( $thumb ) { 849 // Validate the size of the images requested. 850 if ( isset( $extra_args['width'] ) ) { 851 if ( ! isset( $extra_args['height'] ) && ctype_digit( $extra_args['width'] ) ) { 852 // A width was specified but not a height, so calculate it assuming a 4:3 ratio. 853 $extra_args['height'] = round( ( $extra_args['width'] / 4 ) * 3 ); 854 } 855 856 if ( ctype_digit( $extra_args['width'] ) ) { 857 $image_size = array( $extra_args['width'], $extra_args['height'] ); 858 } else { 859 $image_size = $extra_args['width']; // e.g. "thumb", "medium". 860 } 861 } else { 862 $image_size = 'full'; 863 } 864 865 $image = wp_get_attachment_image_src( $thumb, $image_size ); 866 } 867 868 /** 869 * Filters featured images extracted from a WordPress Post. 870 * 871 * @param array $image Extracted images. See {@link BP_Media_Extractor_Post::extract_images()} for format. 872 * @param string $richtext Content to parse. 873 * @param string $plaintext Copy of $richtext without any markup. 874 * @param array $extra_args Bespoke data for a particular extractor. 875 * @since BuddyPress (2.3.0) 876 */ 877 return apply_filters( 'bp_media_extractor_featured_images', $image, $richtext, $plaintext, $extra_args ); 878 } 879 880 /** 881 * Sanitize and format raw content to prepare for content extraction. 882 * 883 * HTML tags and shortcodes are removed, and HTML entities are decoded. 884 * 885 * @param string $richtext 886 * @return string 887 * @since BuddyPress (2.3.0) 888 */ 889 protected function strip_markup( $richtext ) { 890 $plaintext = strip_shortcodes( html_entity_decode( strip_tags( $richtext ) ) ); 891 892 /** 893 * Filters the generated plain text version of the content passed to the extractor. 894 * 895 * @param array $plaintext Generated plain text. 896 * @param string $richtext Original content 897 * @since BuddyPress (2.3.0) 898 */ 899 return apply_filters( 'bp_media_extractor_strip_markup', $plaintext, $richtext ); 900 } 901 } 902 No newline at end of file -
new file tests/phpunit/testcases/core/class-bp-media-extractor.php
diff --git a/tests/phpunit/testcases/core/class-bp-media-extractor.php b/tests/phpunit/testcases/core/class-bp-media-extractor.php new file mode 100644 index 0000000..f791abf
- + 1 <?php 2 /** 3 * @group core 4 * @group BP_Media_Extractor 5 */ 6 class BP_Tests_Media_Extractor extends BP_UnitTestCase { 7 public static $media_extractor = null; 8 public static $richtext = ''; 9 10 11 public static function setUpBeforeClass() { 12 parent::setUpBeforeClass(); 13 14 self::$media_extractor = new BP_Media_Extractor(); 15 self::$richtext = "Hello world. 16 17 This sample text is used to test the media extractor parsing class. @paulgibbs thinks it's pretty cool. 18 Another thing really cool is this @youtube: 19 20 https://www.youtube.com/watch?v=2mjvfnUAfyo 21 22 This video is literally out of the world, but uses a different protocol to the embed above: 23 24 http://www.youtube.com/watch?v=KaOC9danxNo 25 26 <a href='https://example.com'>Testing a regular link.</a> 27 <strong>But we should throw in some markup and maybe even an <img src='http://example.com/image.gif'>. 28 <a href='http://example.com'><img src='http://example.com/image-in-a-link.gif' /></a></strong>. 29 It definitely does not like <img src='data:1234567890A'>data URIs</img>. @ 30 31 The parser only extracts wp_allowed_protocols() protocols, not something like <a href='phone:004400'>phone</a>. 32 33 [caption id='example']Here is a caption shortcode.[/caption] 34 35 There are two types of [gallery] shortcodes; one like that, and another with IDs specified. 36 37 Audio shortcodes: 38 [audio src='http://example.com/source.mp3'] 39 [audio src='http://example.com/source.wav' loop='on' autoplay='off' preload='metadata']. 40 41 The following shortcode should be picked up by the shortcode extractor, but not the audio extractor, because 42 it has an unrecognised file extension (for an audio file). [audio src='http://example.com/not_audio.gif'] 43 <a href='http://example.com/more_audio.mp3'>This should be picked up, too</a>. 44 45 Video shortcodes: 46 [video src='http://example.com/source.ogv'] 47 [video src='http://example.com/source.webm' loop='on' autoplay='off' preload='metadata'] 48 49 The following shortcode should be picked up by the shortcode extractor, but not the video extractor, because 50 it has an unrecognised file extension (for a video file). [video src='http://example.com/not_video.mp3'] 51 "; 52 } 53 54 public function setUp() { 55 parent::setUp(); 56 57 $this->factory->user->create( array( 'user_login' => 'paulgibbs' ) ); 58 } 59 60 public function tearDown() { 61 parent::tearDown(); 62 63 $this->remove_added_uploads(); 64 } 65 66 67 /** 68 * General. 69 */ 70 71 public function test_check_media_extraction_return_types() { 72 $media = self::$media_extractor->extract( self::$richtext ); 73 74 foreach ( array( 'has', 'embeds', 'images', 'links', 'mentions', 'shortcodes', 'audio' ) as $key ) { 75 $this->assertArrayHasKey( $key, $media ); 76 $this->assertInternalType( 'array', $media[ $key ] ); 77 } 78 79 foreach ( $media['has'] as $item ) { 80 $this->assertInternalType( 'int', $item ); 81 } 82 83 foreach ( $media['links'] as $item ) { 84 $this->assertArrayHasKey( 'url', $item ); 85 $this->assertInternalType( 'string', $item['url'] ); 86 $this->assertNotEmpty( $item['url'] ); 87 } 88 89 foreach ( $media['mentions'] as $item ) { 90 $this->assertArrayHasKey( 'name', $item ); 91 $this->assertInternalType( 'string', $item['name'] ); 92 $this->assertNotEmpty( $item['name'] ); 93 } 94 95 foreach ( $media['images'] as $item ) { 96 $this->assertArrayHasKey( 'height', $item ); 97 $this->assertInternalType( 'int', $item['height'] ); 98 99 $this->assertArrayHasKey( 'width', $item ); 100 $this->assertInternalType( 'int', $item['width'] ); 101 102 $this->assertArrayHasKey( 'source', $item ); 103 $this->assertInternalType( 'string', $item['source'] ); 104 $this->assertNotEmpty( $item['source'] ); 105 106 $this->assertArrayHasKey( 'url', $item ); 107 $this->assertInternalType( 'string', $item['url'] ); 108 $this->assertNotEmpty( $item['url'] ); 109 } 110 111 foreach ( $media['shortcodes'] as $shortcode_type => $item ) { 112 $this->assertArrayHasKey( 'attributes', $item ); 113 $this->assertInternalType( 'array', $item['attributes'] ); 114 115 $this->assertArrayHasKey( 'content', $item ); 116 $this->assertInternalType( 'string', $item['content'] ); 117 118 $this->assertArrayHasKey( 'type', $item ); 119 $this->assertInternalType( 'string', $item['type'] ); 120 121 $this->assertArrayHasKey( 'original', $item ); 122 $this->assertInternalType( 'string', $item['original'] ); 123 } 124 125 foreach ( $media['embeds'] as $item ) { 126 $this->assertArrayHasKey( 'url', $item ); 127 $this->assertInternalType( 'string', $item['url'] ); 128 $this->assertNotEmpty( $item['url'] ); 129 } 130 131 foreach ( $media['audio'] as $item ) { 132 $this->assertArrayHasKey( 'url', $item ); 133 $this->assertInternalType( 'string', $item['url'] ); 134 $this->assertNotEmpty( $item['url'] ); 135 136 $this->assertArrayHasKey( 'source', $item ); 137 $this->assertInternalType( 'string', $item['source'] ); 138 $this->assertNotEmpty( $item['source'] ); 139 } 140 } 141 142 public function test_check_media_extraction_counts_are_correct() { 143 $media = self::$media_extractor->extract( self::$richtext ); 144 $types = array_keys( $media ); 145 146 foreach ( $types as $type ) { 147 if ( $type === 'has' ) { 148 continue; 149 } 150 151 $this->assertArrayHasKey( $type, $media['has'] ); 152 $this->assertSame( count( $media[ $type ] ), $media['has'][ $type ], "Difference with the 'has' count for {$type}." ); 153 } 154 } 155 156 157 public function test_extract_multiple_media_types_from_content() { 158 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::LINKS | BP_Media_Extractor::MENTIONS ); 159 160 $this->assertNotEmpty( $media['links'] ); 161 $this->assertNotEmpty( $media['mentions'] ); 162 $this->assertArrayNotHasKey( 'shortcodes', $media ); 163 } 164 165 public function test_extract_media_from_a_wp_post() { 166 $post_id = $this->factory->post->create( array( 'post_content' => self::$richtext ) ); 167 $media = self::$media_extractor->extract( get_post( $post_id ), BP_Media_Extractor::LINKS ); 168 169 $this->assertArrayHasKey( 'links', $media ); 170 $this->assertSame( 'https://example.com', $media['links'][0]['url'] ); 171 $this->assertSame( 'http://example.com', $media['links'][1]['url'] ); 172 } 173 174 175 /** 176 * Link extraction. 177 */ 178 179 public function test_extract_links_from_content() { 180 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::LINKS ); 181 182 $this->assertArrayHasKey( 'links', $media ); 183 $this->assertSame( 'https://example.com', $media['links'][0]['url'] ); 184 $this->assertSame( 'http://example.com', $media['links'][1]['url'] ); 185 } 186 187 public function test_extract_no_links_from_content_with_invalid_links() { 188 $richtext = "This is some sample text, with links, but not the kinds we want. 189 <a href=''>Empty links should be ignore<a/> and 190 <a href='phone:004400'>weird protocols should be ignored, too</a>. 191 "; 192 193 $media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::LINKS ); 194 $this->assertSame( 0, $media['has']['links'] ); 195 } 196 197 198 /** 199 * at-mentions extraction. 200 */ 201 202 public function test_extract_mentions_from_content_with_activity_enabled() { 203 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::MENTIONS ); 204 205 $this->assertArrayHasKey( 'user_id', $media['mentions'][0] ); 206 $this->assertSame( 'paulgibbs', $media['mentions'][0]['name'] ); 207 } 208 209 public function test_extract_mentions_from_content_with_activity_disabled() { 210 $was_activity_enabled = false; 211 212 // Turn activity off. 213 if ( isset( buddypress()->active_components['activity'] ) ) { 214 unset( buddypress()->active_components['activity'] ); 215 $was_activity_enabled = true; 216 } 217 218 219 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::MENTIONS ); 220 221 $this->assertArrayNotHasKey( 'user_id', $media['mentions'][0] ); 222 $this->assertSame( 'paulgibbs', $media['mentions'][0]['name'] ); 223 224 225 // Turn activity on. 226 if ( $was_activity_enabled ) { 227 buddypress()->active_components['activity'] = 1; 228 } 229 } 230 231 232 /** 233 * Shortcodes extraction. 234 */ 235 236 public function test_extract_shortcodes_from_content() { 237 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::SHORTCODES ); 238 239 $this->assertArrayHasKey( 'shortcodes', $media ); 240 241 $this->assertSame( 'caption', $media['shortcodes'][0]['type'] ); 242 $this->assertSame( 'Here is a caption shortcode.', $media['shortcodes'][0]['content'] ); 243 $this->assertSame( 'example', $media['shortcodes'][0]['attributes']['id'] ); 244 245 $this->assertSame( 'gallery', $media['shortcodes'][1]['type'] ); 246 $this->assertEmpty( $media['shortcodes'][1]['content'] ); 247 248 $this->assertSame( 'audio', $media['shortcodes'][2]['type'] ); 249 $this->assertEmpty( $media['shortcodes'][2]['content'] ); 250 $this->assertSame( 'http://example.com/source.mp3', $media['shortcodes'][2]['attributes']['src'] ); 251 252 $this->assertSame( 'audio', $media['shortcodes'][3]['type'] ); 253 $this->assertEmpty( $media['shortcodes'][3]['content'] ); 254 $this->assertSame( 'http://example.com/source.wav', $media['shortcodes'][3]['attributes']['src'] ); 255 $this->assertSame( 'on', $media['shortcodes'][3]['attributes']['loop'] ); 256 $this->assertSame( 'off', $media['shortcodes'][3]['attributes']['autoplay'] ); 257 $this->assertSame( 'metadata', $media['shortcodes'][3]['attributes']['preload'] ); 258 } 259 260 public function test_extract_no_shortcodes_from_content_with_unregistered_shortcodes() { 261 $richtext = 'This sample text has some made-up [fake]shortcodes[/fake].'; 262 263 $media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::SHORTCODES ); 264 $this->assertSame( 0, $media['has']['shortcodes'] ); 265 } 266 267 268 /** 269 * oEmbeds extraction. 270 */ 271 272 public function test_extract_oembeds_from_content() { 273 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::EMBEDS ); 274 275 $this->assertArrayHasKey( 'embeds', $media ); 276 $this->assertSame( 'https://www.youtube.com/watch?v=2mjvfnUAfyo', $media['embeds'][0]['url'] ); 277 $this->assertSame( 'http://www.youtube.com/watch?v=KaOC9danxNo', $media['embeds'][1]['url'] ); 278 } 279 280 281 /** 282 * Images extraction (src tags). 283 */ 284 285 // both quote styles 286 public function test_extract_images_from_content_with_src_tags() { 287 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::IMAGES ); 288 289 $this->assertArrayHasKey( 'images', $media ); 290 $media = array_values( wp_list_filter( $media['images'], array( 'source' => 'html' ) ) ); 291 292 $this->assertSame( 'http://example.com/image.gif', $media[0]['url'] ); 293 $this->assertSame( 'http://example.com/image-in-a-link.gif', $media[1]['url'] ); 294 } 295 296 // empty src attributes, data: URIs 297 public function test_extract_no_images_from_content_with_invalid_src_tags() { 298 $richtext = 'This sample text will contain images with invalid src tags, like this: 299 <img src="data://abcd"> or <img src="phone://0123" />. 300 '; 301 302 $media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::IMAGES ); 303 304 $this->assertArrayHasKey( 'images', $media ); 305 $this->assertSame( 0, $media['has']['images'] ); 306 } 307 308 309 /** 310 * Images extraction (galleries). 311 */ 312 313 public function test_extract_images_from_content_with_galleries_variant_no_ids() { 314 // To test the [gallery] shortcode, we need to create a post and an attachment. 315 $post_id = $this->factory->post->create( array( 'post_content' => self::$richtext ) ); 316 $attachment_id = $this->factory->attachment->create_object( 'image.jpg', $post_id, array( 317 'post_mime_type' => 'image/jpeg', 318 'post_type' => 'attachment' 319 ) ); 320 wp_update_attachment_metadata( $attachment_id, array( 'width' => 100, 'height' => 100 ) ); 321 322 323 // Extract the gallery images. 324 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::IMAGES, array( 325 'post' => get_post( $post_id ), 326 ) ); 327 328 $this->assertArrayHasKey( 'images', $media ); 329 $media = array_values( wp_list_filter( $media['images'], array( 'source' => 'galleries' ) ) ); 330 $this->assertCount( 1, $media ); 331 332 $this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/image.jpg', $media[0]['url'] ); 333 } 334 335 public function test_extract_images_from_content_with_galleries_variant_ids() { 336 // To test the [gallery] shortcode, we need to create a post and attachments. 337 $attachment_ids = array(); 338 foreach ( range( 1, 3 ) as $i ) { 339 $attachment_id = $this->factory->attachment->create_object( "image{$i}.jpg", 0, array( 340 'post_mime_type' => 'image/jpeg', 341 'post_type' => 'attachment' 342 ) ); 343 344 wp_update_attachment_metadata( $attachment_id, array( 'width' => 100, 'height' => 100 ) ); 345 $attachment_ids[] = $attachment_id; 346 } 347 348 $attachment_ids = join( ',', $attachment_ids ); 349 $post_id = $this->factory->post->create( array( 'post_content' => "[gallery ids='{$attachment_ids}']" ) ); 350 351 352 // Extract the gallery images. 353 $media = self::$media_extractor->extract( '', BP_Media_Extractor::IMAGES, array( 354 'post' => get_post( $post_id ), 355 ) ); 356 357 $this->assertArrayHasKey( 'images', $media ); 358 $media = array_values( wp_list_filter( $media['images'], array( 'source' => 'galleries' ) ) ); 359 $this->assertCount( 3, $media ); 360 361 for ( $i = 1; $i <= 3; $i++ ) { 362 $this->assertSame( 'http://' . WP_TESTS_DOMAIN . "/wp-content/uploads/image{$i}.jpg", $media[ $i - 1 ]['url'] ); 363 } 364 } 365 366 public function test_extract_no_images_from_content_with_invalid_galleries_variant_no_ids() { 367 $post_id = $this->factory->post->create( array( 'post_content' => self::$richtext ) ); 368 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::IMAGES, array( 369 'post' => get_post( $post_id ), 370 ) ); 371 372 $this->assertArrayHasKey( 'images', $media ); 373 $media = array_values( wp_list_filter( $media['images'], array( 'source' => 'galleries' ) ) ); 374 $this->assertCount( 0, $media ); 375 } 376 377 public function test_extract_no_images_from_content_with_invalid_galleries_variant_ids() { 378 $post_id = $this->factory->post->create( array( 'post_content' => '[gallery ids="117,4529"]' ) ); 379 $media = self::$media_extractor->extract( '', BP_Media_Extractor::IMAGES, array( 380 'post' => get_post( $post_id ), 381 ) ); 382 383 $this->assertArrayHasKey( 'images', $media ); 384 $media = array_values( wp_list_filter( $media['images'], array( 'source' => 'galleries' ) ) ); 385 $this->assertCount( 0, $media ); 386 } 387 388 389 /** 390 * Images extraction (thumbnail). 391 */ 392 393 public function test_extract_no_images_from_content_with_featured_image() { 394 $post_id = $this->factory->post->create( array( 'post_content' => self::$richtext ) ); 395 $thumbnail_id = $this->factory->attachment->create_object( 'image.jpg', $post_id, array( 396 'post_mime_type' => 'image/jpeg', 397 'post_type' => 'attachment' 398 ) ); 399 set_post_thumbnail( $post_id, $thumbnail_id ); 400 401 402 // Extract the gallery images. 403 $media = self::$media_extractor->extract( '', BP_Media_Extractor::IMAGES, array( 404 'post' => get_post( $post_id ), 405 ) ); 406 407 $this->assertArrayHasKey( 'images', $media ); 408 $media = array_values( wp_list_filter( $media['images'], array( 'source' => 'featured_images' ) ) ); 409 $this->assertCount( 1, $media ); 410 411 $this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/image.jpg', $media[0]['url'] ); 412 } 413 414 public function test_extract_images_from_content_without_featured_image() { 415 $post_id = $this->factory->post->create( array( 'post_content' => self::$richtext ) ); 416 $media = self::$media_extractor->extract( '', BP_Media_Extractor::IMAGES, array( 417 'post' => get_post( $post_id ), 418 ) ); 419 420 $this->assertArrayHasKey( 'images', $media ); 421 $media = array_values( wp_list_filter( $media['images'], array( 'source' => 'featured_images' ) ) ); 422 $this->assertCount( 0, $media ); 423 } 424 425 426 /** 427 * Audio extraction. 428 */ 429 430 public function test_extract_audio_from_content() { 431 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::AUDIO ); 432 433 $this->assertArrayHasKey( 'audio', $media ); 434 $this->assertCount( 3, $media['audio'] ); 435 436 $this->assertSame( 'shortcodes', $media['audio'][0]['source'] ); 437 $this->assertSame( 'shortcodes', $media['audio'][1]['source'] ); 438 $this->assertSame( 'html', $media['audio'][2]['source'] ); 439 440 $this->assertSame( 'http://example.com/source.mp3', $media['audio'][0]['url'] ); 441 $this->assertSame( 'http://example.com/source.wav', $media['audio'][1]['url'] ); 442 $this->assertSame( 'http://example.com/more_audio.mp3', $media['audio'][2]['url'] ); 443 } 444 445 public function test_extract_audio_shortcode_with_no_src_param() { 446 $richtext = '[audio http://example.com/a-song.mp3]'; 447 $media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::AUDIO ); 448 449 $this->assertArrayHasKey( 'audio', $media ); 450 $this->assertCount( 1, $media['audio'] ); 451 $this->assertSame( 'http://example.com/a-song.mp3', $media['audio'][0]['url'] ); 452 } 453 454 public function test_extract_no_audio_from_invalid_content() { 455 $richtext = '[audio src="http://example.com/not_audio.gif"] 456 <a href="http://example.com/more_not_audio.mp33">Hello</a>.'; 457 458 $media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::AUDIO ); 459 $this->assertSame( 0, $media['has']['audio'] ); 460 } 461 462 public function test_extract_no_audio_from_empty_audio_shortcode() { 463 $media = self::$media_extractor->extract( '[audio]', BP_Media_Extractor::AUDIO ); 464 $this->assertSame( 0, $media['has']['audio'] ); 465 } 466 467 468 /** 469 * Video extraction. 470 */ 471 472 public function test_extract_video_from_content() { 473 $media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::VIDEOS ); 474 475 $this->assertArrayHasKey( 'videos', $media ); 476 $this->assertCount( 2, $media['videos'] ); 477 478 $this->assertSame( 'shortcodes', $media['videos'][0]['source'] ); 479 $this->assertSame( 'shortcodes', $media['videos'][1]['source'] ); 480 481 $this->assertSame( 'http://example.com/source.ogv', $media['videos'][0]['url'] ); 482 $this->assertSame( 'http://example.com/source.webm', $media['videos'][1]['url'] ); 483 } 484 485 486 public function test_extract_video_shortcode_with_no_src_param() { 487 $richtext = '[video http://example.com/source.ogv]'; 488 $media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::VIDEOS ); 489 490 $this->assertArrayHasKey( 'videos', $media ); 491 $this->assertCount( 1, $media['videos'] ); 492 $this->assertSame( 'http://example.com/source.ogv', $media['videos'][0]['url'] ); 493 } 494 495 public function test_extract_no_video_from_invalid_content() { 496 $richtext = '[video src="http://example.com/not_video.mp3"]'; 497 $media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::VIDEOS ); 498 499 $this->assertSame( 0, $media['has']['videos'] ); 500 } 501 502 public function test_extract_no_videos_from_empty_video_shortcodes() { 503 $media = self::$media_extractor->extract( '[video]', BP_Media_Extractor::VIDEOS ); 504 $this->assertSame( 0, $media['has']['videos'] ); 505 } 506 }