6

The goal is to grab the first video (embed or shortcode) from the post. So we need to check post content if it has embedded videos ( supported by WP ) or [video] shortcodes. Multiple videos and embeds could exist in the single post. If found any - return the very first of them, not depending on order of given patterns to match, but on order they have been inserted into the post.

Here is my progress so far...

This one returns first [video] shortcode. Is there a way to make it look for not only video shortcodes, but for embeds also?

function theme_self_hosted_videos() {
global $post;
$pattern = get_shortcode_regex();
  if ( preg_match_all( '/'. $pattern .'/s', $post->post_content, $matches ) && array_key_exists( 2, $matches ) && in_array( 'video', $matches[2] ) ) {
    $videos = $matches[0];
    $i = 0;
    foreach ($videos as $video ) {
        if($i == 0) {
            echo do_shortcode($video);
        }
        $i++;
    }
  }
  return false;
}
add_action( 'wp', 'theme_self_hosted_videos' );

Here's my current progress of a function to return first video embed from the post. Not working as expected, though. Obviously depends on $pattern_array order and maybe on the patterns themselves...

function theme_oembed_videos() {

global $post;

// Here is a sample array of patterns for supported video embeds from wp-includes/class-wp-embed.php
$pattern_array = array(
    '#https://youtu\.be/.*#i',
    '#https://(www\.)?youtube\.com/playlist.*#i',
    '#https://(www\.)?youtube\.com/watch.*#i',
    '#http://(www\.)?youtube\.com/watch.*#i',
    '#http://(www\.)?youtube\.com/playlist.*#i',
    '#http://youtu\.be/.*#i',
    '#https?://wordpress.tv/.*#i',
    '#https?://(.+\.)?vimeo\.com/.*#i'
);

foreach ($pattern_array as $pattern) {

    if (preg_match_all($pattern, $post->post_content, $matches)) {
        return wp_oembed_get( $matches[0] );
    }
    return false;

}

}

3 Answers 3

2

You could try mashing all the patterns together in one big regexp or and then doing a simple line by line parse of the content:

function theme_oembed_videos() {

    global $post;

    if ( $post && $post->post_content ) {

        global $shortcode_tags;
        // Make a copy of global shortcode tags - we'll temporarily overwrite it.
        $theme_shortcode_tags = $shortcode_tags;

        // The shortcodes we're interested in.
        $shortcode_tags = array(
            'video' => $theme_shortcode_tags['video'],
            'embed' => $theme_shortcode_tags['embed']
        );
        // Get the absurd shortcode regexp.
        $video_regex = '#' . get_shortcode_regex() . '#i';

        // Restore global shortcode tags.
        $shortcode_tags = $theme_shortcode_tags;

        $pattern_array = array( $video_regex );

        // Get the patterns from the embed object.
        if ( ! function_exists( '_wp_oembed_get_object' ) ) {
            include ABSPATH . WPINC . '/class-oembed.php';
        }
        $oembed = _wp_oembed_get_object();
        $pattern_array = array_merge( $pattern_array, array_keys( $oembed->providers ) );

        // Or all the patterns together.
        $pattern = '#(' . array_reduce( $pattern_array, function ( $carry, $item ) {
            if ( strpos( $item, '#' ) === 0 ) {
                // Assuming '#...#i' regexps.
                $item = substr( $item, 1, -2 );
            } else {
                // Assuming glob patterns.
                $item = str_replace( '*', '(.+)', $item );
            }
            return $carry ? $carry . ')|('  . $item : $item;
        } ) . ')#is';

        // Simplistic parse of content line by line.
        $lines = explode( "\n", $post->post_content );
        foreach ( $lines as $line ) {
            $line = trim( $line );
            if ( preg_match( $pattern, $line, $matches ) ) {
                if ( strpos( $matches[0], '[' ) === 0 ) {
                    $ret = do_shortcode( $matches[0] );
                } else {
                    $ret = wp_oembed_get( $matches[0] );
                }
                return $ret;
            }
        }
    }
}

Also you might want to use transients for the embeds - see this answer I gave.

1
  • It seems like this solution works, @bonger. Thanks!
    – Nikita
    Jan 24, 2015 at 19:08
15

You can use the get_media_embedded_in_content() function for both shortcode and embedded media (it looks for these HTML tags: audio, video, object, embed, or iframe), just be sure to use content with applied filters and shortcode executed.

For example:

$post_id = 125;
$post = get_post($post_id);

//Get the content, apply filters and execute shortcodes
$content = apply_filters( 'the_content', $post->post_content );
$embeds = get_media_embedded_in_content( $content );

//$embeds is an array and each item is the HTML of an embedded media.
//The first item of the array is the first embedded media in the content
$fist_embedded = $embeds[0];

Addapting te above code to a function similar to yours:

function get_first_embed_media($post_id) {

    $post = get_post($post_id);
    $content = do_shortcode( apply_filters( 'the_content', $post->post_content ) );
    $embeds = get_media_embedded_in_content( $content );

    if( !empty($embeds) ) {
        //return first embed
        return $embeds[0];

    } else {
        //No embeds found
        return false;
    }

}

To limit videos only you could do something like this:

function get_first_embed_media($post_id) {

    $post = get_post($post_id);
    $content = do_shortcode( apply_filters( 'the_content', $post->post_content ) );
    $embeds = get_media_embedded_in_content( $content );

    if( !empty($embeds) ) {
        //check what is the first embed containg video tag, youtube or vimeo
        foreach( $embeds as $embed ) {
            if( strpos( $embed, 'video' ) || strpos( $embed, 'youtube' ) || strpos( $embed, 'vimeo' ) ) {
                return $embed;
            }
        }

    } else {
        //No video embedded found
        return false;
    }

}

Note: There was a bug in get_media_embedded_in_content() that made this answer not working as expected bellow WP 4.2.

8
  • thanks for your answer. That could be an elegant solution! However proposed get_media_embedded_in_content has a $types par, which is an array of media types: 'audio', 'video', 'object', 'embed', or 'iframe'. So by default it should return whatever of this types comes first. But it returns not only first media for some posts and always broken html for self-hosted videos. For example a post with youtube and vimeo links and some content between will be returned completely by this function. Even if I specify $types to 'iframe' only. And how can I limit embed media to videos only?
    – Nikita
    Jan 23, 2015 at 17:49
  • I don't understand what you mean. I've been using this for a lot of time and I've never experience broken HTML nor the problem with "between content". Please, edit the question and add a example of content.
    – cybmeta
    Jan 23, 2015 at 19:25
  • Thanks, @cybmeta and sorry for my bad explanation. When returning a video, parent div with wp-video class is missing, that's why the content was broken. Ok, I can wrap it again. Also, even if I do not check each element like you wrote in your last example, but just return the first with return $embeds[0];, I actually get not only first element. Instead, it returns every element found and all content between them.
    – Nikita
    Jan 24, 2015 at 9:42
  • I fully understand now. I've found that the issue is an already known bug in get_tag_regex(),a function used in get_media_embedded_in_content. See bug #26674. I've always used the function in a context where the bug was irrelevant so I've never experienced any issue. I'm sorry but my answer won't work properly.
    – cybmeta
    Jan 24, 2015 at 10:25
  • 1
    Man, you saved not my day but my LIFE. get_media_embedded_in_content() is awesome.
    – Fred K
    Jul 28, 2015 at 22:06
0

Based on @cybmeta answer, improved it a little bit and though I would share

function get_first_embed_media($post_id, $regex_filter = null)
{
    $post = get_post($post_id);
    $content = do_shortcode(apply_filters('the_content', $post->post_content));
    $embeds = get_media_embedded_in_content($content);

    if (!empty($embeds)) {
        // Loop
        foreach ($embeds as $embed) {
            if (
                $regex_filter == null                       ||
                preg_match($regex_filter, $embed)
            ) {
                return $embed;
            }
        }
    }
    
    // No matching embedded found
    return false;
}
$first_video = get_first_embed_media(1234, '/video|youtube|vimeo/')

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.