1<?php 2/** 3 * DokuWiki Plugin vimeo (Syntax Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Michael Große <dokuwiki@cosmocode.de> 7 */ 8 9class syntax_plugin_vimeo extends DokuWiki_Syntax_Plugin 10{ 11 /** 12 * @return string Syntax mode type 13 */ 14 public function getType() 15 { 16 return 'substition'; 17 } 18 19 /** 20 * @return string Paragraph type 21 */ 22 public function getPType() 23 { 24 return 'block'; 25 } 26 27 /** 28 * @return int Sort order - Low numbers go before high numbers 29 */ 30 public function getSort() 31 { 32 return 100; 33 } 34 35 /** 36 * Connect lookup pattern to lexer. 37 * 38 * @param string $mode Parser mode 39 */ 40 public function connectTo($mode) 41 { 42 $this->Lexer->addSpecialPattern('{{vimeoAlbum>.+?}}', $mode, 'plugin_vimeo'); 43 } 44 45 /** 46 * Handle matches of the vimeo syntax 47 * 48 * @param string $match The match of the syntax 49 * @param int $state The state of the handler 50 * @param int $pos The position in the document 51 * @param Doku_Handler $handler The handler 52 * 53 * @return array Data for the renderer 54 */ 55 public function handle($match, $state, $pos, Doku_Handler $handler) 56 { 57 $albumID = substr($match, strlen('{{vimeoAlbum>'), -2); 58 try { 59 $data = $this->getAlbumVideos($albumID); 60 } catch (Exception $e) { 61 $data = ['errors' => [$e->getMessage() . '; Code: ' . $e->getCode()]]; 62 } 63 64 return $data; 65 } 66 67 /** 68 * Render xhtml output or metadata 69 * 70 * @param string $mode Renderer mode (supported modes: xhtml) 71 * @param Doku_Renderer $renderer The renderer 72 * @param array $data The data from the handler() function 73 * 74 * @return bool If rendering was successful. 75 */ 76 public function render($mode, Doku_Renderer $renderer, $data) 77 { 78 if ($mode !== 'xhtml') { 79 return false; 80 } 81 82 $renderer->doc .= '<div class="plugin-vimeo-album">'; 83 84 if (!empty($data['errors'])) { 85 foreach ($data['errors'] as $error) { 86 msg('Vimeo Plugin Error: ' . hsc($error), -1); 87 } 88 } 89 90 if (!empty($data['videos'])) { 91 $videos = $data['videos']; 92 foreach ($videos as $video) { 93 $this->renderVideo($renderer, $video); 94 } 95 } 96 97 $renderer->doc .= '</div>'; 98 99 return true; 100 } 101 102 /** 103 * Get all video data for the given album id 104 * 105 * The albumID must be owned be the user that provided the configured access token 106 * 107 * This also gets the paged videos in further requests if there are more than 100 videos 108 * 109 * @param string $albumID 110 * 111 * @return array data for the videos in the album 112 */ 113 protected function getAlbumVideos($albumID) 114 { 115 $accessToken = $this->getConf('accessToken'); 116 if (empty($accessToken)) { 117 throw new RuntimeException('Vimeo access token not configured! Please see documentation.'); 118 } 119 120 $fields = 'name,description,embed.html,pictures.sizes,privacy,release_time'; 121 $endpoint = '/me/albums/' . $albumID . '/videos?sort=manual&per_page=100&fields=' . $fields; 122 $errors = []; 123 $respData = $this->sendVimeoRequest($accessToken, $endpoint, $errors); 124 $videos = $respData['data']; 125 126 if (!empty($respData['paging']['next'])) { 127 while (true) { 128 $respData = $this->sendVimeoRequest($accessToken, $respData['paging']['next'], $errors); 129 $videos = array_merge($videos, $respData['videos']); 130 if (empty($respData['paging']['next'])) { 131 break; 132 } 133 } 134 } 135 136 return [ 137 'videos' => $videos, 138 'errors' => $errors, 139 ]; 140 } 141 142 /** 143 * Make a single request to Vimeo and return the parsed body 144 * 145 * @param string $accessToken The access token 146 * @param string $endpoint The endpoint to which to connect, must begin with a / 147 * @param array $errors If the rate-limit is hit, then an error-message is written in here 148 * 149 * @return mixed 150 * 151 * @throws RuntimeException If the server returns an error 152 */ 153 protected function sendVimeoRequest($accessToken, $endpoint, &$errors) 154 { 155 $http = new \DokuHTTPClient(); 156 $http->headers['Authorization'] = 'Bearer ' . $accessToken; 157 $http->agent = 'DokuWiki HTTP Client (Vimeo Plugin)'; 158 $http->keep_alive = false; 159 $base = 'https://api.vimeo.com'; 160 $url = $base . $endpoint; 161 $http->sendRequest($url); 162 163 $body = $http->resp_body; 164 $respData = json_decode($body, true); 165 166 if (!empty($respData['error'])) { 167 dbglog($http->resp_headers, __FILE__ . ': ' . __LINE__); 168 throw new RuntimeException( 169 $respData['error'] . ' ' . $respData['developer_message'], 170 $respData['error_code'] 171 ); 172 } 173 174 $remainingRateLimit = $http->resp_headers['x-ratelimit-remaining']; 175 if ($remainingRateLimit < 10) { 176 dbglog($http->resp_headers, __FILE__ . ': ' . __LINE__); 177 $errors[] = 'The remaining Vimeo rate-limit is very low. Please check back in 15min or later'; 178 } 179 180 return $respData; 181 } 182 183 /** 184 * Render a preview image and put the video iframe-html into a data attribute 185 * 186 * This offers all available images in a srcset, so the browser can decide which to load 187 * 188 * @param Doku_Renderer $renderer 189 * @param array $video The video data 190 */ 191 protected function renderVideo(Doku_Renderer $renderer, $video) 192 { 193 $title = hsc($video['name']); 194 if ($video['privacy']['embed'] === 'private') { 195 msg(sprintf($this->getLang('embed_deactivated'), $title), 2); 196 return; 197 } 198 $thumbnailWidthPercent = $this->getConf('thumbnailWidthPercent'); 199 $widthAttr = 'style="width: ' . $thumbnailWidthPercent . '%;"'; 200 $renderer->doc .= '<div class="plugin-vimeo-video"' . $widthAttr . ' data-videoiframe="' . hsc($video['embed']['html']) . '">'; 201 $renderer->doc .= '<figure>'; 202 $src = $video['pictures']['sizes'][2]['link_with_play_button']; 203 $srcset = []; 204 foreach ($video['pictures']['sizes'] as $picture) { 205 $srcset [] = $picture['link_with_play_button'] . ' ' . $picture['width'] . 'w'; 206 } 207 $renderer->doc .= '<img srcset="' . implode(',', $srcset) . '" src="' . $src . '" alt="' . $title . '">'; 208 $caption = $this->createCaption($video); 209 $renderer->doc .= '<figcaption>' . $caption . '</figcaption>'; 210 $renderer->doc .= '</figure>'; 211 $renderer->doc .= '</div>'; 212 } 213 214 /** 215 * Build the caption for a video 216 * 217 * @param array $video the video data 218 * 219 * @return string HTML for the video caption 220 */ 221 protected function createCaption($video) { 222 $title = '<span class="vimeo-video-title">' . hsc($video['name']) . '</span>'; 223 224 $releaseDateObject = new \DateTime($video['release_time']); 225 $releaseTime = dformat($releaseDateObject->format('U')); 226 $releaseString = '<span class="vimeo-video-releaseTime">' 227 . $this->getLang('released') 228 . ' <time>' . $releaseTime .'</time></span>'; 229 230 $description = "<span class='vimeo-video-description'>" . hsc($video['description']) . '</span>'; 231 232 return $title . $releaseString . $description; 233 234 } 235} 236 237