From 6be9ee6504c1f11f478ed5a737db27b32b67c714 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 8 Jun 2015 15:55:05 -0700 Subject: [PATCH] Fixes #935: YouTube browser upgraded to use YouTube API v3. An API key is now required for keyword, playlist or channel search. Direct input of specific videos still possible without using an API key. Now detects more YouTube URL formats. eg. https://youtu.be/xxxxxxx etc. --- e107_admin/image.php | 224 ++++++++++++++++++++----------- e107_handlers/media_class.php | 125 +++++++++-------- e107_web/js/core/mediaManager.js | 6 +- 3 files changed, 216 insertions(+), 139 deletions(-) diff --git a/e107_admin/image.php b/e107_admin/image.php index a10e2def2..554a7feda 100644 --- a/e107_admin/image.php +++ b/e107_admin/image.php @@ -754,8 +754,10 @@ class media_admin_ui extends e_admin_ui 'watermark_shadowcolor' => array('title'=> IMALAN_94, 'tab'=>1,'type' => 'text', 'data' => 'str', 'help'=>IMALAN_95), // 'validate' => 'regex', 'rule' => '#^[\d]+$#i', 'help' => 'allowed characters are a-zA-Z and underscore')), 'watermark_opacity' => array('title'=> IMALAN_96, 'tab'=>1, 'type' => 'number', 'data' => 'int', 'help'=>IMALAN_97), // 'validate' => 'regex', 'rule' => '#^[\d]+$#i', 'help' => 'allowed characters are a-zA-Z and underscore')), - + // https://developers.google.com/youtube/player_parameters + 'youtube_apikey' => array('title'=> "YouTube Public API key", 'tab'=>2, 'type' => 'text', 'data'=>'str', 'help'=>IMALAN_99, 'writeParms'=>array('post'=>" More")), + 'youtube_default_account' => array('title'=> IMALAN_98, 'tab'=>2, 'type' => 'text', 'data'=>'str', 'help'=>IMALAN_99), 'youtube_rel' => array('title'=> IMALAN_100, 'tab'=>2, 'type' => 'boolean', 'data'=>'int', 'help'=>''), @@ -1383,113 +1385,175 @@ class media_admin_ui extends e_admin_ui return e107::getMedia()->browserCarousel($items, $parms); } - + + /** + * Extract Video or Playlist code from a URL + * Currently works with v=xxx or list=xxxx + * @param $url + * @return string + */ function getYouTubeCode($url) { - list($url,$qry) = explode("?",$url); + $url = str_replace("url:","http://",$url); + + list($tmp,$qry) = explode("?",$url); parse_str($qry,$opt); - return $opt['v']; + + if(!empty($opt['list'])) + { + return 'playlist:'.$opt['list']; + } + + if(!empty($opt['v'])) + { + return 'video:'.$opt['v']; + } + + $pattern = '#^(?:https?://)?(?:www\.|m\.)?(?:youtu\.be/|youtube\.com(?:/embed/|/v/|/watch\?v=|/watch\?.+&v=))([\w-]{11})(?:.+)?$#x'; + preg_match($pattern, $url, $matches); + + return isset($matches[1]) ? 'video:'.trim($matches[1]) : false; + } - + + /** + * @param string $parm + * @return mixed|string + * @see https://www.googleapis.com/youtube/v3/search + */ function videoTab($parm='') { - - // $feed = "https://gdata.youtube.com/feeds/base/users/e107inc/uploads"; - - // @see https://developers.google.com/youtube/2.0/developers_guide_protocol_api_query_parameters + // $apiKey = e107::pref('core','youtube_apikey'); - $searchQry = $this->getQuery('search'); + $searchQry = $this->getQuery('search'); + if(substr($searchQry,0,4) == 'url:') + { + $searchQry = $this->getYouTubeCode($searchQry); - if(!empty($searchQry)) + } + + if(!empty($searchQry)) // -- Search Active. + { + if(substr($searchQry,0,6) == 'video:' || substr($searchQry,0,2) == 'v=') // YouTube video code { - if(substr($searchQry,0,6) == 'video:' || substr($searchQry,0,2) == 'v=') // YouTube video code? - { - // return "video: ".$searchQry; - $searchQry = (substr($searchQry,0,2) == 'v=') ? trim(substr($searchQry,2)) : trim(substr($searchQry,6)); - $data = array(); - $data['entry'][0]['id'] = $searchQry; - $data['entry'][0]['title'] = "Specified Video"; - $extension = 'youtube'; - // return print_a($parm,true); - } - elseif(substr($searchQry,0,9) == 'playlist:') // playlist - { - $searchQry = trim(substr($searchQry,9)); - $feed = "http://gdata.youtube.com/feeds/api/playlists/".urlencode($searchQry); - $plData = e107::getXml()->loadXMLfile($feed,true); - unset($feed); - // return print_a($plData,true); - - $code = $this->getYouTubeCode( $plData['entry'][0]['link'][0]['@attributes']['href']); - - if(!empty($plData)) - { - $data = array(); - $data['entry'][0]['id'] = $searchQry; - $data['entry'][0]['title'] = "Playlist: ". $plData['title']; - $data['entry'][0]['thumb'] = "http://i1.ytimg.com/vi/".$code."/maxresdefault.jpg"; - $extension = 'youtubepl'; - - e107::getMedia()->saveThumb("http://i1.ytimg.com/vi/".$code."/maxresdefault.jpg", $searchQry); - } - - } - else - { - $feed = "http://gdata.youtube.com/feeds/api/videos?orderby=relevance&vq=".urlencode($searchQry)."&max-results=50"; // maximum is 50. - $extension = 'youtube'; - } + $searchQry = (substr($searchQry,0,2) == 'v=') ? trim(substr($searchQry,2)) : trim(substr($searchQry,6)); + $extension = 'youtube'; + // $feed = "https://www.googleapis.com/youtube/v3/videos?part=snippet&id=".urlencode($searchQry)."&key=".$apiKey; + $data = array(); + $data['items'][0]['id']['videoId'] = $searchQry; + $data['items'][0]['snippet']['thumbnails']['medium']['url'] = "http://i.ytimg.com/vi/".$searchQry."/mqdefault.jpg"; + $data['items'][0]['snippet']['title'] = 'Specified Video'; + } + elseif(substr($searchQry,0,9) == 'playlist:') // playlist + { + $searchQry = trim(substr($searchQry,9)); + $feed = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=".urlencode($searchQry)."&type=playlist&maxResults=1&key=".$apiKey; + $extension = 'youtubepl'; + } + elseif(substr($searchQry,0,8) == 'channel:') + { + $searchQry = trim(substr($searchQry,8)); + $extension = 'youtube'; + $feed = "https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=".urlencode($searchQry)."&type=video&maxResults=20&key=".$apiKey; } else { - - $defaultAccount = e107::pref('core','youtube_default_account'); - if(empty($defaultAccount)) - { - $defaultAccount = 'e107inc'; - } - - $feed = "https://gdata.youtube.com/feeds/api/users/".$defaultAccount."/uploads"; + $feed = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=".urlencode($searchQry)."&type=video&maxResults=20&key=".$apiKey; $extension = 'youtube'; } - //return print_a($feed,true); + } + else // -- default state. + { - if(!empty($feed) && empty($data)) + $defaultAccount = e107::pref('core','youtube_default_account'); + if(empty($defaultAccount)) { - $data = e107::getXml()->loadXMLfile($feed,true); + $defaultAccount = 'e107inc'; } - // $text .= "

".$data['title']."

"; - // return print_a($data,true); - - $items = array(); - - foreach($data['entry'] as $value) + + $accFeed = "https://www.googleapis.com/youtube/v3/channels?part=contentDetails&forUsername=".$defaultAccount."&key=".$apiKey; + $accData = e107::getFile()->getRemoteContent($accFeed); + $accData = json_decode($accData,true); + $channelID = null; + + foreach($accData['items'] as $val) { - $id = str_replace('http://gdata.youtube.com/feeds/api/videos/','',$value['id']); // http://gdata.youtube.com/feeds/api/videos/_j0b9syAuIk - $thumbnail = "https://i1.ytimg.com/vi/".$id."/0.jpg"; - - $items[] = array( - 'previewUrl' => ($value['thumb']) ? $value['thumb'] : $thumbnail, + if($val['kind'] == 'youtube#channel') + { + $channelID = $val['id']; + break; + } + } + + $feed = "https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=".$channelID."&type=video&maxResults=20&key=".$apiKey; + $extension = 'youtube'; + } + + + if(!empty($feed) ) + { + + if(!empty($apiKey)) + { + $data = e107::getFile()->getRemoteContent($feed); + $data = json_decode($data,true); + $items = array(); + } + else // empty key. + { + $items = "

Youtube search requires a (free) YouTube v3 api key.
+ This key is not required unless you wish to perform a keyword, playlist or channel search.
+ Entering a Youtube video URL directly into the box above will still work without having an api key.
+ Click here for more information and to enter your api key. +

+
"; + } + + } + + + if(!empty($data)) + { + foreach($data['items'] as $value) + { + + $id = $value['id']['videoId']; + $thumbnail = $value['snippet']['thumbnails']['medium']['url']; + + $items[] = array( + 'previewUrl' => $thumbnail, 'saveValue' => $id.".".$extension, // youtube", - 'thumbUrl' => ($value['thumb']) ? $value['thumb'] : $thumbnail, - 'title' => $value['title'] - ); + 'thumbUrl' => $thumbnail, + 'title' => varset($value['snippet']['title'],'') + ); + + if($extension == 'youtubepl') // save Image for background. + { + $hiresThumbnail = $thumbnail = $value['snippet']['thumbnails']['high']['url']; + e107::getMedia()->saveThumb($hiresThumbnail, $id); //TODO move to $tp->Video(); ? + } } + } + // return print_a($data,true); - // return print_a($items,true); - // return print_a($data,true); + $parms = array('width' => 200, 'height'=>113, 'type'=>'image', 'bbcode'=>'video', 'tagid'=> $this->getQuery('tagid'), 'action'=>'youtube','searchPlaceholder'=>'Search Youtube. Paste any YouTube URL here for a specific video/playlist/channel' ); + $text = e107::getMedia()->browserCarousel($items, $parms); + + if(E107_DEBUG_LEVEL > 0 && !empty($feed)) + { + $text .= "
Debug: ". $feed."
"; + if(!empty($data)) + { + $text .= print_a($data,true); + } + } - $parms = array('width' => 200, 'height'=>113, 'type'=>'image', 'bbcode'=>'video', 'tagid'=> $this->getQuery('tagid'), 'action'=>'youtube','searchPlaceholder'=>'Search Youtube. Use video: or playlist: prefixes if you know the code.' ); - - $text = e107::getMedia()->browserCarousel($items, $parms); - - return $text; } diff --git a/e107_handlers/media_class.php b/e107_handlers/media_class.php index fb16760a3..f07eed66f 100644 --- a/e107_handlers/media_class.php +++ b/e107_handlers/media_class.php @@ -1209,9 +1209,12 @@ class e_media } - - - + /** + * Carousel Item Browser. + * @param array|string $data - array for items or string for an error alert. + * @param array $parm + * @return string + */ function browserCarousel($data,$parm=null) { /* Fix for Bootstrap2 margin-left issue when wrapping */ @@ -1250,11 +1253,7 @@ class e_media $text .= "
"; } - if(count($data) < 1) - { - return "
No Results Found.
"; - } - + // $text .= $this->search('srch', $srch, 'go', $filterName, $filterArray, $filterVal).$frm->hidden('mode','online'); @@ -1272,61 +1271,73 @@ class e_media $c=0; $slides = array(); - - foreach($data as $key=>$val) + + if(is_array($data) && count($data) > 0) { - if($c == 0) + + + foreach($data as $key=>$val) { - $active = (count($slides) <1) ? ' active' : ''; - $text .= ' - - -
'; - - if(vartrue($val['slideCaption'])) + if($c == 0) { - $text .= "

".$val['slideCaption']."

"; + $active = (count($slides) <1) ? ' active' : ''; + $text .= ' + + +
'; + + if(vartrue($val['slideCaption'])) + { + $text .= "

".$val['slideCaption']."

"; + } + } + + + $val['width'] = $parm['width']; + $val['height'] = $parm['height']; + $val['id'] = $parm['id']; + $val['tagid'] = $parm['tagid']; + $val['type'] = $parm['type']; + $val['bbcode'] = $parm['bbcode']; + $val['gridClass'] = $parm['gridClass']; + + $text .= $this->browserCarouselItem($val); + + $c++; + + if(varset($val['slideCategory']) && isset($prevCat)) + { + if($val['slideCategory'] !== $prevCat) + { + $c = $perPage; + } + + $prevCat = $val['slideCategory']; + + } + + if($c == $perPage) + { + $text .= ' +
+ + + '; + $slides[] = 1; + $c = 0; } } - - - $val['width'] = $parm['width']; - $val['height'] = $parm['height']; - $val['id'] = $parm['id']; - $val['tagid'] = $parm['tagid']; - $val['type'] = $parm['type']; - $val['bbcode'] = $parm['bbcode']; - $val['gridClass'] = $parm['gridClass']; - $text .= $this->browserCarouselItem($val); - - $c++; - - if(varset($val['slideCategory']) && isset($prevCat)) - { - if($val['slideCategory'] !== $prevCat) - { - $c = $perPage; - } - - $prevCat = $val['slideCategory']; - - } - - if($c == $perPage) - { - $text .= ' -
- - - '; - $slides[] = 1; - $c = 0; - } } - - - + elseif(is_string($data)) // error message. + { + $text .= "
".$data."
"; + } + else + { + $text .= "
No Results Found.
"; + } + $text .= ($c != 0) ? "
\n\n" : ""; @@ -1344,7 +1355,7 @@ class e_media if(E107_DEBUG_LEVEL > 0) { - print_a($parm); + // print_a($parm); } diff --git a/e107_web/js/core/mediaManager.js b/e107_web/js/core/mediaManager.js index d00d15e5f..dcd816f6c 100644 --- a/e107_web/js/core/mediaManager.js +++ b/e107_web/js/core/mediaManager.js @@ -344,10 +344,12 @@ $(document).ready(function() var id = $(this).attr("data-target"); var src = $(this).attr("data-src"); var search = $(this).val(); - + if(search !== null) { - src = src + '&search=' + encodeURIComponent(search); + search = search.replace('https://','url:'); + search = search.replace('http://','url:'); + src = src + '&search=' + encodeURIComponent(search); } // alert(src);