See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Provides {@link flickr_client} class. 19 * 20 * @package core 21 * @copyright 2017 David Mudrák <david@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 require_once($CFG->libdir.'/oauthlib.php'); 28 29 /** 30 * Simple Flickr API client implementing the features needed by Moodle 31 * 32 * @copyright 2017 David Mudrak <david@moodle.com> 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class flickr_client extends oauth_helper { 36 37 /** 38 * Base URL for Flickr OAuth 1.0 API calls. 39 */ 40 const OAUTH_ROOT = 'https://www.flickr.com/services/oauth'; 41 42 /** 43 * Base URL for Flickr REST API calls. 44 */ 45 const REST_ROOT = 'https://api.flickr.com/services/rest'; 46 47 /** 48 * Base URL for Flickr Upload API call. 49 */ 50 const UPLOAD_ROOT = 'https://up.flickr.com/services/upload/'; 51 52 /** 53 * Set up OAuth and initialize the client. 54 * 55 * The callback URL specified here will override the one specified in the 56 * auth flow defined at Flickr Services. 57 * 58 * @param string $consumerkey 59 * @param string $consumersecret 60 * @param moodle_url|string $callbackurl 61 */ 62 public function __construct($consumerkey, $consumersecret, $callbackurl = '') { 63 parent::__construct([ 64 'api_root' => self::OAUTH_ROOT, 65 'oauth_consumer_key' => $consumerkey, 66 'oauth_consumer_secret' => $consumersecret, 67 'oauth_callback' => $callbackurl, 68 'http_options' => [ 69 'CURLOPT_USERAGENT' => static::user_agent(), 70 ], 71 ]); 72 } 73 74 /** 75 * Return User-Agent string suitable for calls to Flickr endpoint, avoiding problems caused by the string returned by 76 * the {@see core_useragent::get_moodlebot_useragent} helper, which is often rejected due to presence of "Bot" within 77 * 78 * @return string 79 */ 80 public static function user_agent(): string { 81 global $CFG; 82 83 $version = moodle_major_version(); 84 85 return "MoodleSite/{$version} (+{$CFG->wwwroot})"; 86 } 87 88 /** 89 * Temporarily store the request token secret in the session. 90 * 91 * The request token secret is returned by the oauth request_token method. 92 * It needs to be stored in the session before the user is redirected to 93 * the Flickr to authorize the client. After redirecting back, this secret 94 * is used for exchanging the request token with the access token. 95 * 96 * The identifiers help to avoid collisions between multiple calls to this 97 * method from different plugins in the same session. They are used as the 98 * session cache identifiers. Provide an associative array identifying the 99 * particular method call. At least, the array must contain the 'caller' 100 * with the caller's component name. Use additional items if needed. 101 * 102 * @param array $identifiers Identification of the call 103 * @param string $secret 104 */ 105 public function set_request_token_secret(array $identifiers, $secret) { 106 107 if (empty($identifiers) || empty($identifiers['caller'])) { 108 throw new coding_exception('Invalid call identification'); 109 } 110 111 $cache = cache::make_from_params(cache_store::MODE_SESSION, 'core', 'flickrclient', $identifiers); 112 $cache->set('request_token_secret', $secret); 113 } 114 115 /** 116 * Returns previously stored request token secret. 117 * 118 * See {@link self::set_request_token_secret()} for more details on the 119 * $identifiers argument. 120 * 121 * @param array $identifiers Identification of the call 122 * @return string|bool False on error, string secret otherwise. 123 */ 124 public function get_request_token_secret(array $identifiers) { 125 126 if (empty($identifiers) || empty($identifiers['caller'])) { 127 throw new coding_exception('Invalid call identification'); 128 } 129 130 $cache = cache::make_from_params(cache_store::MODE_SESSION, 'core', 'flickrclient', $identifiers); 131 132 return $cache->get('request_token_secret'); 133 } 134 135 /** 136 * Call a Flickr API method. 137 * 138 * @param string $function API function name like 'flickr.photos.getSizes' or just 'photos.getSizes' 139 * @param array $params Additional API call arguments. 140 * @param string $method HTTP method to use (GET or POST). 141 * @return object|bool Response as returned by the Flickr or false on invalid authentication 142 */ 143 public function call($function, array $params = [], $method = 'GET') { 144 145 if (strpos($function, 'flickr.') !== 0) { 146 $function = 'flickr.'.$function; 147 } 148 149 $params['method'] = $function; 150 $params['format'] = 'json'; 151 $params['nojsoncallback'] = 1; 152 153 $rawresponse = $this->request($method, self::REST_ROOT, $params); 154 $response = json_decode($rawresponse); 155 156 if (!is_object($response) || !isset($response->stat)) { 157 throw new moodle_exception('flickr_api_call_failed', 'core_error', '', $rawresponse); 158 } 159 160 if ($response->stat === 'ok') { 161 return $response; 162 163 } else if ($response->stat === 'fail' && $response->code == 98) { 164 // Authentication failure, give the caller a chance to re-authenticate. 165 return false; 166 167 } else { 168 throw new moodle_exception('flickr_api_call_failed', 'core_error', '', $response); 169 } 170 171 return $response; 172 } 173 174 /** 175 * Return the URL to fetch the given photo from. 176 * 177 * Flickr photos are distributed via farm servers staticflickr.com in 178 * various sizes (resolutions). The method tries to find the source URL of 179 * the photo in the highest possible resolution. Results are cached so that 180 * we do not need to query the Flickr API over and over again. 181 * 182 * @param string $photoid Flickr photo identifier 183 * @return string URL 184 */ 185 public function get_photo_url($photoid) { 186 187 $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'core', 'flickrclient'); 188 189 $url = $cache->get('photourl_'.$photoid); 190 191 if ($url === false) { 192 $response = $this->call('photos.getSizes', ['photo_id' => $photoid]); 193 // Sizes are returned from smallest to greatest. 194 if (!empty($response->sizes->size) && is_array($response->sizes->size)) { 195 while ($bestsize = array_pop($response->sizes->size)) { 196 if (isset($bestsize->source)) { 197 $url = $bestsize->source; 198 break; 199 } 200 } 201 } 202 } 203 204 if ($url === false) { 205 throw new repository_exception('cannotdownload', 'repository'); 206 207 } else { 208 $cache->set('photourl_'.$photoid, $url); 209 } 210 211 return $url; 212 } 213 214 /** 215 * Upload a photo from Moodle file pool to Flickr. 216 * 217 * Optional meta information are title, description, tags, is_public, 218 * is_friend, is_family, safety_level, content_type and hidden. 219 * See {@link https://www.flickr.com/services/api/upload.api.html}. 220 * 221 * Upload can't be asynchronous because then the query would not return the 222 * photo ID which we need to add the photo to a photoset (album) 223 * eventually. 224 * 225 * @param stored_file $photo stored in Moodle file pool 226 * @param array $meta optional meta information 227 * @return int|bool photo id, false on authentication failure 228 */ 229 public function upload(stored_file $photo, array $meta = []) { 230 231 $args = [ 232 'title' => isset($meta['title']) ? $meta['title'] : null, 233 'description' => isset($meta['description']) ? $meta['description'] : null, 234 'tags' => isset($meta['tags']) ? $meta['tags'] : null, 235 'is_public' => isset($meta['is_public']) ? $meta['is_public'] : 0, 236 'is_friend' => isset($meta['is_friend']) ? $meta['is_friend'] : 0, 237 'is_family' => isset($meta['is_family']) ? $meta['is_family'] : 0, 238 'safety_level' => isset($meta['safety_level']) ? $meta['safety_level'] : 1, 239 'content_type' => isset($meta['content_type']) ? $meta['content_type'] : 1, 240 'hidden' => isset($meta['hidden']) ? $meta['hidden'] : 2, 241 ]; 242 243 $this->sign_secret = $this->consumer_secret.'&'.$this->access_token_secret; 244 $params = $this->prepare_oauth_parameters(self::UPLOAD_ROOT, ['oauth_token' => $this->access_token] + $args, 'POST'); 245 246 $params['photo'] = $photo; 247 248 $response = $this->http->post(self::UPLOAD_ROOT, $params); 249 250 // Reset http header and options to prepare for the next request. 251 $this->reset_state(); 252 253 if ($response) { 254 $xml = simplexml_load_string($response); 255 256 if ((string)$xml['stat'] === 'ok') { 257 return (int)$xml->photoid; 258 259 } else if ((string)$xml['stat'] === 'fail' && (int)$xml->err['code'] == 98) { 260 // Authentication failure. 261 return false; 262 263 } else { 264 throw new moodle_exception('flickr_upload_failed', 'core_error', '', 265 ['code' => (int)$xml->err['code'], 'message' => (string)$xml->err['msg']]); 266 } 267 268 } else { 269 throw new moodle_exception('flickr_upload_error', 'core_error', '', null, $response); 270 } 271 } 272 273 /** 274 * Resets curl state. 275 * 276 * @return void 277 */ 278 public function reset_state(): void { 279 $this->http->cleanopt(); 280 $this->http->resetHeader(); 281 } 282 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body