/repository/googledocs/lib.php
PHP | 1206 lines | 722 code | 126 blank | 358 comment | 110 complexity | 6b7c14d87952a375c9bc457c53757ec0 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
- <?php
- // This file is part of Moodle - http://moodle.org/
- //
- // Moodle is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // Moodle is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
- /**
- * This plugin is used to access Google Drive.
- *
- * @since Moodle 2.0
- * @package repository_googledocs
- * @copyright 2009 Dan Poltawski <talktodan@gmail.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- defined('MOODLE_INTERNAL') || die();
- require_once($CFG->dirroot . '/repository/lib.php');
- require_once($CFG->libdir . '/filebrowser/file_browser.php');
- use repository_googledocs\helper;
- use repository_googledocs\googledocs_content_search;
- /**
- * Google Docs Plugin
- *
- * @since Moodle 2.0
- * @package repository_googledocs
- * @copyright 2009 Dan Poltawski <talktodan@gmail.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class repository_googledocs extends repository {
- /**
- * OAuth 2 client
- * @var \core\oauth2\client
- */
- private $client = null;
- /**
- * OAuth 2 Issuer
- * @var \core\oauth2\issuer
- */
- private $issuer = null;
- /**
- * Additional scopes required for drive.
- */
- const SCOPES = 'https://www.googleapis.com/auth/drive';
- /** @var string Defines the path node identifier for the repository root. */
- const REPOSITORY_ROOT_ID = 'repository_root';
- /** @var string Defines the path node identifier for the my drive root. */
- const MY_DRIVE_ROOT_ID = 'root';
- /** @var string Defines the path node identifier for the shared drives root. */
- const SHARED_DRIVES_ROOT_ID = 'shared_drives_root';
- /** @var string Defines the path node identifier for the content search root. */
- const SEARCH_ROOT_ID = 'search';
- /**
- * Constructor.
- *
- * @param int $repositoryid repository instance id.
- * @param int|stdClass $context a context id or context object.
- * @param array $options repository options.
- * @param int $readonly indicate this repo is readonly or not.
- * @return void
- */
- public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array(), $readonly = 0) {
- parent::__construct($repositoryid, $context, $options, $readonly = 0);
- try {
- $this->issuer = \core\oauth2\api::get_issuer(get_config('googledocs', 'issuerid'));
- } catch (dml_missing_record_exception $e) {
- $this->disabled = true;
- }
- if ($this->issuer && !$this->issuer->get('enabled')) {
- $this->disabled = true;
- }
- }
- /**
- * Get a cached user authenticated oauth client.
- *
- * @param moodle_url $overrideurl - Use this url instead of the repo callback.
- * @return \core\oauth2\client
- */
- protected function get_user_oauth_client($overrideurl = false) {
- if ($this->client) {
- return $this->client;
- }
- if ($overrideurl) {
- $returnurl = $overrideurl;
- } else {
- $returnurl = new moodle_url('/repository/repository_callback.php');
- $returnurl->param('callback', 'yes');
- $returnurl->param('repo_id', $this->id);
- $returnurl->param('sesskey', sesskey());
- }
- $this->client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, self::SCOPES, true);
- return $this->client;
- }
- /**
- * Checks whether the user is authenticate or not.
- *
- * @return bool true when logged in.
- */
- public function check_login() {
- $client = $this->get_user_oauth_client();
- return $client->is_logged_in();
- }
- /**
- * Print or return the login form.
- *
- * @return void|array for ajax.
- */
- public function print_login() {
- $client = $this->get_user_oauth_client();
- $url = $client->get_login_url();
- if ($this->options['ajax']) {
- $popup = new stdClass();
- $popup->type = 'popup';
- $popup->url = $url->out(false);
- return array('login' => array($popup));
- } else {
- echo '<a target="_blank" href="'.$url->out(false).'">'.get_string('login', 'repository').'</a>';
- }
- }
- /**
- * Print the login in a popup.
- *
- * @param array|null $attr Custom attributes to be applied to popup div.
- */
- public function print_login_popup($attr = null) {
- global $OUTPUT, $PAGE;
- $client = $this->get_user_oauth_client(false);
- $url = new moodle_url($client->get_login_url());
- $state = $url->get_param('state') . '&reloadparent=true';
- $url->param('state', $state);
- $PAGE->set_pagelayout('embedded');
- echo $OUTPUT->header();
- $repositoryname = get_string('pluginname', 'repository_googledocs');
- $button = new single_button($url, get_string('logintoaccount', 'repository', $repositoryname), 'post', true);
- $button->add_action(new popup_action('click', $url, 'Login'));
- $button->class = 'mdl-align';
- $button = $OUTPUT->render($button);
- echo html_writer::div($button, '', $attr);
- echo $OUTPUT->footer();
- }
- /**
- * Build the breadcrumb from a path.
- *
- * @deprecated since Moodle 3.11.
- * @param string $path to create a breadcrumb from.
- * @return array containing name and path of each crumb.
- */
- protected function build_breadcrumb($path) {
- debugging('The function build_breadcrumb() is deprecated, please use get_navigation() from the ' .
- 'googledocs repository content classes instead.', DEBUG_DEVELOPER);
- $bread = explode('/', $path);
- $crumbtrail = '';
- foreach ($bread as $crumb) {
- list($id, $name) = $this->explode_node_path($crumb);
- $name = empty($name) ? $id : $name;
- $breadcrumb[] = array(
- 'name' => $name,
- 'path' => $this->build_node_path($id, $name, $crumbtrail)
- );
- $tmp = end($breadcrumb);
- $crumbtrail = $tmp['path'];
- }
- return $breadcrumb;
- }
- /**
- * Generates a safe path to a node.
- *
- * Typically, a node will be id|Name of the node.
- *
- * @deprecated since Moodle 3.11.
- * @param string $id of the node.
- * @param string $name of the node, will be URL encoded.
- * @param string $root to append the node on, must be a result of this function.
- * @return string path to the node.
- */
- protected function build_node_path($id, $name = '', $root = '') {
- debugging('The function build_node_path() is deprecated, please use ' .
- '\repository_googledocs\helper::build_node_path() instead.', DEBUG_DEVELOPER);
- $path = $id;
- if (!empty($name)) {
- $path .= '|' . urlencode($name);
- }
- if (!empty($root)) {
- $path = trim($root, '/') . '/' . $path;
- }
- return $path;
- }
- /**
- * Returns information about a node in a path.
- *
- * @deprecated since Moodle 3.11.
- * @see self::build_node_path()
- * @param string $node to extrat information from.
- * @return array about the node.
- */
- protected function explode_node_path($node) {
- debugging('The function explode_node_path() is deprecated, please use ' .
- '\repository_googledocs\helper::explode_node_path() instead.', DEBUG_DEVELOPER);
- if (strpos($node, '|') !== false) {
- list($id, $name) = explode('|', $node, 2);
- $name = urldecode($name);
- } else {
- $id = $node;
- $name = '';
- }
- $id = urldecode($id);
- return array(
- 0 => $id,
- 1 => $name,
- 'id' => $id,
- 'name' => $name
- );
- }
- /**
- * List the files and folders.
- *
- * @param string $path path to browse.
- * @param string $page page to browse.
- * @return array of result.
- */
- public function get_listing($path='', $page = '') {
- if (empty($path)) {
- $pluginname = get_string('pluginname', 'repository_googledocs');
- $path = helper::build_node_path('repository_root', $pluginname);
- }
- if (!$this->issuer->get('enabled')) {
- // Empty list of files for disabled repository.
- return [
- 'dynload' => false,
- 'list' => [],
- 'nologin' => true,
- ];
- }
- // We analyse the path to extract what to browse.
- $trail = explode('/', $path);
- $uri = array_pop($trail);
- list($id, $name) = helper::explode_node_path($uri);
- $service = new repository_googledocs\rest($this->get_user_oauth_client());
- // Define the content class object and query which will be used to get the contents for this path.
- if ($id === self::SEARCH_ROOT_ID) {
- // The special keyword 'search' is the ID of the node. This is possible as we can set up a breadcrumb in
- // the search results. Therefore, we should use the content search object to get the results from the
- // previously performed search.
- $contentobj = new googledocs_content_search($service, $path);
- // We need to deconstruct the node name in order to obtain the search term and use it as a query.
- $query = str_replace(get_string('searchfor', 'repository_googledocs'), '', $name);
- $query = trim(str_replace("'", "", $query));
- } else {
- // Otherwise, return and use the appropriate (based on the path) content browser object.
- $contentobj = helper::get_browser($service, $path);
- // Use the node ID as a query.
- $query = $id;
- }
- return [
- 'dynload' => true,
- 'defaultreturntype' => $this->default_returntype(),
- 'path' => $contentobj->get_navigation(),
- 'list' => $contentobj->get_content_nodes($query, [$this, 'filter']),
- 'manage' => 'https://drive.google.com/',
- ];
- }
- /**
- * Search throughout the Google Drive.
- *
- * @param string $searchtext text to search for.
- * @param int $page search page.
- * @return array of results.
- */
- public function search($searchtext, $page = 0) {
- // Construct the path to the repository root.
- $pluginname = get_string('pluginname', 'repository_googledocs');
- $rootpath = helper::build_node_path(self::REPOSITORY_ROOT_ID, $pluginname);
- // Construct the path to the search results node.
- // Currently, when constructing the search node name, the search term is concatenated to the lang string.
- // This was done deliberately so that we can easily and accurately obtain the search term from the search node
- // name later when navigating to the search results through the breadcrumb navigation.
- $name = get_string('searchfor', 'repository_googledocs') . " '{$searchtext}'";
- $path = helper::build_node_path(self::SEARCH_ROOT_ID, $name, $rootpath);
- $service = new repository_googledocs\rest($this->get_user_oauth_client());
- $searchobj = new googledocs_content_search($service, $path);
- return [
- 'dynload' => true,
- 'path' => $searchobj->get_navigation(),
- 'list' => $searchobj->get_content_nodes($searchtext, [$this, 'filter']),
- 'manage' => 'https://drive.google.com/',
- ];
- }
- /**
- * Query Google Drive for files and folders using a search query.
- *
- * Documentation about the query format can be found here:
- * https://developers.google.com/drive/search-parameters
- *
- * This returns a list of files and folders with their details as they should be
- * formatted and returned by functions such as get_listing() or search().
- *
- * @deprecated since Moodle 3.11.
- * @param string $q search query as expected by the Google API.
- * @param string $path parent path of the current files, will not be used for the query.
- * @param int $page page.
- * @return array of files and folders.
- */
- protected function query($q, $path = null, $page = 0) {
- debugging('The function query() is deprecated, please use get_content_nodes() from the ' .
- 'googledocs repository content classes instead.', DEBUG_DEVELOPER);
- global $OUTPUT;
- $files = array();
- $folders = array();
- $config = get_config('googledocs');
- $fields = "files(id,name,mimeType,webContentLink,webViewLink,fileExtension,modifiedTime,size,thumbnailLink,iconLink)";
- $params = array('q' => $q, 'fields' => $fields, 'spaces' => 'drive');
- try {
- // Retrieving files and folders.
- $client = $this->get_user_oauth_client();
- $service = new repository_googledocs\rest($client);
- $response = $service->call('list', $params);
- } catch (Exception $e) {
- if ($e->getCode() == 403 && strpos($e->getMessage(), 'Access Not Configured') !== false) {
- // This is raised when the service Drive API has not been enabled on Google APIs control panel.
- throw new repository_exception('servicenotenabled', 'repository_googledocs');
- } else {
- throw $e;
- }
- }
- $gfiles = isset($response->files) ? $response->files : array();
- foreach ($gfiles as $gfile) {
- if ($gfile->mimeType == 'application/vnd.google-apps.folder') {
- // This is a folder.
- $folders[$gfile->name . $gfile->id] = array(
- 'title' => $gfile->name,
- 'path' => $this->build_node_path($gfile->id, $gfile->name, $path),
- 'date' => strtotime($gfile->modifiedTime),
- 'thumbnail' => $OUTPUT->image_url(file_folder_icon(64))->out(false),
- 'thumbnail_height' => 64,
- 'thumbnail_width' => 64,
- 'children' => array()
- );
- } else {
- // This is a file.
- $link = isset($gfile->webViewLink) ? $gfile->webViewLink : '';
- if (empty($link)) {
- $link = isset($gfile->webContentLink) ? $gfile->webContentLink : '';
- }
- if (isset($gfile->fileExtension)) {
- // The file has an extension, therefore we can download it.
- $source = json_encode([
- 'id' => $gfile->id,
- 'name' => $gfile->name,
- 'exportformat' => 'download',
- 'link' => $link
- ]);
- $title = $gfile->name;
- } else {
- // The file is probably a Google Doc file, we get the corresponding export link.
- // This should be improved by allowing the user to select the type of export they'd like.
- $type = str_replace('application/vnd.google-apps.', '', $gfile->mimeType);
- $title = '';
- $exporttype = '';
- $types = get_mimetypes_array();
- switch ($type){
- case 'document':
- $ext = $config->documentformat;
- $title = $gfile->name . '.gdoc';
- if ($ext === 'rtf') {
- // Moodle user 'text/rtf' as the MIME type for RTF files.
- // Google uses 'application/rtf' for the same type of file.
- // See https://developers.google.com/drive/v3/web/manage-downloads.
- $exporttype = 'application/rtf';
- } else {
- $exporttype = $types[$ext]['type'];
- }
- break;
- case 'presentation':
- $ext = $config->presentationformat;
- $title = $gfile->name . '.gslides';
- $exporttype = $types[$ext]['type'];
- break;
- case 'spreadsheet':
- $ext = $config->spreadsheetformat;
- $title = $gfile->name . '.gsheet';
- $exporttype = $types[$ext]['type'];
- break;
- case 'drawing':
- $ext = $config->drawingformat;
- $title = $gfile->name . '.'. $ext;
- $exporttype = $types[$ext]['type'];
- break;
- }
- // Skips invalid/unknown types.
- if (empty($title)) {
- continue;
- }
- $source = json_encode([
- 'id' => $gfile->id,
- 'exportformat' => $exporttype,
- 'link' => $link,
- 'name' => $gfile->name
- ]);
- }
- // Adds the file to the file list. Using the itemId along with the name as key
- // of the array because Google Drive allows files with identical names.
- $thumb = '';
- if (isset($gfile->thumbnailLink)) {
- $thumb = $gfile->thumbnailLink;
- } else if (isset($gfile->iconLink)) {
- $thumb = $gfile->iconLink;
- }
- $files[$title . $gfile->id] = array(
- 'title' => $title,
- 'source' => $source,
- 'date' => strtotime($gfile->modifiedTime),
- 'size' => isset($gfile->size) ? $gfile->size : null,
- 'thumbnail' => $thumb,
- 'thumbnail_height' => 64,
- 'thumbnail_width' => 64,
- );
- }
- }
- // Filter and order the results.
- $files = array_filter($files, array($this, 'filter'));
- core_collator::ksort($files, core_collator::SORT_NATURAL);
- core_collator::ksort($folders, core_collator::SORT_NATURAL);
- return array_merge(array_values($folders), array_values($files));
- }
- /**
- * Logout.
- *
- * @return string
- */
- public function logout() {
- $client = $this->get_user_oauth_client();
- $client->log_out();
- return parent::logout();
- }
- /**
- * Get a file.
- *
- * @param string $reference reference of the file.
- * @param string $file name to save the file to.
- * @return string JSON encoded array of information about the file.
- */
- public function get_file($reference, $filename = '') {
- global $CFG;
- if (!$this->issuer->get('enabled')) {
- throw new repository_exception('cannotdownload', 'repository');
- }
- $source = json_decode($reference);
- $client = null;
- if (!empty($source->usesystem)) {
- $client = \core\oauth2\api::get_system_oauth_client($this->issuer);
- } else {
- $client = $this->get_user_oauth_client();
- }
- $base = 'https://www.googleapis.com/drive/v3';
- $newfilename = false;
- if ($source->exportformat == 'download') {
- $params = ['alt' => 'media'];
- $sourceurl = new moodle_url($base . '/files/' . $source->id, $params);
- $source = $sourceurl->out(false);
- } else {
- $params = ['mimeType' => $source->exportformat];
- $sourceurl = new moodle_url($base . '/files/' . $source->id . '/export', $params);
- $types = get_mimetypes_array();
- $checktype = $source->exportformat;
- if ($checktype == 'application/rtf') {
- $checktype = 'text/rtf';
- }
- foreach ($types as $extension => $info) {
- if ($info['type'] == $checktype) {
- $newfilename = $source->name . '.' . $extension;
- break;
- }
- }
- $source = $sourceurl->out(false);
- }
- // We use download_one and not the rest API because it has special timeouts etc.
- $path = $this->prepare_file($filename);
- $options = ['filepath' => $path, 'timeout' => 15, 'followlocation' => true, 'maxredirs' => 5];
- $success = $client->download_one($source, null, $options);
- if ($success) {
- @chmod($path, $CFG->filepermissions);
- $result = [
- 'path' => $path,
- 'url' => $reference,
- ];
- if (!empty($newfilename)) {
- $result['newfilename'] = $newfilename;
- }
- return $result;
- }
- throw new repository_exception('cannotdownload', 'repository');
- }
- /**
- * Prepare file reference information.
- *
- * We are using this method to clean up the source to make sure that it
- * is a valid source.
- *
- * @param string $source of the file.
- * @return string file reference.
- */
- public function get_file_reference($source) {
- // We could do some magic upgrade code here.
- return $source;
- }
- /**
- * What kind of files will be in this repository?
- *
- * @return array return '*' means this repository support any files, otherwise
- * return mimetypes of files, it can be an array
- */
- public function supported_filetypes() {
- return '*';
- }
- /**
- * Tells how the file can be picked from this repository.
- *
- * @return int
- */
- public function supported_returntypes() {
- // We can only support references if the system account is connected.
- if (!empty($this->issuer) && $this->issuer->is_system_account_connected()) {
- $setting = get_config('googledocs', 'supportedreturntypes');
- if ($setting == 'internal') {
- return FILE_INTERNAL;
- } else if ($setting == 'external') {
- return FILE_CONTROLLED_LINK;
- } else {
- return FILE_CONTROLLED_LINK | FILE_INTERNAL;
- }
- } else {
- return FILE_INTERNAL;
- }
- }
- /**
- * Which return type should be selected by default.
- *
- * @return int
- */
- public function default_returntype() {
- $setting = get_config('googledocs', 'defaultreturntype');
- $supported = get_config('googledocs', 'supportedreturntypes');
- if (($setting == FILE_INTERNAL && $supported != 'external') || $supported == 'internal') {
- return FILE_INTERNAL;
- } else {
- return FILE_CONTROLLED_LINK;
- }
- }
- /**
- * Return names of the general options.
- * By default: no general option name.
- *
- * @return array
- */
- public static function get_type_option_names() {
- return array('issuerid', 'pluginname',
- 'documentformat', 'drawingformat',
- 'presentationformat', 'spreadsheetformat',
- 'defaultreturntype', 'supportedreturntypes');
- }
- /**
- * Store the access token.
- */
- public function callback() {
- $client = $this->get_user_oauth_client();
- // This will upgrade to an access token if we have an authorization code and save the access token in the session.
- $client->is_logged_in();
- }
- /**
- * Repository method to serve the referenced file
- *
- * @see send_stored_file
- *
- * @param stored_file $storedfile the file that contains the reference
- * @param int $lifetime Number of seconds before the file should expire from caches (null means $CFG->filelifetime)
- * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
- * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
- * @param array $options additional options affecting the file serving
- */
- public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
- if (!$this->issuer->get('enabled')) {
- throw new repository_exception('cannotdownload', 'repository');
- }
- $source = json_decode($storedfile->get_reference());
- $fb = get_file_browser();
- $context = context::instance_by_id($storedfile->get_contextid(), MUST_EXIST);
- $info = $fb->get_file_info($context,
- $storedfile->get_component(),
- $storedfile->get_filearea(),
- $storedfile->get_itemid(),
- $storedfile->get_filepath(),
- $storedfile->get_filename());
- if (empty($options['offline']) && !empty($info) && $info->is_writable() && !empty($source->usesystem)) {
- // Add the current user as an OAuth writer.
- $systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
- if ($systemauth === false) {
- $details = 'Cannot connect as system user';
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- $systemservice = new repository_googledocs\rest($systemauth);
- // Get the user oauth so we can get the account to add.
- $url = moodle_url::make_pluginfile_url($storedfile->get_contextid(),
- $storedfile->get_component(),
- $storedfile->get_filearea(),
- $storedfile->get_itemid(),
- $storedfile->get_filepath(),
- $storedfile->get_filename(),
- $forcedownload);
- $url->param('sesskey', sesskey());
- $param = ($options['embed'] == true) ? false : $url;
- $userauth = $this->get_user_oauth_client($param);
- if (!$userauth->is_logged_in()) {
- if ($options['embed'] == true) {
- // Due to Same-origin policy, we cannot redirect to googledocs login page.
- // If the requested file is embed and the user is not logged in, add option to log in using a popup.
- $this->print_login_popup(['style' => 'margin-top: 250px']);
- exit;
- }
- redirect($userauth->get_login_url());
- }
- if ($userauth === false) {
- $details = 'Cannot connect as current user';
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- $userinfo = $userauth->get_userinfo();
- $useremail = $userinfo['email'];
- $this->add_temp_writer_to_file($systemservice, $source->id, $useremail);
- }
- if (!empty($options['offline'])) {
- $downloaded = $this->get_file($storedfile->get_reference(), $storedfile->get_filename());
- $filename = $storedfile->get_filename();
- if (isset($downloaded['newfilename'])) {
- $filename = $downloaded['newfilename'];
- }
- send_file($downloaded['path'], $filename, $lifetime, $filter, false, $forcedownload, '', false, $options);
- } else if ($source->link) {
- // Do not use redirect() here because is not compatible with webservice/pluginfile.php.
- header('Location: ' . $source->link);
- } else {
- $details = 'File is missing source link';
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- }
- /**
- * See if a folder exists within a folder
- *
- * @param \repository_googledocs\rest $client Authenticated client.
- * @param string $foldername The folder we are looking for.
- * @param string $parentid The parent folder we are looking in.
- * @return string|boolean The file id if it exists or false.
- */
- protected function folder_exists_in_folder(\repository_googledocs\rest $client, $foldername, $parentid) {
- $q = '\'' . addslashes($parentid) . '\' in parents and trashed = false and name = \'' . addslashes($foldername). '\'';
- $fields = 'files(id, name)';
- $params = [ 'q' => $q, 'fields' => $fields];
- $response = $client->call('list', $params);
- $missing = true;
- foreach ($response->files as $child) {
- if ($child->name == $foldername) {
- return $child->id;
- }
- }
- return false;
- }
- /**
- * Create a folder within a folder
- *
- * @param \repository_googledocs\rest $client Authenticated client.
- * @param string $foldername The folder we are creating.
- * @param string $parentid The parent folder we are creating in.
- *
- * @return string The file id of the new folder.
- */
- protected function create_folder_in_folder(\repository_googledocs\rest $client, $foldername, $parentid) {
- $fields = 'id';
- $params = ['fields' => $fields];
- $folder = ['mimeType' => 'application/vnd.google-apps.folder', 'name' => $foldername, 'parents' => [$parentid]];
- $created = $client->call('create', $params, json_encode($folder));
- if (empty($created->id)) {
- $details = 'Cannot create folder:' . $foldername;
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- return $created->id;
- }
- /**
- * Get simple file info for humans.
- *
- * @param \repository_googledocs\rest $client Authenticated client.
- * @param string $fileid The file we are querying.
- *
- * @return stdClass
- */
- protected function get_file_summary(\repository_googledocs\rest $client, $fileid) {
- $fields = "id,name,owners,parents";
- $params = [
- 'fileid' => $fileid,
- 'fields' => $fields
- ];
- return $client->call('get', $params);
- }
- /**
- * Copy a file and return the new file details. A side effect of the copy
- * is that the owner will be the account authenticated with this oauth client.
- *
- * @param \repository_googledocs\rest $client Authenticated client.
- * @param string $fileid The file we are copying.
- * @param string $name The original filename (don't change it).
- *
- * @return stdClass file details.
- */
- protected function copy_file(\repository_googledocs\rest $client, $fileid, $name) {
- $fields = "id,name,mimeType,webContentLink,webViewLink,size,thumbnailLink,iconLink";
- $params = [
- 'fileid' => $fileid,
- 'fields' => $fields,
- ];
- // Keep the original name (don't put copy at the end of it).
- $copyinfo = [];
- if (!empty($name)) {
- $copyinfo = [ 'name' => $name ];
- }
- $fileinfo = $client->call('copy', $params, json_encode($copyinfo));
- if (empty($fileinfo->id)) {
- $details = 'Cannot copy file:' . $fileid;
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- return $fileinfo;
- }
- /**
- * Add a writer to the permissions on the file (temporary).
- *
- * @param \repository_googledocs\rest $client Authenticated client.
- * @param string $fileid The file we are updating.
- * @param string $email The email of the writer account to add.
- * @return boolean
- */
- protected function add_temp_writer_to_file(\repository_googledocs\rest $client, $fileid, $email) {
- // Expires in 7 days.
- $expires = new DateTime();
- $expires->add(new DateInterval("P7D"));
- $updateeditor = [
- 'emailAddress' => $email,
- 'role' => 'writer',
- 'type' => 'user',
- 'expirationTime' => $expires->format(DateTime::RFC3339)
- ];
- $params = ['fileid' => $fileid, 'sendNotificationEmail' => 'false'];
- $response = $client->call('create_permission', $params, json_encode($updateeditor));
- if (empty($response->id)) {
- $details = 'Cannot add user ' . $email . ' as a writer for document: ' . $fileid;
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- return true;
- }
- /**
- * Add a writer to the permissions on the file.
- *
- * @param \repository_googledocs\rest $client Authenticated client.
- * @param string $fileid The file we are updating.
- * @param string $email The email of the writer account to add.
- * @return boolean
- */
- protected function add_writer_to_file(\repository_googledocs\rest $client, $fileid, $email) {
- $updateeditor = [
- 'emailAddress' => $email,
- 'role' => 'writer',
- 'type' => 'user'
- ];
- $params = ['fileid' => $fileid, 'sendNotificationEmail' => 'false'];
- $response = $client->call('create_permission', $params, json_encode($updateeditor));
- if (empty($response->id)) {
- $details = 'Cannot add user ' . $email . ' as a writer for document: ' . $fileid;
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- return true;
- }
- /**
- * Move from root to folder
- *
- * @param \repository_googledocs\rest $client Authenticated client.
- * @param string $fileid The file we are updating.
- * @param string $folderid The id of the folder we are moving to
- * @return boolean
- */
- protected function move_file_from_root_to_folder(\repository_googledocs\rest $client, $fileid, $folderid) {
- // Set the parent.
- $params = [
- 'fileid' => $fileid, 'addParents' => $folderid, 'removeParents' => 'root'
- ];
- $response = $client->call('update', $params, ' ');
- if (empty($response->id)) {
- $details = 'Cannot move the file to a folder: ' . $fileid;
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- return true;
- }
- /**
- * Prevent writers from sharing.
- *
- * @param \repository_googledocs\rest $client Authenticated client.
- * @param string $fileid The file we are updating.
- * @return boolean
- */
- protected function prevent_writers_from_sharing_file(\repository_googledocs\rest $client, $fileid) {
- // We don't want anyone but Moodle to change the sharing settings.
- $params = [
- 'fileid' => $fileid
- ];
- $update = [
- 'writersCanShare' => false
- ];
- $response = $client->call('update', $params, json_encode($update));
- if (empty($response->id)) {
- $details = 'Cannot prevent writers from sharing document: ' . $fileid;
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- return true;
- }
- /**
- * Allow anyone with the link to read the file.
- *
- * @param \repository_googledocs\rest $client Authenticated client.
- * @param string $fileid The file we are updating.
- * @return boolean
- */
- protected function set_file_sharing_anyone_with_link_can_read(\repository_googledocs\rest $client, $fileid) {
- $updateread = [
- 'type' => 'anyone',
- 'role' => 'reader',
- 'allowFileDiscovery' => 'false'
- ];
- $params = ['fileid' => $fileid];
- $response = $client->call('create_permission', $params, json_encode($updateread));
- if (empty($response->id) || $response->id != 'anyoneWithLink') {
- $details = 'Cannot update link sharing for the document: ' . $fileid;
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- return true;
- }
- /**
- * Called when a file is selected as a "link".
- * Invoked at MOODLE/repository/repository_ajax.php
- *
- * This is called at the point the reference files are being copied from the draft area to the real area
- * (when the file has really really been selected.
- *
- * @param string $reference this reference is generated by
- * repository::get_file_reference()
- * @param context $context the target context for this new file.
- * @param string $component the target component for this new file.
- * @param string $filearea the target filearea for this new file.
- * @param string $itemid the target itemid for this new file.
- * @return string updated reference (final one before it's saved to db).
- */
- public function reference_file_selected($reference, $context, $component, $filearea, $itemid) {
- global $CFG, $SITE;
- // What we need to do here is transfer ownership to the system user (or copy)
- // then set the permissions so anyone with the share link can view,
- // finally update the reference to contain the share link if it was not
- // already there (and point to new file id if we copied).
- // Get the details from the reference.
- $source = json_decode($reference);
- if (!empty($source->usesystem)) {
- // If we already copied this file to the system account - we are done.
- return $reference;
- }
- // Check this issuer is enabled.
- if ($this->disabled) {
- throw new repository_exception('cannotdownload', 'repository');
- }
- // Get a system oauth client and a user oauth client.
- $systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
- if ($systemauth === false) {
- $details = 'Cannot connect as system user';
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- // Get the system user email so we can share the file with this user.
- $systemuserinfo = $systemauth->get_userinfo();
- $systemuseremail = $systemuserinfo['email'];
- $userauth = $this->get_user_oauth_client();
- if ($userauth === false) {
- $details = 'Cannot connect as current user';
- throw new repository_exception('errorwhilecommunicatingwith', 'repository', '', $details);
- }
- $userservice = new repository_googledocs\rest($userauth);
- $systemservice = new repository_googledocs\rest($systemauth);
- // Add Moodle as writer.
- $this->add_writer_to_file($userservice, $source->id, $systemuseremail);
- // Now move it to a sensible folder.
- $contextlist = array_reverse($context->get_parent_contexts(true));
- $cache = cache::make('repository_googledocs', 'folder');
- $parentid = 'root';
- $fullpath = 'root';
- $allfolders = [];
- foreach ($contextlist as $context) {
- // Prepare human readable context folders names, making sure they are still unique within the site.
- $prevlang = force_current_language($CFG->lang);
- $foldername = $context->get_context_name();
- force_current_language($prevlang);
- if ($context->contextlevel == CONTEXT_SYSTEM) {
- // Append the site short name to the root folder.
- $foldername .= ' ('.$SITE->shortname.')';
- // Append the relevant object id.
- } else if ($context->instanceid) {
- $foldername .= ' (id '.$context->instanceid.')';
- } else {
- // This does not really happen but just in case.
- $foldername .= ' (ctx '.$context->id.')';
- }
- $foldername = clean_param($foldername, PARAM_PATH);
- $allfolders[] = $foldername;
- }
- $allfolders[] = clean_param($component, PARAM_PATH);
- $allfolders[] = clean_param($filearea, PARAM_PATH);
- $allfolders[] = clean_param($itemid, PARAM_PATH);
- // Variable $allfolders is the full path we want to put the file in - so walk it and create each folder.
- foreach ($allfolders as $foldername) {
- // Make sure a folder exists here.
- $fullpath .= '/' . $foldername;
- $folderid = $cache->get($fullpath);
- if (empty($folderid)) {
- $folderid = $this->folder_exists_in_folder($systemservice, $foldername, $parentid);
- }
- if ($folderid !== false) {
- $cache->set($fullpath, $folderid);
- $parentid = $folderid;
- } else {
- // Create it.
- $parentid = $this->create_folder_in_folder($systemservice, $foldername, $parentid);
- $cache->set($fullpath, $parentid);
- }
- }
- // Copy the file so we get a snapshot file owned by Moodle.
- $newsource = $this->copy_file($systemservice, $source->id, $source->name);
- // Move the copied file to the correct folder.
- $this->move_file_from_root_to_folder($systemservice, $newsource->id, $parentid);
- // Set the sharing options.
- $this->set_file_sharing_anyone_with_link_can_read($systemservice, $newsource->id);
- $this->prevent_writers_from_sharing_file($systemservice, $newsource->id);
- // Update the returned reference so that the stored_file in moodle points to the newly copied file.
- $source->id = $newsource->id;
- $source->link = isset($newsource->webViewLink) ? $newsource->webViewLink : '';
- $source->usesystem = true;
- if (empty($source->link)) {
- $source->link = isset($newsource->webContentLink) ? $newsource->webContentLink : '';
- }
- $reference = json_encode($source);
- return $reference;
- }
- /**
- * Get human readable file info from a the reference.
- *
- * @param string $reference
- * @param int $filestatus
- */
- public function get_reference_details($reference, $filestatus = 0) {
- if ($this->disabled) {
- throw new repository_exception('cannotdownload', 'repository');
- }
- if (empty($reference)) {
- return get_string('unknownsource', 'repository');
- }
- $source = json_decode($reference);
- if (empty($source->usesystem)) {
- return '';
- }
- $systemauth = \core\oauth2\api::get_system_oauth_client($this->issuer);
- if ($systemauth === false) {
- return '';
- }
- $systemservice = new repository_googledocs\rest($systemauth);
- $info = $this->get_file_summary($systemservice, $source->id);
- $owner = '';
- if (!empty($info->owners[0]->displayName)) {
- $owner = $info->owners[0]->displayName;
- }
- if ($owner) {
- return get_string('owner', 'repository_googledocs', $owner);
- } else {
- return $info->name;
- }
- }
- /**
- * Edit/Create Admin Settings Moodle form.
- *
- * @param moodleform $mform Moodle form (passed by reference).
- * @param string $classname repository class name.
- */
- public static function type_config_form($mform, $classname = 'repository') {
- $url = new moodle_url('/admin/tool/oauth2/issuers.php');
- $url = $url->out();
- $mform->addElement('static', null, '', get_string('oauth2serviceslink', 'repository_googledocs', $url));
- parent::type_config_form($mform);
- $options = [];
- $issuers = \core\oauth2\api::get_all_issuers();
- foreach ($issuers as $issuer) {
- $options[$issuer->get('id')] = s($issuer->get('name'));
- }
- $strrequired = get_string('required');
- $mform->addElement('select', 'issuerid', get_string('issuer', 'repository_googledocs'), $options);
- $mform->addHelpButton('issuerid', 'issuer', 'repository_googledocs');
- $mform->addRule('issuerid', $strrequired, 'required', null, 'client');
- $mform->addElement('static', null, '', get_string('fileoptions', 'repository_googledocs'));
- $choices = [
- 'internal' => get_string('internal', 'repository_googledocs'),
- 'external' => get_string('external', 'repository_googledocs'),
- 'both' => get_string('both', 'repository_googledocs')
- ];
- $mform->addElement('select', 'supportedreturntypes', get_string('supportedreturntypes', 'repository_googledocs'), $choices);
- $choices = [
- FILE_INTERNAL => get_string('internal', 'repository_googledocs'),
- FILE_CONTROLLED_LINK => get_string('external', 'repository_googledocs'),
- ];
- $mform->addElement('select', 'defaultreturntype', get_string('defaultreturntype', 'repository_googledocs'), $choices);
- $mform->addElement('static', null, '', get_string('importformat', 'repository_googledocs'));
- // Documents.
- $docsformat = array();
- $docsformat['html'] = 'html';
- $docsformat['docx'] = 'docx';
- $docsformat['odt'] = 'odt';
- $docsformat['pdf'] = 'pdf';
- $docsformat['rtf'] = 'rtf';
- $docsformat['txt'] = 'txt';
- core_collator::ksort($docsformat, core_collator::SORT_NATURAL);
- $mform->addElement('select', 'documentformat', get_string('docsformat', 'repository_googledocs'), $docsformat);
- $mform->setDefault('documentformat', $docsformat['rtf']);
- $mform->setType('documentformat', PARAM_ALPHANUM);
- // Drawing.
- $drawingformat = array();
- $drawingformat['jpeg'] = 'jpeg';
- $drawingformat['png'] = 'png';
- $drawingformat['svg'] = 'svg';
- $drawingformat['pdf'] = 'pdf';
- core_collator::ksort($drawingformat, core_collator::SORT_NATURAL);
- $mform->addElement('select', 'drawingformat', get_string('drawingformat', 'repository_googledocs'), $drawingformat);
- $mform->setDefault('drawingformat', $drawingformat['pdf']);
- $mform->setType('drawingformat', PARAM_ALPHANUM);
- // Presentation.
- $presentationformat = array();
- $presentationformat['pdf'] = 'pdf';
- $presentationformat['pptx'] = 'pptx';
- $presentationformat['txt'] = 'txt';
- core_collator::ksort($presentationformat, core_collator::SORT_NATURAL);
- $str = get_string('presentationformat', 'repository_googledocs');
- $mform->addElement('select', 'presentationformat', $str, $presentationformat);
- $mform->setDefault('presentationformat', $presentationformat['pptx']);
- $mform->setType('presentationformat', PARAM_ALPHANUM);
- // Spreadsheet.
- $spreadsheetformat = array();
- $spreadsheetformat['csv'] = 'csv';
- $spreadsheetformat['ods'] = 'ods';
- $spreadsheetformat['pdf'] = 'pdf';
- $spreadsheetformat['xlsx'] = 'xlsx';
- core_collator::ksort($spreadsheetformat, core_collator::SORT_NATURAL);
- $str = get_string('spreadsheetformat', 'repository_googledocs');
- $mform->addElement('select', 'spreadsheetformat', $str, $spreadsheetformat);
- $mform->setDefault('spreadsheetformat', $spreadsheetformat['xlsx']);
- $mform->setType('spreadsheetformat', PARAM_ALPHANUM);
- }
- }
- /**
- * Callback to get the required scopes for system account.
- *
- * @param \core\oauth2\issuer $issuer
- * @return string
- */
- function repository_googledocs_oauth2_system_scopes(\core\oauth2\issuer $issuer) {
- if ($issuer->get('id') == get_config('googledocs', 'issuerid')) {
- return 'https://www.googleapis.com/auth/drive';
- }
- return '';
- }