Skip to:
Content

BuddyPress.org

Ticket #6177: 6177.1.patch

File 6177.1.patch, 62.3 KB (added by DJPaul, 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 ) { 
    386386/**
    387387 * Truncate long activity entries when viewed in activity streams.
    388388 *
     389 * This method can only be used inside the Activity loop.
     390 *
    389391 * @since BuddyPress (1.5.0)
    390392 *
    391393 * @uses bp_is_single_activity()
    function bp_activity_make_nofollow_filter( $text ) { 
    402404function bp_activity_truncate_entry( $text ) {
    403405        global $activities_template;
    404406
     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
    405417        // 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() ) {
    407419                return $text;
     420        }
    408421
    409422        /**
    410423         * 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 
    18691869                'recorded_time'     => $post->post_date_gmt,
    18701870        );
    18711871
    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 
    18771872        if ( ! empty( $activity_args['content'] ) ) {
    18781873                // Create the excerpt.
    1879                 $activity_excerpt = bp_create_excerpt( $activity_args['content'] );
     1874                $activity_summary = bp_activity_create_summary( $activity_args['content'], $activity_args );
    18801875
    18811876                // Backward compatibility filter for blog posts.
    18821877                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 );
    18841879                } else {
    1885                         $activity_args['content'] = $activity_excerpt;
     1880                        $activity_args['content'] = $activity_summary;
    18861881                }
    18871882        }
    18881883
    function bp_activity_post_type_update( $post = null ) { 
    19631958        $activity = new BP_Activity_Activity( $activity_id );
    19641959
    19651960        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 );
    19711962
    19721963                // Backward compatibility filter for the blogs component.
    19731964                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 );
    19751966                } else {
    1976                         $activity->content = $activity_excerpt;
     1967                        $activity->content = $activity_summary;
    19771968                }
    19781969        }
    19791970
    function bp_activity_hide_user_activity( $user_id ) { 
    25852576 * through the content, grabs the first image and converts it to a thumbnail,
    25862577 * and removes the rest of the images from the string.
    25872578 *
     2579 * As of BuddyPress 2.3, this function is no longer in use.
     2580 *
    25882581 * @since BuddyPress (1.2.0)
    25892582 *
    25902583 * @uses esc_attr()
    function bp_activity_thumbnail_content_images( $content, $link = false, $args = 
    26562649}
    26572650
    26582651/**
     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 */
     2663function 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/**
    26592807 * Fetch whether the current user is allowed to mark items as spam.
    26602808 *
    26612809 * @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 = '' ) { 
    352352
    353353        $r = wp_parse_args( $args, $defaults );
    354354
    355         // Remove large images and replace them with just one image thumbnail
    356         if ( ! empty( $r['content'] ) ) {
    357                 $r['content'] = bp_activity_thumbnail_content_images( $r['content'], $r['primary_link'], $r );
    358         }
    359 
    360355        if ( ! empty( $r['action'] ) ) {
    361356
    362357                /**
    function bp_blogs_record_activity( $args = '' ) { 
    376371                 *
    377372                 * @since BuddyPress (1.2.0)
    378373                 *
    379                  * @param string $value Generated excerpt from content for the activity stream.
     374                 * @param string $value Generated summary from content for the activity stream.
    380375                 * @param string $value Content for the activity stream.
    381376                 * @param array  $r     Array of arguments used for the activity stream item.
    382377                 */
    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 );
    384379        }
    385380
    386381        // 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'; 
    2020require dirname( __FILE__ ) . '/classes/class-bp-suggestions.php';
    2121require dirname( __FILE__ ) . '/classes/class-bp-members-suggestions.php';
    2222require dirname( __FILE__ ) . '/classes/class-bp-recursive-query.php';
     23require 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
     10defined( '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 */
     39class 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 */
     6class 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}