From fa12d5dcc1d3a7987726cac3d3821112094fd477 Mon Sep 17 00:00:00 2001 From: Mihail Geshoski Date: Thu, 30 Apr 2020 14:44:48 +0800 Subject: [PATCH] MDL-67812 repository_contentbank: Create content bank files repository --- .../classes/browser/contentbank_browser.php | 167 ++++++++++++++++++ .../contentbank_browser_context_course.php | 68 +++++++ .../contentbank_browser_context_coursecat.php | 84 +++++++++ .../contentbank_browser_context_system.php | 68 +++++++ repository/contentbank/classes/helper.php | 133 ++++++++++++++ .../contentbank/classes/privacy/provider.php | 44 +++++ repository/contentbank/db/access.php | 62 +++++++ repository/contentbank/db/install.php | 41 +++++ .../lang/en/repository_contentbank.php | 34 ++++ repository/contentbank/lib.php | 147 +++++++++++++++ repository/contentbank/pix/icon.png | Bin 0 -> 7382 bytes repository/contentbank/pix/icon.svg | 17 ++ .../contentbank/tests/generator/lib.php | 35 ++++ repository/contentbank/version.php | 30 ++++ 14 files changed, 930 insertions(+) create mode 100644 repository/contentbank/classes/browser/contentbank_browser.php create mode 100644 repository/contentbank/classes/browser/contentbank_browser_context_course.php create mode 100644 repository/contentbank/classes/browser/contentbank_browser_context_coursecat.php create mode 100644 repository/contentbank/classes/browser/contentbank_browser_context_system.php create mode 100644 repository/contentbank/classes/helper.php create mode 100644 repository/contentbank/classes/privacy/provider.php create mode 100644 repository/contentbank/db/access.php create mode 100644 repository/contentbank/db/install.php create mode 100644 repository/contentbank/lang/en/repository_contentbank.php create mode 100644 repository/contentbank/lib.php create mode 100644 repository/contentbank/pix/icon.png create mode 100644 repository/contentbank/pix/icon.svg create mode 100644 repository/contentbank/tests/generator/lib.php create mode 100644 repository/contentbank/version.php diff --git a/repository/contentbank/classes/browser/contentbank_browser.php b/repository/contentbank/classes/browser/contentbank_browser.php new file mode 100644 index 00000000000..c8bb979328d --- /dev/null +++ b/repository/contentbank/classes/browser/contentbank_browser.php @@ -0,0 +1,167 @@ +. + +/** + * Utility class for browsing of content bank files. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace repository_contentbank\browser; + +/** + * Base class for the content bank browsers. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class contentbank_browser { + + /** @var \context The current context. */ + protected $context; + + /** + * Get all content nodes in the current context which can be viewed/accessed by the user. + * + * @return array[] The array containing all nodes which can be viewed/accessed by the user in the current context + */ + public function get_content(): array { + return array_merge($this->get_context_folders(), $this->get_contentbank_content()); + } + + /** + * Generate the full navigation to the current node. + * + * @return array[] The array containing the path to each node in the navigation. + * Each navigation node is an array with keys: name, path. + */ + public function get_navigation(): array { + // Get the current navigation node. + $currentnavigationnode = \repository_contentbank\helper::create_navigation_node($this->context); + $navigationnodes = [$currentnavigationnode]; + // Get the parent content bank browser. + $parent = $this->get_parent(); + // Prepend parent navigation node in the navigation nodes array until there is no existing parent. + while ($parent !== null) { + $parentnavigationnode = \repository_contentbank\helper::create_navigation_node($parent->context); + array_unshift($navigationnodes, $parentnavigationnode); + $parent = $parent->get_parent(); + } + return $navigationnodes; + } + + /** + * The required condition to enable the user to view/access the content bank content in this context. + * + * @return bool Whether the user can view/access the content bank content in the context + */ + abstract public function can_access_content(): bool; + + /** + * Define the allowed child context levels. + * + * @return int[] The array containing the relevant child context levels + */ + abstract protected function allowed_child_context_levels(): array; + + /** + * Get the relevant child contexts. + * + * @return \context[] The array containing the relevant, next-level children contexts + */ + protected function get_child_contexts(): array { + global $DB; + + if (empty($allowedcontextlevels = $this->allowed_child_context_levels())) { + // Early return if there aren't any defined child context levels. + return []; + } + + list($contextlevelsql, $params) = $DB->get_in_or_equal($allowedcontextlevels, SQL_PARAMS_NAMED); + $pathsql = $DB->sql_like('path', ':path', false, false); + + $select = "contextlevel {$contextlevelsql} + AND {$pathsql} + AND depth = :depth"; + + $params['path'] = "{$this->context->path}/%"; + $params['depth'] = $this->context->depth + 1; + + $childcontexts = $DB->get_records_select('context', $select, $params); + + return array_map(function($childcontext) { + return \context::instance_by_id($childcontext->id); + }, $childcontexts); + } + + /** + * Get the content bank browser class of the parent context. Currently used to generate the navigation path. + * + * @return contentbank_browser|null The content bank browser of the parent context + */ + private function get_parent(): ?self { + if ($parentcontext = $this->context->get_parent_context()) { + return \repository_contentbank\helper::get_contentbank_browser($parentcontext); + } + return null; + } + + /** + * Generate folder nodes for the relevant child contexts which can be accessed/viewed by the user. + * + * @return array[] The array containing the context folder nodes where each folder node is an array with keys: + * title, datemodified, datecreated, path, thumbnail, children. + */ + private function get_context_folders(): array { + // Get all relevant child contexts. + $children = $this->get_child_contexts(); + // Return all child context folder nodes which can be accessed by the user following the defined conditions + // in can_access_content(). + return array_reduce($children, function ($list, $child) { + $browser = \repository_contentbank\helper::get_contentbank_browser($child); + if ($browser->can_access_content()) { + $name = $child->get_context_name(false); + $path = base64_encode(json_encode(['contextid' => $child->id])); + $list[] = \repository_contentbank\helper::create_context_folder_node($name, $path); + } + return $list; + }, []); + } + + /** + * Generate nodes for the content bank content in the current context which can be accessed/viewed by the user. + * + * @return array[] The array containing the content nodes where each content node is an array with keys: + * shorttitle, title, datemodified, datecreated, author, license, isref, source, icon, thumbnail. + */ + private function get_contentbank_content(): array { + $cb = new \core_contentbank\contentbank(); + // Get all content bank files in the current context. + $contents = $cb->search_contents(null, $this->context->id); + // Return all content bank content nodes from the current context which can be accessed by the user following + // the defined conditions in can_access_content(). + return array_reduce($contents, function($list, $content) { + if ($this->can_access_content() && + $contentnode = \repository_contentbank\helper::create_contentbank_content_node($content)) { + $list[] = $contentnode; + } + return $list; + }, []); + } +} diff --git a/repository/contentbank/classes/browser/contentbank_browser_context_course.php b/repository/contentbank/classes/browser/contentbank_browser_context_course.php new file mode 100644 index 00000000000..a6e9cc2fa5f --- /dev/null +++ b/repository/contentbank/classes/browser/contentbank_browser_context_course.php @@ -0,0 +1,68 @@ +. + +/** + * Utility class for browsing of content bank files in the course context. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace repository_contentbank\browser; + +/** + * Represents the content bank browser in the course context. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class contentbank_browser_context_course extends contentbank_browser { + + /** + * Constructor. + * + * @param \context_course $context The current context + */ + public function __construct(\context_course $context) { + $this->context = $context; + } + + /** + * Define the allowed child context levels. + * + * @return int[] The array containing the relevant child context levels + */ + protected function allowed_child_context_levels(): array { + // The course context is the last relevant context level, therefore child context levels are not being returned. + return []; + } + + /** + * The required condition to enable the user to view/access the content bank content in this context. + * + * @return bool Whether the user can view/access the content bank content in the context + */ + public function can_access_content(): bool { + // When the following conditions are met, the user would be able to share the content created in the course + // context level all over the site. + // The content from the course context level should be available to: + // * Every user which has capability to access the content of a given course. + + return has_capability('repository/contentbank:accesscoursecontent', $this->context); + } +} diff --git a/repository/contentbank/classes/browser/contentbank_browser_context_coursecat.php b/repository/contentbank/classes/browser/contentbank_browser_context_coursecat.php new file mode 100644 index 00000000000..267e3b8566b --- /dev/null +++ b/repository/contentbank/classes/browser/contentbank_browser_context_coursecat.php @@ -0,0 +1,84 @@ +. + +/** + * Utility class for browsing of content bank files in the course category context. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace repository_contentbank\browser; + +/** + * Represents the content bank browser in the course category context. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class contentbank_browser_context_coursecat extends contentbank_browser { + + /** + * Constructor. + * + * @param \context_coursecat $context The current context + */ + public function __construct(\context_coursecat $context) { + $this->context = $context; + } + + /** + * Define the allowed child context levels. + * + * @return int[] The array containing the relevant child context levels + */ + protected function allowed_child_context_levels(): array { + // The expected child contexts in the course category context level are the course category context + // (ex. subcategories) and the course context. + return [\CONTEXT_COURSECAT, \CONTEXT_COURSE]; + } + + /** + * The required condition to enable the user to view/access the content bank content in this context. + * + * @return bool Whether the user can view/access the content bank content in the context + */ + public function can_access_content(): bool { + // When the following conditions are met, the user would be able to share the content created in the course + // category context level all over the site. + // The content from the course category context level should be available to either: + // * Every user which has a capability to access the 'general' content and has capability to access the + // content of any child course of the given course category. + // * Users that have capability to access content at a course category context level. + + if (has_capability('repository/contentbank:accesscoursecategorycontent', $this->context)) { + return true; + } + + $canaccesschildcontent = false; + foreach ($this->get_child_contexts() as $childcontext) { + $browser = \repository_contentbank\helper::get_contentbank_browser($childcontext); + if ($canaccesschildcontent = $browser->can_access_content()) { + break; + } + } + + return $canaccesschildcontent && has_capability('repository/contentbank:accessgeneralcontent', + $this->context); + } +} diff --git a/repository/contentbank/classes/browser/contentbank_browser_context_system.php b/repository/contentbank/classes/browser/contentbank_browser_context_system.php new file mode 100644 index 00000000000..6d861af1360 --- /dev/null +++ b/repository/contentbank/classes/browser/contentbank_browser_context_system.php @@ -0,0 +1,68 @@ +. + +/** + * Utility class for browsing of content bank files in the system context. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace repository_contentbank\browser; + +/** + * Represents the content bank browser in the system context. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class contentbank_browser_context_system extends contentbank_browser { + + /** + * Constructor. + * + * @param \context_system $context The current context + */ + public function __construct(\context_system $context) { + $this->context = $context; + } + + /** + * Define the allowed child context levels. + * + * @return int[] The array containing the relevant child context levels + */ + protected function allowed_child_context_levels(): array { + // The expected child context in the system context level is the course category context. + return [\CONTEXT_COURSECAT]; + } + + /** + * The required condition to enable the user to view/access the content bank content in this context. + * + * @return bool Whether the user can view/access the content bank content in the context + */ + public function can_access_content(): bool { + // When the following conditions are met, the user would be able to share the content created in the system + // context level all over the site. + // The content from the system context level should be available to: + // * Every user that has a capability to access the 'general' content. + + return has_capability('repository/contentbank:accessgeneralcontent', $this->context); + } +} diff --git a/repository/contentbank/classes/helper.php b/repository/contentbank/classes/helper.php new file mode 100644 index 00000000000..2df172de330 --- /dev/null +++ b/repository/contentbank/classes/helper.php @@ -0,0 +1,133 @@ +. + +/** + * Content bank files repository helpers. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace repository_contentbank; + +use repository_contentbank\browser\contentbank_browser; + +/** + * Helper class for content bank files repository. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class helper { + + /** + * Get the content bank repository browser for a certain context. + * + * @param \context $context The context + * @return \repository_contentbank\browser\contentbank_browser|null The content bank repository browser + */ + public static function get_contentbank_browser(\context $context): ?contentbank_browser { + switch ($context->contextlevel) { + case CONTEXT_SYSTEM: + return new \repository_contentbank\browser\contentbank_browser_context_system($context); + case CONTEXT_COURSECAT: + return new \repository_contentbank\browser\contentbank_browser_context_coursecat($context); + case CONTEXT_COURSE: + return new \repository_contentbank\browser\contentbank_browser_context_course($context); + } + return null; + } + + /** + * Create the context folder node. + * + * @param string $name The name of the context folder node + * @param string $path The path to the context folder node + * @return array The context folder node + */ + public static function create_context_folder_node(string $name, string $path): array { + global $OUTPUT; + + return [ + 'title' => $name, + 'datemodified' => '', + 'datecreated' => '', + 'path' => $path, + 'thumbnail' => $OUTPUT->image_url(file_folder_icon(90))->out(false), + 'children' => [] + ]; + } + + /** + * Create the content bank content node. + * + * @param \core_contentbank\content $content The content bank content + * @return array|null The content bank content node + */ + public static function create_contentbank_content_node(\core_contentbank\content $content): ?array { + global $OUTPUT; + // Only content files are currently supported, but should be able to create content folder nodes in the future. + // Early return if the content is not a stored file. + if (!$file = $content->get_file()) { + return null; + } + + $params = [ + 'contextid' => $file->get_contextid(), + 'component' => $file->get_component(), + 'filearea' => $file->get_filearea(), + 'itemid' => $file->get_itemid(), + 'filepath' => $file->get_filepath(), + 'filename' => $file->get_filename() + ]; + + $encodedpath = base64_encode(json_encode($params)); + + $node = [ + 'shorttitle' => $content->get_name(), + 'title' => $file->get_filename(), + 'datemodified' => $file->get_timemodified(), + 'datecreated' => $file->get_timecreated(), + 'author' => $file->get_author(), + 'license' => $file->get_license(), + 'isref' => $file->is_external_file(), + 'source' => $encodedpath, + 'icon' => $OUTPUT->image_url(file_file_icon($file, 24))->out(false), + 'thumbnail' => $OUTPUT->image_url(file_file_icon($file, 90))->out(false) + ]; + + if ($file->get_status() == 666) { + $node['originalmissing'] = true; + } + + return $node; + } + + /** + * Generate a navigation node. + * + * @param \context $context The context + * @return array The navigation node + */ + public static function create_navigation_node(\context $context): array { + return [ + 'path' => base64_encode(json_encode(['contextid' => $context->id])), + 'name' => $context->get_context_name(false) + ]; + } +} \ No newline at end of file diff --git a/repository/contentbank/classes/privacy/provider.php b/repository/contentbank/classes/privacy/provider.php new file mode 100644 index 00000000000..ae13ce47680 --- /dev/null +++ b/repository/contentbank/classes/privacy/provider.php @@ -0,0 +1,44 @@ +. + +/** + * Privacy Subsystem implementation for repository_contentbank. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace repository_contentbank\privacy; + +/** + * Privacy Subsystem for repository_contentbank implementing null_provider. + * + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements \core_privacy\local\metadata\null_provider { + + /** + * Get the language string identifier with the component's language + * file to explain why this plugin stores no data. + * + * @return string + */ + public static function get_reason() : string { + return 'privacy:metadata'; + } +} diff --git a/repository/contentbank/db/access.php b/repository/contentbank/db/access.php new file mode 100644 index 00000000000..de2872628f1 --- /dev/null +++ b/repository/contentbank/db/access.php @@ -0,0 +1,62 @@ +. + +/** + * Plugin capabilities. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @author Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$capabilities = [ + 'repository/contentbank:view' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => [ + 'coursecreator' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ] + ], + 'repository/contentbank:accesscoursecontent' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'coursecreator' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ] + ], + 'repository/contentbank:accesscoursecategorycontent' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSECAT, + 'archetypes' => [ + 'coursecreator' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ] + ], + 'repository/contentbank:accessgeneralcontent' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSECAT, + 'archetypes' => [ + 'user' => CAP_ALLOW + ] + ] +]; diff --git a/repository/contentbank/db/install.php b/repository/contentbank/db/install.php new file mode 100644 index 00000000000..ec6e3270073 --- /dev/null +++ b/repository/contentbank/db/install.php @@ -0,0 +1,41 @@ +. + +/** + * Installation file for the content bank repository + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Create a default instance of the content bank repository + * + * @return bool A status indicating success or failure + */ +function xmldb_repository_contentbank_install() { + global $CFG; + $result = true; + require_once($CFG->dirroot.'/repository/lib.php'); + $userplugin = new repository_type('contentbank', [], true); + if (!$id = $userplugin->create(true)) { + $result = false; + } + return $result; +} diff --git a/repository/contentbank/lang/en/repository_contentbank.php b/repository/contentbank/lang/en/repository_contentbank.php new file mode 100644 index 00000000000..6b469d96e38 --- /dev/null +++ b/repository/contentbank/lang/en/repository_contentbank.php @@ -0,0 +1,34 @@ +. + +/** + * Strings for component 'repository_contentbank', language 'en'. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @author Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['configplugin'] = 'Configuration for content bank repository'; +$string['pluginname_help'] = 'Files in content bank'; +$string['pluginname'] = 'Content bank'; +$string['emptyfilelist'] = 'There are no files to show'; +$string['contentbank:view'] = 'View content bank repository'; +$string['contentbank:accesscoursecontent'] = 'Access course content bank files'; +$string['contentbank:accesscoursecategorycontent'] = 'Access course category content bank files'; +$string['contentbank:accessgeneralcontent'] = 'Access system content bank files and content bank files from course categories which have an accessible course'; +$string['privacy:metadata'] = 'The Content bank repository plugin does not store or transmit any personal data.'; diff --git a/repository/contentbank/lib.php b/repository/contentbank/lib.php new file mode 100644 index 00000000000..3a17689098a --- /dev/null +++ b/repository/contentbank/lib.php @@ -0,0 +1,147 @@ +. + +/** + * This plugin is used to access the content bank files. + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/repository/lib.php'); + +/** + * repository_contentbank class is used to browse the content bank files + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class repository_contentbank extends repository { + + /** + * Get file listing. + * + * @param string $encodedpath + * @param string $page + * @return array + */ + public function get_listing($encodedpath = '', $page = '') { + global $SITE; + + $ret = []; + $ret['dynload'] = true; + $ret['nosearch'] = true; + $ret['nologin'] = true; + + // Return the parameters from the encoded path if the encoded path is not empty. + if (!empty($encodedpath)) { + $params = json_decode(base64_decode($encodedpath), true); + if (is_array($params) && isset($params['contextid'])) { + $context = context::instance_by_id(clean_param($params['contextid'], PARAM_INT)); + } + } + // Return the current context if the context was not specified in the encoded path. + // The current context should be an instance of context_system, context_coursecat or course related contexts. + if (empty($context) && !empty($this->context)) { + if ($this->context instanceof \context_system || $this->context instanceof \context_coursecat) { + $context = $this->context; + } else if ($coursecontext = $this->context->get_course_context(false)) { + // Skip if front page context. + if ($coursecontext->instanceid !== $SITE->id) { + $context = $coursecontext; + } + } + } + // If not, return the system context as a default context. + if (empty($context)) { + $context = context_system::instance(); + } + + $ret['list'] = []; + $ret['path'] = []; + + // Get the content bank browser for the specified context. + if ($browser = \repository_contentbank\helper::get_contentbank_browser($context)) { + $manageurl = new moodle_url('/contentbank/index.php', ['contextid' => $context->id]); + $canaccesscontent = has_capability('moodle/contentbank:access', $context); + $ret['manage'] = $canaccesscontent ? $manageurl->out() : ''; + $ret['list'] = $browser->get_content(); + $ret['path'] = $browser->get_navigation(); + } + + return $ret; + } + + /** + * Is this repository used to browse moodle files? + * + * @return boolean + */ + public function has_moodle_files() { + return true; + } + + /** + * Tells how the file can be picked from this repository. + * + * @return int + */ + public function supported_returntypes() { + return FILE_INTERNAL | FILE_REFERENCE; + } + + /** + * Is this repository accessing private data? + * + * @return bool + */ + public function contains_private_data() { + return false; + } + + /** + * Repository method to make sure that user can access particular file. + * + * This is checked when user tries to pick the file from repository to deal with + * potential parameter substitutions in request + * + * @param string $source + * @return bool whether the file is accessible by current user + */ + public function file_is_accessible($source) { + global $DB; + + $fileparams = json_decode(base64_decode($source)); + $itemid = clean_param($fileparams->itemid, PARAM_INT); + $contextid = clean_param($fileparams->contextid, PARAM_INT); + + $contentbankfile = $DB->get_record('contentbank_content', ['id' => $itemid]); + $plugin = \core_plugin_manager::instance()->get_plugin_info($contentbankfile->contenttype); + + $managerclass = "\\$contentbankfile->contenttype\\content"; + if ($plugin && $plugin->is_enabled() && class_exists($managerclass)) { + $context = \context::instance_by_id($contextid); + $browser = \repository_contentbank\helper::get_contentbank_browser($context); + return $browser->can_access_content(); + } + + return false; + } +} diff --git a/repository/contentbank/pix/icon.png b/repository/contentbank/pix/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..44c7274ed680502ccd912431639e5128171b08ad GIT binary patch literal 7382 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 literal 0 HcmV?d00001 diff --git a/repository/contentbank/pix/icon.svg b/repository/contentbank/pix/icon.svg new file mode 100644 index 00000000000..8f1863baa8a --- /dev/null +++ b/repository/contentbank/pix/icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/repository/contentbank/tests/generator/lib.php b/repository/contentbank/tests/generator/lib.php new file mode 100644 index 00000000000..f03459e368b --- /dev/null +++ b/repository/contentbank/tests/generator/lib.php @@ -0,0 +1,35 @@ +. + +/** + * Content bank files repository data generator + * + * @package repository_contentbank + * @category test + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Content bank files repository data generator class + * + * @package repository_contentbank + * @category test + * @copyright 2020 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class repository_contentbank_generator extends testing_repository_generator { +} diff --git a/repository/contentbank/version.php b/repository/contentbank/version.php new file mode 100644 index 00000000000..84b83d93c87 --- /dev/null +++ b/repository/contentbank/version.php @@ -0,0 +1,30 @@ +. + +/** + * Version details + * + * @package repository_contentbank + * @copyright 2020 Mihail Geshoski + * @author Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2020042700; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2020041500.00; // Requires this Moodle version. +$plugin->component = 'repository_contentbank'; // Full name of the plugin (used for diagnostics).