Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]
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 * Simple implementation of some Google API functions for Moodle. 19 * 20 * @package core 21 * @copyright Dan Poltawski <talktodan@gmail.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.'/filelib.php'); 28 require_once($CFG->libdir.'/oauthlib.php'); 29 30 /** 31 * Class for manipulating google documents through the google data api. 32 * 33 * Docs for this can be found here: 34 * {@link http://code.google.com/apis/documents/docs/2.0/developers_guide_protocol.html} 35 * 36 * @package core 37 * @subpackage lib 38 * @copyright Dan Poltawski <talktodan@gmail.com> 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class google_docs { 42 /** @var string Realm for authentication, need both docs and spreadsheet realm */ 43 const REALM = 'https://docs.google.com/feeds/ https://spreadsheets.google.com/feeds/ https://docs.googleusercontent.com/'; 44 /** @var string Document list url */ 45 const DOCUMENTFEED_URL = 'https://docs.google.com/feeds/default/private/full'; 46 /** @var string Upload url */ 47 const UPLOAD_URL = 'https://docs.google.com/feeds/upload/create-session/default/private/full?convert=false'; 48 49 /** @var google_oauth oauth curl class for making authenticated requests */ 50 private $googleoauth = null; 51 52 /** 53 * Constructor. 54 * 55 * @param google_oauth $googleoauth oauth curl class for making authenticated requests 56 */ 57 public function __construct(google_oauth $googleoauth) { 58 $this->googleoauth = $googleoauth; 59 $this->reset_curl_state(); 60 } 61 62 /** 63 * Resets state on oauth curl object and set GData protocol 64 * version 65 */ 66 private function reset_curl_state() { 67 $this->googleoauth->reset_state(); 68 $this->googleoauth->setHeader('GData-Version: 3.0'); 69 } 70 71 /** 72 * Returns a list of files the user has formated for files api 73 * 74 * @param string $search A search string to do full text search on the documents 75 * @return mixed Array of files formated for fileapoi 76 */ 77 public function get_file_list($search = '') { 78 global $CFG, $OUTPUT; 79 $url = self::DOCUMENTFEED_URL; 80 81 if ($search) { 82 $url.='?q='.urlencode($search); 83 } 84 85 $files = array(); 86 $content = $this->googleoauth->get($url); 87 try { 88 if (strpos($content, '<?xml') !== 0) { 89 throw new moodle_exception('invalidxmlresponse'); 90 } 91 $xml = new SimpleXMLElement($content); 92 } catch (Exception $e) { 93 // An error occured while trying to parse the XML, let's just return nothing. SimpleXML does not 94 // return a more specific Exception, that's why the global Exception class is caught here. 95 return $files; 96 } 97 date_default_timezone_set(core_date::get_user_timezone()); 98 foreach ($xml->entry as $gdoc) { 99 $docid = (string) $gdoc->children('http://schemas.google.com/g/2005')->resourceId; 100 list($type, $docid) = explode(':', $docid); 101 102 $title = ''; 103 $source = ''; 104 // FIXME: We're making hard-coded choices about format here. 105 // If the repo api can support it, we could let the user 106 // chose. 107 switch($type){ 108 case 'document': 109 $title = $gdoc->title.'.rtf'; 110 $source = 'https://docs.google.com/feeds/download/documents/Export?id='.$docid.'&exportFormat=rtf'; 111 break; 112 case 'presentation': 113 $title = $gdoc->title.'.ppt'; 114 $source = 'https://docs.google.com/feeds/download/presentations/Export?id='.$docid.'&exportFormat=ppt'; 115 break; 116 case 'spreadsheet': 117 $title = $gdoc->title.'.xls'; 118 $source = 'https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key='.$docid.'&exportFormat=xls'; 119 break; 120 case 'pdf': 121 case 'file': 122 $title = (string)$gdoc->title; 123 // Some files don't have a content probably because the download has been restricted. 124 if (isset($gdoc->content)) { 125 $source = (string)$gdoc->content[0]->attributes()->src; 126 } 127 break; 128 } 129 130 $files[] = array( 'title' => $title, 131 'url' => "{$gdoc->link[0]->attributes()->href}", 132 'source' => $source, 133 'date' => strtotime($gdoc->updated), 134 'thumbnail' => (string) $OUTPUT->image_url(file_extension_icon($title, 32)) 135 ); 136 } 137 core_date::set_default_server_timezone(); 138 139 return $files; 140 } 141 142 /** 143 * Sends a file object to google documents 144 * 145 * @param object $file File object 146 * @return boolean True on success 147 */ 148 public function send_file($file) { 149 // First we create the 'resumable upload request'. 150 $this->googleoauth->setHeader("Content-Length: 0"); 151 $this->googleoauth->setHeader("X-Upload-Content-Length: ". $file->get_filesize()); 152 $this->googleoauth->setHeader("X-Upload-Content-Type: ". $file->get_mimetype()); 153 $this->googleoauth->setHeader("Slug: ". $file->get_filename()); 154 $this->googleoauth->post(self::UPLOAD_URL); 155 156 if ($this->googleoauth->info['http_code'] !== 200) { 157 throw new moodle_exception('Cantpostupload'); 158 } 159 160 // Now we http PUT the file in the location returned. 161 $location = $this->googleoauth->response['Location']; 162 if (empty($location)) { 163 throw new moodle_exception('Nouploadlocation'); 164 } 165 166 // Reset the curl object for actually sending the file. 167 $this->reset_curl_state(); 168 $this->googleoauth->setHeader("Content-Length: ". $file->get_filesize()); 169 $this->googleoauth->setHeader("Content-Type: ". $file->get_mimetype()); 170 171 // We can't get a filepointer, so have to copy the file.. 172 $tmproot = make_temp_directory('googledocsuploads'); 173 $tmpfilepath = $tmproot.'/'.$file->get_contenthash(); 174 $file->copy_content_to($tmpfilepath); 175 176 // HTTP PUT the file. 177 $this->googleoauth->put($location, array('file'=>$tmpfilepath)); 178 179 // Remove the temporary file we created.. 180 unlink($tmpfilepath); 181 182 if ($this->googleoauth->info['http_code'] === 201) { 183 // Clear headers for further requests. 184 $this->reset_curl_state(); 185 return true; 186 } else { 187 return false; 188 } 189 } 190 191 /** 192 * Downloads a file using authentication 193 * 194 * @param string $url url of file 195 * @param string $path path to save file to 196 * @param int $timeout request timeout, default 0 which means no timeout 197 * @return array stucture for repository download_file 198 */ 199 public function download_file($url, $path, $timeout = 0) { 200 $result = $this->googleoauth->download_one($url, null, array('filepath' => $path, 'timeout' => $timeout)); 201 if ($result === true) { 202 $info = $this->googleoauth->get_info(); 203 if (isset($info['http_code']) && $info['http_code'] == 200) { 204 return array('path'=>$path, 'url'=>$url); 205 } else { 206 throw new moodle_exception('cannotdownload', 'repository'); 207 } 208 } else { 209 throw new moodle_exception('errorwhiledownload', 'repository', '', $result); 210 } 211 } 212 } 213 214 /** 215 * Class for manipulating picasa through the google data api. 216 * 217 * Docs for this can be found here: 218 * {@link http://code.google.com/apis/picasaweb/developers_guide_protocol.html} 219 * 220 * @package core 221 * @copyright Dan Poltawski <talktodan@gmail.com> 222 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 223 */ 224 class google_picasa { 225 /** @var string Realm for authentication */ 226 const REALM = 'http://picasaweb.google.com/data/'; 227 /** @var string Upload url */ 228 const UPLOAD_LOCATION = 'https://picasaweb.google.com/data/feed/api/user/default/albumid/default'; 229 /** @var string photo list url */ 230 const ALBUM_PHOTO_LIST = 'https://picasaweb.google.com/data/feed/api/user/default/albumid/'; 231 /** @var string search url */ 232 const PHOTO_SEARCH_URL = 'https://picasaweb.google.com/data/feed/api/user/default?kind=photo&q='; 233 /** @var string album list url */ 234 const LIST_ALBUMS_URL = 'https://picasaweb.google.com/data/feed/api/user/default'; 235 /** @var string manage files url */ 236 const MANAGE_URL = 'http://picasaweb.google.com/'; 237 238 /** @var google_oauth oauth curl class for making authenticated requests */ 239 private $googleoauth = null; 240 /** @var string Last album name retrievied */ 241 private $lastalbumname = null; 242 243 /** 244 * Constructor. 245 * 246 * @param google_oauth $googleoauth oauth curl class for making authenticated requests 247 */ 248 public function __construct(google_oauth $googleoauth) { 249 $this->googleoauth = $googleoauth; 250 $this->googleoauth->setHeader('GData-Version: 2'); 251 } 252 253 /** 254 * Sends a file object to picasaweb 255 * 256 * @param object $file File object 257 * @return boolean True on success 258 */ 259 public function send_file($file) { 260 $this->googleoauth->setHeader("Content-Length: ". $file->get_filesize()); 261 $this->googleoauth->setHeader("Content-Type: ". $file->get_mimetype()); 262 $this->googleoauth->setHeader("Slug: ". $file->get_filename()); 263 264 $this->googleoauth->post(self::UPLOAD_LOCATION, $file->get_content()); 265 266 if ($this->googleoauth->info['http_code'] === 201) { 267 return true; 268 } else { 269 return false; 270 } 271 } 272 273 /** 274 * Returns list of photos for file picker. 275 * If top level then returns list of albums, otherwise 276 * photos within an album. 277 * 278 * @param string $path The path to files (assumed to be albumid) 279 * @return mixed $files A list of files for the file picker 280 */ 281 public function get_file_list($path = '') { 282 if (!$path) { 283 return $this->get_albums(); 284 } else { 285 return $this->get_album_photos($path); 286 } 287 } 288 289 /** 290 * Returns list of photos in album specified 291 * 292 * @param int $albumid Photo album to list photos from 293 * @return mixed $files A list of files for the file picker 294 */ 295 public function get_album_photos($albumid) { 296 $albumcontent = $this->googleoauth->get(self::ALBUM_PHOTO_LIST.$albumid); 297 298 return $this->get_photo_details($albumcontent); 299 } 300 301 /** 302 * Returns the name of the album for which get_photo_details was called last time. 303 * 304 * @return string 305 */ 306 public function get_last_album_name() { 307 return $this->lastalbumname; 308 } 309 310 /** 311 * Does text search on the users photos and returns 312 * matches in format for picasa api 313 * 314 * @param string $query Search terms 315 * @return mixed $files A list of files for the file picker 316 */ 317 public function do_photo_search($query) { 318 $content = $this->googleoauth->get(self::PHOTO_SEARCH_URL.htmlentities($query)); 319 320 return $this->get_photo_details($content); 321 } 322 323 /** 324 * Gets all the users albums and returns them as a list of folders 325 * for the file picker 326 * 327 * @return mixes $files Array in the format get_listing uses for folders 328 */ 329 public function get_albums() { 330 $files = array(); 331 $content = $this->googleoauth->get(self::LIST_ALBUMS_URL); 332 333 try { 334 if (strpos($content, '<?xml') !== 0) { 335 throw new moodle_exception('invalidxmlresponse'); 336 } 337 $xml = new SimpleXMLElement($content); 338 } catch (Exception $e) { 339 // An error occured while trying to parse the XML, let's just return nothing. SimpleXML does not 340 // return a more specific Exception, that's why the global Exception class is caught here. 341 return $files; 342 } 343 344 foreach ($xml->entry as $album) { 345 $gphoto = $album->children('http://schemas.google.com/photos/2007'); 346 347 $mediainfo = $album->children('http://search.yahoo.com/mrss/'); 348 // Hacky... 349 $thumbnailinfo = $mediainfo->group->thumbnail[0]->attributes(); 350 351 $files[] = array( 'title' => (string) $album->title, 352 'date' => userdate($gphoto->timestamp), 353 'size' => (int) $gphoto->bytesUsed, 354 'path' => (string) $gphoto->id, 355 'thumbnail' => (string) $thumbnailinfo['url'], 356 'thumbnail_width' => 160, // 160 is the native maximum dimension. 357 'thumbnail_height' => 160, 358 'children' => array(), 359 ); 360 } 361 362 return $files; 363 } 364 365 /** 366 * Recieves XML from a picasa list of photos and returns 367 * array in format for file picker. 368 * 369 * @param string $rawxml XML from picasa api 370 * @return mixed $files A list of files for the file picker 371 */ 372 public function get_photo_details($rawxml) { 373 $files = array(); 374 375 try { 376 if (strpos($rawxml, '<?xml') !== 0) { 377 throw new moodle_exception('invalidxmlresponse'); 378 } 379 $xml = new SimpleXMLElement($rawxml); 380 } catch (Exception $e) { 381 // An error occured while trying to parse the XML, let's just return nothing. SimpleXML does not 382 // return a more specific Exception, that's why the global Exception class is caught here. 383 return $files; 384 } 385 $this->lastalbumname = (string)$xml->title; 386 387 foreach ($xml->entry as $photo) { 388 $gphoto = $photo->children('http://schemas.google.com/photos/2007'); 389 390 $mediainfo = $photo->children('http://search.yahoo.com/mrss/'); 391 $fullinfo = $mediainfo->group->content->attributes(); 392 // Hacky... 393 $thumbnailinfo = $mediainfo->group->thumbnail[0]->attributes(); 394 395 // Derive the nicest file name we can. 396 if (!empty($mediainfo->group->description)) { 397 $title = shorten_text((string)$mediainfo->group->description, 20, false, ''); 398 $title = clean_filename($title).'.jpg'; 399 } else { 400 $title = (string)$mediainfo->group->title; 401 } 402 403 $files[] = array( 404 'title' => $title, 405 'date' => userdate($gphoto->timestamp), 406 'size' => (int) $gphoto->size, 407 'path' => $gphoto->albumid.'/'.$gphoto->id, 408 'thumbnail' => (string) $thumbnailinfo['url'], 409 'thumbnail_width' => 72, // 72 is the native maximum dimension. 410 'thumbnail_height' => 72, 411 'source' => (string) $fullinfo['url'], 412 'url' => (string) $fullinfo['url'] 413 ); 414 } 415 416 return $files; 417 } 418 } 419 420 /** 421 * OAuth 2.0 client for Google Services 422 * 423 * @package core 424 * @copyright 2012 Dan Poltawski 425 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 426 */ 427 class google_oauth extends oauth2_client { 428 /** 429 * Returns the auth url for OAuth 2.0 request 430 * @return string the auth url 431 */ 432 protected function auth_url() { 433 return 'https://accounts.google.com/o/oauth2/auth'; 434 } 435 436 /** 437 * Returns the token url for OAuth 2.0 request 438 * @return string the auth url 439 */ 440 protected function token_url() { 441 return 'https://accounts.google.com/o/oauth2/token'; 442 } 443 444 /** 445 * Resets headers and response for multiple requests 446 */ 447 public function reset_state() { 448 $this->header = array(); 449 $this->response = array(); 450 } 451 452 /** 453 * Make a HTTP request, we override the parents because we do not 454 * want to send accept headers (this was a change in the parent class and we want to keep the old behaviour). 455 * 456 * @param string $url The URL to request 457 * @param array $options 458 * @param mixed $acceptheader Not used. 459 * @return bool 460 */ 461 protected function request($url, $options = array(), $acceptheader = 'application/json') { 462 return parent::request($url, $options, false); 463 } 464 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body