: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @author Michael Pratt <yo@michael-pratt.com>
* @link http://www.michael-pratt.com/
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
namespace EmbedPress\Providers;
use Embera\Provider\ProviderAdapter;
use Embera\Provider\ProviderInterface;
* @link https://youtube.com
* @link https://youtube-eng.googleblog.com/2009/10/oembed-support_9.html
class Youtube extends ProviderAdapter implements ProviderInterface {
/** inline {@inheritdoc} */
protected $shouldSendRequest = false;
public static $curltimeout = 30;
/** inline {@inheritdoc} */
protected $endpoint = 'https://www.youtube.com/oembed?format=json&scheme=https';
protected static $channel_endpoint = 'https://www.googleapis.com/youtube/v3/';
/** @var array Array with allowed params for the current Provider */
protected $allowedParams = [ 'maxwidth', 'maxheight', 'pagesize', 'thumbnail', 'gallery', 'hideprivate', 'columns', 'ispagination', 'gapbetweenvideos' ];
/** inline {@inheritdoc} */
protected static $hosts = [
'm.youtube.com', 'youtube.com', 'youtu.be',
/** inline {@inheritdoc} */
protected $httpsSupport = true;
public function getAllowedParams(){
return $this->allowedParams;
/** inline {@inheritdoc} */
public function validateUrl(Url $url) {
return (bool) (preg_match('~\/channel\/|\/c\/|\/user\/|\/@\w+|(?:https?:\/\/)?(?:www\.)?(?:youtube.com\/)(\w+)[^?\/]*$~i', (string) $url));
public function validateTYLiveUrl($url) {
return (bool) (preg_match('~(?:https?:\/\/)?(?:www\.)?(?:youtube.com\/(?:channel|c|user)\/\w+\/live|@\w+\/live)~i', (string) $url));
/** inline {@inheritdoc} */
public function normalizeUrl(Url $url) {
public function isChannel($url = null) {
$channel = $this->getChannel($url);
return !empty($channel['id']);
public function getChannel($url = null) {
$channelId = 'unknown_id'; // temporarily assigned a placeholder value for demonstration purposes
preg_match('~\/(channel|c|user)\/(.+)~i', (string) $url, $matches);
preg_match('~(?:https?:\/\/)?(?:www\.)?(?:youtube.com\/)(\w+)[^?\/]*$~i', (string) $url, $matches);
// $channel___id = $this->get_channel_id_by_handler('adin');
// print_r($channel___id); die;
preg_match('~\/(@)(\w+)~i', (string) $url, $matches);
if(!empty($this->get_youtube_handler($this->url))){
if(!empty($this->get_channel_id_by_handler($this->get_youtube_handler($this->url)))){
$channelId = $this->get_channel_id_by_handler($this->get_youtube_handler($this->url));
"type" => isset($matches[1]) ? $matches[1] : '',
"id" => isset($matches[2]) ? $matches[2] : '',
/** inline {@inheritdoc} */
public function getEndpoint() {
if ($this->isChannel()) {
$apiEndpoint = 'https://www.googleapis.com/youtube/v3/channels';
return (string) $this->endpoint;
protected static function get_api_key() {
$settings = (array) get_option(EMBEDPRESS_PLG_NAME . ':youtube', []);
return !empty($settings['api_key']) ? $settings['api_key'] : '';
protected static function get_pagesize() {
$settings = (array) get_option(EMBEDPRESS_PLG_NAME . ':youtube', []);
return !empty($settings['pagesize']) ? $settings['pagesize'] : '';
/** inline {@inheritdoc} */
public function getParams() {
$params = parent::getParams();
if ($this->isChannel() && self::get_api_key()) {
$channel = $this->getChannel();
$params['part'] = 'contentDetails,snippet';
$params['key'] = self::get_api_key();
if ($channel['type'] == 'c') {
$params['forUsername'] = $channel['id'];
$params['id'] = $channel['id'];
* Builds a valid Oembed query string based on the given parameters,
* Since this method uses the http_build_query function, there is no
* need to pass urlencoded parameters, http_build_query already does
* @param string $endpoint The Url to the Oembed endpoint
* @param array $params Parameters for the query string
protected function constructUrl($endpoint, array $params = array())
$endpoint = self::$channel_endpoint . $endpoint;
return $endpoint . ((strpos($endpoint, '?') === false) ? '?' : '&') . http_build_query(array_filter($params));
public function getStaticResponse() {
'provider_name' => $this->getProviderName(),
"provider_url" => "https://www.youtube.com/",
$params = $this->getParams();
if (preg_match("/^https?:\/\/(?:www\.)?youtube\.com\/channel\/([\w-]+)\/live$/", $this->url, $matches) || $this->validateTYLiveUrl($this->url)) {
$channelId = $matches[1];
if(!empty($this->get_youtube_handler($this->url))){
if(!empty($this->get_channel_id_by_handler($this->get_youtube_handler($this->url)))){
$channelId = $this->get_channel_id_by_handler($this->get_youtube_handler($this->url));
$embedUrl = 'https://www.youtube.com/embed/live_stream?channel='.$channelId.'&feature=oembed';
$attr[] = 'width="'.esc_attr($params['maxheight']).'"';
$attr[] = 'height="'.esc_attr($params['maxheight']).'";';
$attr[] = 'src="' . esc_url($embedUrl) . '"';
$attr[] = 'frameborder="0"';
$attr[] = 'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"';
$attr[] = 'allowfullscreen';
$results['html'] = '<iframe ' . implode(' ', $attr) . '></iframe>';
else if($this->isChannel()){
$channel = $this->getChannelGallery();
$results = array_merge($results, $channel);
public function getChannelPlaylist(){
$channel = $this->getChannel();
$channel_url = $this->constructUrl('channels', $this->getParams());
$transient_key = 'ep_embed_youtube_channel_playlist_id_' . md5($channel_url);
$jsonResult = get_transient($transient_key);
if($channel['type'] == 'user' || $channel['type'] == 'c'){
$this->getChannelIDbyUsername();
$channel_url = $this->constructUrl('channels', $this->getParams());
if (empty(self::get_api_key())) {
$result['html'] = self::get_api_key_error_message();
$apiResult = wp_remote_get($channel_url, array('timeout' => self::$curltimeout));
if (is_wp_error($apiResult)) {
$result['html'] = self::clean_api_error_html($apiResult->get_error_message(), true);
set_transient($transient_key, $result, 10);
$jsonResult = json_decode($apiResult['body']);
if (isset($jsonResult->error)) {
if (isset($jsonResult->error->message)) {
$result['html'] = self::clean_api_error_html($jsonResult->error->message, true);
$result['html'] = self::clean_api_error_html(__('Sorry, there may be an issue with your YouTube API key.', 'embedpress'));
set_transient($transient_key, $result, MINUTE_IN_SECONDS);
elseif(!empty($jsonResult->items[0]->contentDetails->relatedPlaylists->uploads)){
$result['playlistID'] = $jsonResult->items[0]->contentDetails->relatedPlaylists->uploads;
$result['title'] = isset($jsonResult->items[0]->snippet->title) ? $jsonResult->items[0]->snippet->title : '';
set_transient($transient_key, $result, DAY_IN_SECONDS);
public function get_youtube_handler($url){
// preg_match('/^https:\/\/www.youtube.com\/@(.+)\/live$/i', $url, $matches);
preg_match('/^https:\/\/www.youtube.com\/@([^\/?]+)/i', $url, $matches);
$handle_name = $matches[1];
public function getChannelIDbyUsername(){
$apiResult = wp_remote_get($url, array('timeout' => self::$curltimeout));
if (!is_wp_error($apiResult)) {
$channel_html = $apiResult['body'];
preg_match("/<meta\s+itemprop=[\"']channelId[\"']\s+content=[\"'](.*?)[\"']\/?>/", $channel_html, $matches);
$url = "https://www.youtube.com/channel/{$matches[1]}";
$this->url = $this->normalizeUrl(new Url($url));
public function get_channel_id_by_handler($handle)
$transient_key = 'channel_id_' . md5($handle);
$channel_id = get_transient($transient_key);
if (false === $channel_id) {
$channel_handle = "https://www.youtube.com/@{$handle}";
curl_setopt($ch, CURLOPT_URL, $channel_handle);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
return 'cURL error: ' . curl_error($ch);
$pattern = '/(<link rel="canonical" href="https:\/\/www\.youtube\.com\/channel\/)(.{1,50})(">)/';
if (preg_match($pattern, $response, $matches)) {
$channel_id = $matches[2];
set_transient($transient_key, $channel_id, 30 * DAY_IN_SECONDS);
return "Not a channel URL";
/** inline {@inheritdoc} */
public function getChannelGallery() {
$channel = $this->getChannelPlaylist();
if(!empty($channel['error'])){
if (!empty($channel["playlistID"])) {
$params = $this->getParams();
$the_playlist_id = $channel["playlistID"];
$rel = 'https://www.youtube.com/embed?listType=playlist&list=' . esc_attr($the_playlist_id);
$title = $channel['title'];
'playlistId' => $the_playlist_id,
if(!empty($params['pagesize'])){
$gallery_args['pagesize'] = $params['pagesize'];
$gallery = self::get_gallery_page($gallery_args);
if (!empty($gallery->first_vid)) {
$rel = "https://www.youtube.com/embed/{$gallery->first_vid}?feature=oembed";
$main_iframe = "<div class='ep-first-video'><iframe width='{$params['maxwidth']}' height='{$params['maxheight']}' src='$rel' frameborder='0' allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture' allowfullscreen title='{$title}'></iframe></div>";
if($gallery->html && $this->validateTYLiveUrl($this->getUrl())){
$styles = self::styles($params, $this->getUrl());
"html" => "<div class='ep-player-wrap'>$main_iframe $styles</div>",
$styles = self::styles($params, $this->getUrl());
"html" => "<div class='ep-player-wrap'>$main_iframe {$gallery->html} $styles</div>",
elseif ($this->isChannel() && empty(self::get_api_key()) && current_user_can('manage_options')) {
"html" => "<div class='ep-player-wrap'>" . __('Please enter your YouTube API key to embed YouTube Channel.', 'embedpress') . "</div>",
public static function get_gallery_page($options) {
$gallobj = new \stdClass();
$options = wp_parse_args($options, [
'pagesize' => self::get_pagesize() ? self::get_pagesize() : 6,
'apiKey' => self::get_api_key(),
$options['pagesize'] = $options['pagesize'] > 50 ? 50 : $options['pagesize'];
$options['pagesize'] = $options['pagesize'] < 1 ? 1 : $options['pagesize'];
if (empty($options['apiKey'])) {
$gallobj->html = self::get_api_key_error_message();
$apiEndpoint = 'https://www.googleapis.com/youtube/v3/playlistItems?part=snippet,status&playlistId=' . $options['playlistId']
. '&maxResults=' . $options['pagesize']
. '&key=' . $options['apiKey'];
if ($options['pageToken'] != null) {
$apiEndpoint .= '&pageToken=' . $options['pageToken'];
$transient_key = 'ep_embed_youtube_channel_' . md5($apiEndpoint);
$gallobj->transient_key = $transient_key;
$jsonResult = get_transient($transient_key);
if (empty($jsonResult)) {
$apiResult = wp_remote_get($apiEndpoint, array('timeout' => self::$curltimeout));
if (is_wp_error($apiResult)) {
$gallobj->html = self::clean_api_error_html($apiResult->get_error_message(), true);
$jsonResult = json_decode($apiResult['body']);
if (empty($jsonResult->error)) {
set_transient($transient_key, $jsonResult, MINUTE_IN_SECONDS * 20);
set_transient($transient_key, $jsonResult, 10);
if (isset($jsonResult->error)) {
if(!empty($jsonResult->error->errors[0]->reason) && $jsonResult->error->errors[0]->reason == 'playlistNotFound'){
$gallobj->html = self::clean_api_error_html(__('There is nothing on the playlist.', 'embedpress'));
if (isset($jsonResult->error->message)) {
$gallobj->html = self::clean_api_error_html($jsonResult->error->message);
$gallobj->html = self::clean_api_error_html(__('Sorry, there may be an issue with your YouTube API key.', 'embedpress'));
$resultsPerPage = $jsonResult->pageInfo->resultsPerPage;
$totalResults = $jsonResult->pageInfo->totalResults;
$totalPages = ceil($totalResults / $resultsPerPage);
if (isset($jsonResult->nextPageToken)) {
$nextPageToken = $jsonResult->nextPageToken;
if (isset($jsonResult->prevPageToken)) {
$prevPageToken = $jsonResult->prevPageToken;
if (!empty($jsonResult->items) && is_array($jsonResult->items)) :
if($options['gallery'] === "false"){
if(count($jsonResult->items) === 1){
$gallobj->first_vid = self::get_id($jsonResult->items[0]);
if(count($jsonResult->items) === 1 && empty($nextPageToken) && empty($prevPageToken)){
$gallobj->first_vid = self::get_id($jsonResult->items[0]);
if (strpos($options['playlistId'], 'UU') === 0) {
usort($jsonResult->items, array(self::class, 'compare_vid_date')); // sorts in place
<div class="ep-youtube__content__block" data-unique-id="<?php echo wp_rand(); ?>">
<div class="youtube__content__body">
<div class="content__wrap">
<?php foreach ($jsonResult->items as $item) : ?>
$privacyStatus = isset($item->status->privacyStatus) ? $item->status->privacyStatus : null;
$thumbnail = self::get_thumbnail_url($item, $options['thumbnail'], $privacyStatus);
$vid = self::get_id($item);
if (empty($gallobj->first_vid)) {
$gallobj->first_vid = $vid;
if ($privacyStatus == 'private' && $options['hideprivate']) {