Skip to content

Commit

Permalink
MDL-61768 repository_googledocs: Support shared drives
Browse files Browse the repository at this point in the history
Enables the Google Drive repository to support browsing and searching
for content from the existing shared drives.
  • Loading branch information
Mihail Geshoski committed Apr 15, 2021
1 parent 2950a9a commit bded723
Show file tree
Hide file tree
Showing 13 changed files with 915 additions and 39 deletions.
120 changes: 120 additions & 0 deletions repository/googledocs/classes/googledocs_content.php
@@ -0,0 +1,120 @@
<?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/>.

namespace repository_googledocs;

/**
* Base class for presenting the googledocs repository contents.
*
* @package repository_googledocs
* @copyright 2021 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class googledocs_content {

/** @var rest The rest API object. */
protected $service;

/** @var string The current path. */
protected $path;

/** @var bool Whether sorting should be applied to the fetched content. */
protected $sortcontent;

/**
* Constructor.
*
* @param rest $service The rest API object
* @param string $path The current path
* @param bool $sortcontent Whether sorting should be applied to the content
*/
public function __construct(rest $service, string $path, bool $sortcontent = true) {
$this->service = $service;
$this->path = $path;
$this->sortcontent = $sortcontent;
}

/**
* Generate and return an array containing all repository node (files and folders) arrays for the existing content
* based on the path or search query.
*
* @param string $query The search query
* @param callable $isaccepted The callback function which determines whether a given file should be displayed
* or filtered based on the existing file restrictions
* @return array The array containing the repository content node arrays
*/
public function get_content_nodes(string $query, callable $isaccepted): array {
$files = [];
$folders = [];

foreach ($this->get_contents($query) as $gdcontent) {
$node = helper::get_node($gdcontent, $this->path);
// Create the repository node array.
$nodearray = $node->create_node_array();
// If the repository node array was successfully generated and the type of the content is accepted,
// add it to the repository content nodes array.
if ($nodearray && $isaccepted($nodearray)) {
// Group the content nodes by type (files and folders). Generate unique array keys for each content node
// which will be later used by the sorting function. Note: Using the item id along with the name as key
// of the array because Google Drive allows files and folders with identical names.
if (isset($nodearray['source'])) { // If the content node has a source attribute, it is a file node.
$files["{$nodearray['title']}{$nodearray['id']}"] = $nodearray;
} else {
$folders["{$nodearray['title']}{$nodearray['id']}"] = $nodearray;
}
}
}
// If sorting is required, order the results alphabetically by their array keys.
if ($this->sortcontent) {
\core_collator::ksort($files, \core_collator::SORT_STRING);
\core_collator::ksort($folders, \core_collator::SORT_STRING);
}

return array_merge(array_values($folders), array_values($files));
}

/**
* Build the navigation (breadcrumb) from a given path.
*
* @return array Array containing name and path of each navigation node
*/
public function get_navigation(): array {
$nav = [];
$navtrail = '';
$pathnodes = explode('/', $this->path);

foreach ($pathnodes as $node) {
list($id, $name) = helper::explode_node_path($node);
$name = empty($name) ? $id : $name;
$nav[] = [
'name' => $name,
'path' => helper::build_node_path($id, $name, $navtrail),
];
$tmp = end($nav);
$navtrail = $tmp['path'];
}

return $nav;
}

/**
* Returns all relevant contents (files and folders) based on the given path or search query.
*
* @param string $query The search query
* @return array The array containing the contents
*/
abstract protected function get_contents(string $query): array;
}
67 changes: 67 additions & 0 deletions repository/googledocs/classes/googledocs_content_search.php
@@ -0,0 +1,67 @@
<?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/>.

namespace repository_googledocs;

/**
* Utility class for displaying google drive content that matched a given search criteria.
*
* This class is responsible for generating the content that is returned based on a given search query.
*
* @package repository_googledocs
* @copyright 2021 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class googledocs_content_search extends googledocs_content {

/**
* Returns all relevant contents based on the given path and/or search query.
*
* The method fetches all content (files) through an API call that matches a given search criteria.
*
* @param string $query The search query
* @return array The array containing the contents
*/
protected function get_contents(string $query): array {
$searchterm = str_replace("'", "\'", $query);

// Define the parameters required by the API call.
// Query all contents which name contains $searchterm and have not been trashed.
$q = "fullText contains '{$searchterm}' AND trashed = false";
// The file fields that should be returned in the response.
$fields = "files(id,name,mimeType,webContentLink,webViewLink,fileExtension,modifiedTime,size,iconLink)";

$params = [
'q' => $q,
'fields' => $fields,
'spaces' => 'drive',
];

// If shared drives exist, include the additional required parameters in order to extend the content search
// into the shared drives area as well.
$response = helper::request($this->service, 'shared_drives_list', []);
if (!empty($response->drives)) {
$params['supportsAllDrives'] = 'true';
$params['includeItemsFromAllDrives'] = 'true';
$params['corpora'] = 'allDrives';
}

// Request the content through the API call.
$response = helper::request($this->service, 'list', $params);

return $response->files ?? [];
}
}
143 changes: 143 additions & 0 deletions repository/googledocs/classes/helper.php
@@ -0,0 +1,143 @@
<?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/>.

namespace repository_googledocs;

use repository_googledocs\local\browser\googledocs_root_content;
use repository_googledocs\local\browser\googledocs_shared_drives_content;
use repository_googledocs\local\browser\googledocs_drive_content;
use repository_googledocs\local\node\node;
use repository_googledocs\local\node\file_node;
use repository_googledocs\local\node\folder_node;

/**
* Helper class for the googledocs repository.
*
* @package repository_googledocs
* @copyright 2021 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {

/**
* Generates a safe path to a node.
*
* Typically, a node will be id|name of the node.
*
* @param string $id The ID of the node
* @param string $name The name of the node, will be URL encoded
* @param string $root The path to append the node on (must be a result of this function)
* @return string The path to the node
*/
public static function build_node_path(string $id, string $name = '', string $root = ''): string {
$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.
*
* @param string $node The node string to extract information from
* @return array The array containing the information about the node
* @see self::build_node_path()
*/
public static function explode_node_path(string $node): array {
if (strpos($node, '|') !== false) {
list($id, $name) = explode('|', $node, 2);
$name = urldecode($name);
} else {
$id = $node;
$name = '';
}
$id = urldecode($id);

return [
0 => $id,
1 => $name,
'id' => $id,
'name' => $name,
];
}

/**
* Returns the relevant googledocs content browser class based on the given path.
*
* @param rest $service The rest API object
* @param string $path The current path
* @return googledocs_content The googledocs repository content browser
*/
public static function get_browser(rest $service, string $path): googledocs_content {
$pathnodes = explode('/', $path);
$currentnode = self::explode_node_path(array_pop($pathnodes));

// Return the relevant content browser class based on the ID of the current path node.
switch ($currentnode['id']) {
case \repository_googledocs::REPOSITORY_ROOT_ID:
return new googledocs_root_content($service, $path, false);
case \repository_googledocs::SHARED_DRIVES_ROOT_ID:
return new googledocs_shared_drives_content($service, $path);
default:
return new googledocs_drive_content($service, $path);
}
}

/**
* Returns the relevant repository content node class based on the Google Drive file's mimetype.
*
* @param \stdClass $gdcontent The Google Drive content (file/folder) object
* @param string $path The current path
* @return node The content node object
*/
public static function get_node(\stdClass $gdcontent, string $path): node {
// Return the relevant content browser class based on the ID of the current path node.
switch ($gdcontent->mimeType) {
case 'application/vnd.google-apps.folder':
return new folder_node($gdcontent, $path);
default:
return new file_node($gdcontent);
}
}

/**
* Wrapper function to perform an API call and also catch and handle potential exceptions.
*
* @param rest $service The rest API object
* @param string $api The name of the API call
* @param array $params The parameters required by the API call
* @return \stdClass The response object
* @throws \repository_exception
*/
public static function request(rest $service, string $api, array $params): ?\stdClass {
try {
// Retrieving files and folders.
$response = $service->call($api, $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');
}
throw $e;
}

return $response;
}
}

0 comments on commit bded723

Please sign in to comment.