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 * This plugin is used to access box.net repository 19 * 20 * @since Moodle 2.0 21 * @package repository_boxnet 22 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 require_once($CFG->dirroot . '/repository/lib.php'); 26 require_once($CFG->libdir . '/boxlib.php'); 27 28 /** 29 * repository_boxnet class implements box.net client 30 * 31 * @since Moodle 2.0 32 * @package repository_boxnet 33 * @copyright 2010 Dongsheng Cai {@link http://dongsheng.org} 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class repository_boxnet extends repository { 37 38 /** @const MANAGE_URL Manage URL. */ 39 const MANAGE_URL = 'https://app.box.com/files'; 40 41 /** @const SESSION_PREFIX Key used to store information in the session. */ 42 const SESSION_PREFIX = 'repository_boxnet'; 43 44 /** @var string Client ID */ 45 protected $clientid; 46 47 /** @var string Client secret */ 48 protected $clientsecret; 49 50 /** @var string Access token */ 51 protected $accesstoken; 52 53 /** @var object Box.net object */ 54 protected $boxnetclient; 55 56 /** 57 * Constructor 58 * 59 * @param int $repositoryid 60 * @param stdClass $context 61 * @param array $options 62 */ 63 public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) { 64 parent::__construct($repositoryid, $context, $options); 65 66 $clientid = get_config('boxnet', 'clientid'); 67 $clientsecret = get_config('boxnet', 'clientsecret'); 68 $returnurl = new moodle_url('/repository/repository_callback.php'); 69 $returnurl->param('callback', 'yes'); 70 $returnurl->param('repo_id', $this->id); 71 $returnurl->param('sesskey', sesskey()); 72 73 $this->boxnetclient = new boxnet_client($clientid, $clientsecret, $returnurl, ''); 74 } 75 76 /** 77 * Construct a breadcrumb from a path. 78 * 79 * @param string $fullpath Path containing multiple parts separated by slashes. 80 * @return array Array expected to be generated in {@link self::get_listing()}. 81 */ 82 protected function build_breadcrumb($fullpath) { 83 $breadcrumb = array(array( 84 'name' => get_string('pluginname', 'repository_boxnet'), 85 'path' => '' 86 )); 87 $breadcrumbpath = ''; 88 $crumbs = explode('/', $fullpath); 89 foreach ($crumbs as $crumb) { 90 if (empty($crumb)) { 91 // That is probably the root crumb, we've already added it. 92 continue; 93 } 94 list($unused, $tosplit) = explode(':', $crumb, 2); 95 if (strpos($tosplit, '|') !== false) { 96 list($id, $crumbname) = explode('|', $tosplit, 2); 97 } else { 98 $crumbname = $tosplit; 99 } 100 $breadcrumbpath .= '/' . $crumb; 101 $breadcrumb[] = array( 102 'name' => urldecode($crumbname), 103 'path' => $breadcrumbpath 104 ); 105 } 106 return $breadcrumb; 107 } 108 109 /** 110 * Build a part of the path. 111 * 112 * This is used to construct the path that the user is currently browsing. 113 * It must contain a 'type', and a 'value'. Then it can also contain a 114 * 'name' which is very useful to prevent extra queries to get the name only. 115 * 116 * See {@link self::split_part} to extra the information from a part. 117 * 118 * @param string $type Type of part, typically 'folder' or 'search'. 119 * @param string $value The value of the part, eg. a folder ID or search terms. 120 * @param string $name The name of the part. 121 * @return string type:value or type:value|name 122 */ 123 protected function build_part($type, $value, $name = '') { 124 $return = $type . ':' . urlencode($value); 125 if ($name !== '') { 126 $return .= '|' . urlencode($name); 127 } 128 return $return; 129 } 130 131 /** 132 * Extract information from a part of path. 133 * 134 * @param string $part value generated from {@link self::build_parth()}. 135 * @return array containing type, value and name. 136 */ 137 protected function split_part($part) { 138 list($type, $tosplit) = explode(':', $part); 139 $name = ''; 140 if (strpos($tosplit, '|') !== false) { 141 list($value, $name) = explode('|', $tosplit, 2); 142 } else { 143 $value = $tosplit; 144 } 145 return array($type, urldecode($value), urldecode($name)); 146 } 147 148 /** 149 * check if user logged 150 * 151 * @return boolean 152 */ 153 public function check_login() { 154 return $this->boxnetclient->is_logged_in(); 155 } 156 157 /** 158 * reset auth token 159 * 160 * @return string 161 */ 162 public function logout() { 163 if ($this->check_login()) { 164 $this->boxnetclient->log_out(); 165 } 166 return $this->print_login(); 167 } 168 169 /** 170 * Search files from box.net 171 * 172 * @param string $search_text 173 * @return mixed 174 */ 175 public function search($search_text, $page = 0) { 176 return $this->get_listing($this->build_part('search', $search_text)); 177 } 178 179 /** 180 * Downloads a repository file and saves to a path. 181 * 182 * @param string $ref reference to the file 183 * @param string $filename to save file as 184 * @return array 185 */ 186 public function get_file($ref, $filename = '') { 187 global $CFG; 188 189 $ref = unserialize(self::convert_to_valid_reference($ref)); 190 $path = $this->prepare_file($filename); 191 if (!empty($ref->downloadurl)) { 192 $c = new curl(); 193 $result = $c->download_one($ref->downloadurl, null, array('filepath' => $filename, 194 'timeout' => $CFG->repositorygetfiletimeout, 'followlocation' => true)); 195 $info = $c->get_info(); 196 if ($result !== true || !isset($info['http_code']) || $info['http_code'] != 200) { 197 throw new moodle_exception('errorwhiledownload', 'repository', '', $result); 198 } 199 } else { 200 if (!$this->boxnetclient->download_file($ref->fileid, $path)) { 201 throw new moodle_exception('cannotdownload', 'repository'); 202 } 203 } 204 return array('path' => $path); 205 } 206 207 /** 208 * Get file listing 209 * 210 * @param string $path 211 * @param string $page 212 * @return mixed 213 */ 214 public function get_listing($fullpath = '', $page = ''){ 215 global $OUTPUT; 216 217 $ret = array(); 218 $ret['list'] = array(); 219 $ret['manage'] = self::MANAGE_URL; 220 $ret['dynload'] = true; 221 222 $crumbs = explode('/', $fullpath); 223 $path = array_pop($crumbs); 224 225 if (empty($path)) { 226 $type = 'folder'; 227 $pathid = 0; 228 $pathname = get_string('pluginname', 'repository_boxnet'); 229 } else { 230 list($type, $pathid, $pathname) = $this->split_part($path); 231 } 232 233 $ret['path'] = $this->build_breadcrumb($fullpath); 234 $folders = array(); 235 $files = array(); 236 237 if ($type == 'search') { 238 $result = $this->boxnetclient->search($pathname); 239 } else { 240 $result = $this->boxnetclient->get_folder_items($pathid); 241 } 242 foreach ($result->entries as $item) { 243 if ($item->type == 'folder') { 244 $folders[$item->name . ':' . $item->id] = array( 245 'title' => $item->name, 246 'path' => $fullpath . '/' . $this->build_part('folder', $item->id, $item->name), 247 'date' => strtotime($item->modified_at), 248 'thumbnail' => $OUTPUT->image_url(file_folder_icon(64))->out(false), 249 'thumbnail_height' => 64, 250 'thumbnail_width' => 64, 251 'children' => array(), 252 'size' => $item->size, 253 ); 254 } else { 255 $files[$item->name . ':' . $item->id] = array( 256 'title' => $item->name, 257 'source' => $this->build_part('file', $item->id, $item->name), 258 'size' => $item->size, 259 'date' => strtotime($item->modified_at), 260 'thumbnail' => $OUTPUT->image_url(file_extension_icon($item->name, 64))->out(false), 261 'thumbnail_height' => 64, 262 'thumbnail_width' => 64, 263 'author' => $item->owned_by->name, 264 ); 265 } 266 } 267 268 core_collator::ksort($folders, core_collator::SORT_NATURAL); 269 core_collator::ksort($files, core_collator::SORT_NATURAL); 270 $ret['list'] = array_merge($folders, $files); 271 $ret['list'] = array_filter($ret['list'], array($this, 'filter')); 272 273 return $ret; 274 } 275 276 /** 277 * Return login form 278 * 279 * @return array 280 */ 281 public function print_login(){ 282 $url = $this->boxnetclient->get_login_url(); 283 if ($this->options['ajax']) { 284 $ret = array(); 285 $popup_btn = new stdClass(); 286 $popup_btn->type = 'popup'; 287 $popup_btn->url = $url->out(false); 288 $ret['login'] = array($popup_btn); 289 return $ret; 290 } else { 291 echo html_writer::link($url, get_string('login', 'repository'), array('target' => '_blank')); 292 } 293 } 294 295 /** 296 * Names of the plugin settings 297 * 298 * @return array 299 */ 300 public static function get_type_option_names() { 301 return array('clientid', 'clientsecret', 'pluginname'); 302 } 303 304 /** 305 * Catch the request token. 306 */ 307 public function callback() { 308 $this->boxnetclient->is_logged_in(); 309 } 310 311 /** 312 * Add Plugin settings input to Moodle form 313 * 314 * @param moodleform $mform 315 * @param string $classname 316 */ 317 public static function type_config_form($mform, $classname = 'repository') { 318 global $CFG; 319 parent::type_config_form($mform); 320 321 $clientid = get_config('boxnet', 'clientid'); 322 $clientsecret = get_config('boxnet', 'clientsecret'); 323 $strrequired = get_string('required'); 324 325 $mform->addElement('text', 'clientid', get_string('clientid', 'repository_boxnet'), 326 array('value' => $clientid, 'size' => '40')); 327 $mform->addRule('clientid', $strrequired, 'required', null, 'client'); 328 $mform->setType('clientid', PARAM_RAW_TRIMMED); 329 330 $mform->addElement('text', 'clientsecret', get_string('clientsecret', 'repository_boxnet'), 331 array('value' => $clientsecret, 'size' => '40')); 332 $mform->addRule('clientsecret', $strrequired, 'required', null, 'client'); 333 $mform->setType('clientsecret', PARAM_RAW_TRIMMED); 334 335 $mform->addElement('static', null, '', get_string('information', 'repository_boxnet')); 336 337 if (!is_https()) { 338 $mform->addElement('static', null, '', get_string('warninghttps', 'repository_boxnet')); 339 } 340 } 341 342 /** 343 * Box.net supports copied and links. 344 * 345 * Theoretically this API is ready for references, though it only works for 346 * Box.net Business accounts, but it is not enabled because we are not supporting it. 347 * 348 * @return int 349 */ 350 public function supported_returntypes() { 351 return FILE_INTERNAL | FILE_EXTERNAL; 352 } 353 354 /** 355 * Convert a reference to the new reference style. 356 * 357 * While converting Box.net to APIv2 we introduced a new format for 358 * file references, see {@link self::get_file_reference()}. This function 359 * ensures that the format is always the same regardless of the whether 360 * the reference was from APIv1 or v2. 361 * 362 * @param mixed $reference File reference. 363 * @return stdClass Valid file reference. 364 */ 365 public static function convert_to_valid_reference($reference) { 366 if (strpos($reference, 'http') === 0) { 367 // It is faster to check if the reference is a URL rather than trying to unserialize it. 368 $reference = serialize((object) array('downloadurl' => $reference, 'fileid' => '', 'filename' => '', 'userid' => '')); 369 } 370 return $reference; 371 } 372 373 /** 374 * Prepare file reference information 375 * 376 * @param string $source 377 * @return string file referece 378 */ 379 public function get_file_reference($source) { 380 global $USER; 381 list($type, $fileid, $filename) = $this->split_part($source); 382 $reference = new stdClass(); 383 $reference->fileid = $fileid; 384 $reference->filename = $filename; 385 $reference->userid = $USER->id; 386 $reference->downloadurl = ''; 387 if (optional_param('usefilereference', false, PARAM_BOOL)) { 388 try { 389 $shareinfo = $this->boxnetclient->share_file($reference->fileid); 390 } catch (moodle_exception $e) { 391 throw new repository_exception('cannotcreatereference', 'repository_boxnet'); 392 } 393 $reference->downloadurl = $shareinfo->download_url; 394 } 395 return serialize($reference); 396 } 397 398 /** 399 * Get a link to the file. 400 * 401 * This returns the URL of the web view of the file. To generate this link the 402 * file must be shared. 403 * 404 * @param stdClass $reference Reference. 405 * @return string URL. 406 */ 407 public function get_link($reference) { 408 $reference = unserialize(self::convert_to_valid_reference($reference)); 409 $shareinfo = $this->boxnetclient->share_file($reference->fileid, false); 410 return $shareinfo->url; 411 } 412 413 /** 414 * Synchronize the references. 415 * 416 * @param stored_file $file Stored file. 417 * @return boolean 418 */ 419 public function sync_reference(stored_file $file) { 420 global $CFG; 421 if ($file->get_referencelastsync() + DAYSECS > time()) { 422 // Synchronise not more often than once a day. 423 return false; 424 } 425 $c = new curl(); 426 $reference = unserialize(self::convert_to_valid_reference($file->get_reference())); 427 $url = $reference->downloadurl; 428 if (file_extension_in_typegroup($file->get_filename(), 'web_image')) { 429 $path = $this->prepare_file(''); 430 $result = $c->download_one($url, null, array('filepath' => $path, 'timeout' => $CFG->repositorysyncimagetimeout)); 431 $info = $c->get_info(); 432 if ($result === true && isset($info['http_code']) && $info['http_code'] == 200) { 433 $file->set_synchronised_content_from_file($path); 434 return true; 435 } 436 } 437 $c->get($url, null, array('timeout' => $CFG->repositorysyncimagetimeout, 'followlocation' => true, 'nobody' => true)); 438 $info = $c->get_info(); 439 if (isset($info['http_code']) && $info['http_code'] == 200 && 440 array_key_exists('download_content_length', $info) && 441 $info['download_content_length'] >= 0) { 442 $filesize = (int)$info['download_content_length']; 443 $file->set_synchronized(null, $filesize); 444 return true; 445 } 446 $file->set_missingsource(); 447 return true; 448 } 449 450 /** 451 * Return human readable reference information 452 * {@link stored_file::get_reference()} 453 * 454 * @param string $reference 455 * @param int $filestatus status of the file, 0 - ok, 666 - source missing 456 * @return string 457 */ 458 public function get_reference_details($reference, $filestatus = 0) { 459 // Indicate it's from box.net repository. 460 $reference = unserialize(self::convert_to_valid_reference($reference)); 461 if (!$filestatus) { 462 return $this->get_name() . ': ' . $reference->filename; 463 } else { 464 return get_string('lostsource', 'repository', $reference->filename); 465 } 466 } 467 468 /** 469 * Return the source information. 470 * 471 * @param string $source Not the reference, just the source. 472 * @return string|null 473 */ 474 public function get_file_source_info($source) { 475 global $USER; 476 list($type, $fileid, $filename) = $this->split_part($source); 477 return 'Box ('. fullname($USER) . '): ' . $filename; 478 } 479 480 /** 481 * Repository method to serve the referenced file 482 * 483 * @param stored_file $storedfile the file that contains the reference 484 * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime) 485 * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only 486 * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin 487 * @param array $options additional options affecting the file serving 488 */ 489 public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) { 490 $ref = unserialize(self::convert_to_valid_reference($storedfile->get_reference())); 491 header('Location: ' . $ref->downloadurl); 492 } 493 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body