Merge branch 'MDL-67812-master-latest-3' of git://github.com/mihailges/moodle
authorVíctor Déniz Falcón <victor@moodle.com>
Mon, 18 May 2020 15:04:58 +0000 (16:04 +0100)
committerVíctor Déniz Falcón <victor@moodle.com>
Mon, 18 May 2020 15:04:58 +0000 (16:04 +0100)
23 files changed:
backup/util/ui/tests/behat/import_contentbank_content.feature
contentbank/contenttype/h5p/tests/behat/manage_content.feature
contentbank/tests/behat/events.feature
contentbank/tests/behat/search_content.feature
lib/behat/classes/behat_core_generator.php
lib/classes/plugin_manager.php
repository/contentbank/classes/browser/contentbank_browser.php [new file with mode: 0644]
repository/contentbank/classes/browser/contentbank_browser_context_course.php [new file with mode: 0644]
repository/contentbank/classes/browser/contentbank_browser_context_coursecat.php [new file with mode: 0644]
repository/contentbank/classes/browser/contentbank_browser_context_system.php [new file with mode: 0644]
repository/contentbank/classes/helper.php [new file with mode: 0644]
repository/contentbank/classes/privacy/provider.php [new file with mode: 0644]
repository/contentbank/db/access.php [new file with mode: 0644]
repository/contentbank/db/install.php [new file with mode: 0644]
repository/contentbank/lang/en/repository_contentbank.php [new file with mode: 0644]
repository/contentbank/lib.php [new file with mode: 0644]
repository/contentbank/pix/icon.png [new file with mode: 0644]
repository/contentbank/pix/icon.svg [new file with mode: 0644]
repository/contentbank/tests/behat/select_content.feature [new file with mode: 0644]
repository/contentbank/tests/browser_test.php [new file with mode: 0644]
repository/contentbank/tests/generator/lib.php [new file with mode: 0644]
repository/contentbank/version.php [new file with mode: 0644]
repository/tests/behat/behat_filepicker.php

index 68f2fb3..eb7507f 100644 (file)
@@ -17,8 +17,8 @@ Feature: Import course content bank content
       | teacher1 | C1 | editingteacher |
       | teacher1 | C2 | editingteacher |
     And the following "contentbank content" exist:
-      | course| contenttype     | user     | contentname       |
-      | C1    | contenttype_h5p | teacher1 | ipsums.h5p        |
+      | contextlevel | reference | contenttype     | user     | contentname |
+      | Course       | C1        | contenttype_h5p | teacher1 | ipsums.h5p  |
     And I log in as "teacher1"
 
   Scenario: Import content bank content to another course
index 5705a9e..65f2e2a 100644 (file)
@@ -15,9 +15,9 @@ Feature: Manage H5P content from the content bank
       | user     | course | role           |
       | teacher1 | C1     | editingteacher |
     And the following "contentbank content" exist:
-      | course| contenttype     | user     | contentname       |
-      | C1    | contenttype_h5p | admin    | filltheblanks.h5p |
-      | C1    | contenttype_h5p | teacher1 | ipsums.h5p        |
+      | contextlevel | reference | contenttype     | user     | contentname       |
+      | Course       | C1        | contenttype_h5p | admin    | filltheblanks.h5p |
+      | Course       | C1        | contenttype_h5p | teacher1 | ipsums.h5p        |
     And I log in as "admin"
     And I am on "Course 1" course homepage with editing mode on
     And I add the "Navigation" block if not present
index 708a9a8..efb8804 100644 (file)
@@ -9,8 +9,8 @@ Feature: Confirm content bank events are triggered
       | fullname | shortname | category |
       | Course 1 | C1        | 0        |
     And the following "contentbank content" exist:
-      | course | contenttype     | user  | contentname |
-      | C1     | contenttype_h5p | admin | Existing    |
+      | contextlevel | reference | contenttype     | user  | contentname |
+      | Course       | C1        | contenttype_h5p | admin | Existing    |
     And I log in as "admin"
     And I follow "Manage private files..."
     And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
index 940415f..54f9ecd 100644 (file)
@@ -6,15 +6,15 @@ Feature: Search content in the content bank
 
   Background:
     Given the following "contentbank content" exist:
-        | contextid | contenttype       | user  | contentname          |
-        | 1         | contenttype_h5p   | admin | santjordi.h5p        |
-        | 1         | contenttype_h5p   | admin | santjordi_rose.h5p   |
-        | 1         | contenttype_h5p   | admin | SantJordi_book       |
-        | 1         | contenttype_h5p   | admin | Dragon_santjordi.h5p |
-        | 1         | contenttype_h5p   | admin | princess.h5p         |
-        | 1         | contenttype_h5p   | admin | mathsbook.h5p        |
-        | 1         | contenttype_h5p   | admin | historybook.h5p      |
-        | 1         | contenttype_h5p   | admin | santvicenc.h5p       |
+      | contextlevel | reference | contenttype     | user     | contentname          |
+      | System       |           | contenttype_h5p | admin    | santjordi.h5p        |
+      | System       |           | contenttype_h5p | admin    | santjordi_rose.h5p   |
+      | System       |           | contenttype_h5p | admin    | SantJordi_book       |
+      | System       |           | contenttype_h5p | admin    | Dragon_santjordi.h5p |
+      | System       |           | contenttype_h5p | admin    | princess.h5p         |
+      | System       |           | contenttype_h5p | admin    | mathsbook.h5p        |
+      | System       |           | contenttype_h5p | admin    | historybook.h5p      |
+      | System       |           | contenttype_h5p | admin    | santvicenc.h5p       |
 
   Scenario: Admins can search content in the content bank
     Given I log in as "admin"
index cbb6905..2aa6049 100644 (file)
@@ -219,8 +219,8 @@ class behat_core_generator extends behat_generator_base {
             ],
             'contentbank content' => [
                 'datagenerator' => 'contentbank_content',
-                'required' => array('contenttype', 'user', 'contentname'),
-                'switchids' => array('course' => 'courseid', 'user' => 'userid')
+                'required' => array('contextlevel', 'reference', 'contenttype', 'user', 'contentname'),
+                'switchids' => array('user' => 'userid')
             ],
         ];
     }
@@ -823,27 +823,50 @@ class behat_core_generator extends behat_generator_base {
     }
 
     /**
-     * Create content in the given context's content bank
+     * Create content in the given context's content bank.
      *
      * @param array $data
      * @return void
      */
     protected function process_contentbank_content(array $data) {
-        if (empty($data['contextid'])) {
-            if (empty($data['courseid'])) {
-                throw new Exception('contentbank_content requires the field course or contextid to be specified');
-            }
-            $context = context_course::instance($data['courseid']);
-        } else {
-            $context = context::instance_by_id($data['contextid']);
+        global $CFG;
+
+        if (empty($data['contextlevel'])) {
+            throw new Exception('contentbank_content requires the field contextlevel to be specified');
         }
+
+        if (!isset($data['reference'])) {
+            throw new Exception('contentbank_content requires the field reference to be specified');
+        }
+
+        if (empty($data['contenttype'])) {
+            throw new Exception('contentbank_content requires the field contenttype to be specified');
+        }
+
         $contenttypeclass = "\\".$data['contenttype']."\\contenttype";
         if (class_exists($contenttypeclass)) {
+            $context = $this->get_context($data['contextlevel'], $data['reference']);
             $contenttype = new $contenttypeclass($context);
             $record = new stdClass();
             $record->usercreated = $data['userid'];
             $record->name = $data['contentname'];
             $content = $contenttype->create_content($record);
+
+            if (!empty($data['filepath'])) {
+                $fs = get_file_storage();
+                $filerecord = array(
+                    'component' => 'contentbank',
+                    'filearea' => 'public',
+                    'contextid' => $context->id,
+                    'userid' => $data['userid'],
+                    'itemid' => $content->get_id(),
+                    'filename' => $data['contentname'],
+                    'filepath' => '/'
+                );
+                $fs->create_file_from_pathname($filerecord, $CFG->dirroot . $data['filepath']);
+            }
+        } else {
+            throw new Exception('The specified "' . $data['contenttype'] . '" contenttype does not exist');
         }
     }
 }
index 962bc3e..ba448c9 100644 (file)
@@ -1970,7 +1970,7 @@ class core_plugin_manager {
             ),
 
             'repository' => array(
-                'areafiles', 'boxnet', 'coursefiles', 'dropbox', 'equella', 'filesystem',
+                'areafiles', 'boxnet', 'contentbank', 'coursefiles', 'dropbox', 'equella', 'filesystem',
                 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot', 'nextcloud',
                 'onedrive', 'picasa', 'recent', 'skydrive', 's3', 'upload', 'url', 'user', 'webdav',
                 'wikimedia', 'youtube'
diff --git a/repository/contentbank/classes/browser/contentbank_browser.php b/repository/contentbank/classes/browser/contentbank_browser.php
new file mode 100644 (file)
index 0000000..c8bb979
--- /dev/null
@@ -0,0 +1,167 @@
+<?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/>.
+
+/**
+ * Utility class for browsing of content bank files.
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski <mihail@moodle.com>
+ * @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 <mihail@moodle.com>
+ * @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 (file)
index 0000000..a6e9cc2
--- /dev/null
@@ -0,0 +1,68 @@
+<?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/>.
+
+/**
+ * Utility class for browsing of content bank files in the course context.
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski <mihail@moodle.com>
+ * @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 <mihail@moodle.com>
+ * @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 (file)
index 0000000..267e3b8
--- /dev/null
@@ -0,0 +1,84 @@
+<?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/>.
+
+/**
+ * Utility class for browsing of content bank files in the course category context.
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski <mihail@moodle.com>
+ * @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 <mihail@moodle.com>
+ * @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 (file)
index 0000000..6d861af
--- /dev/null
@@ -0,0 +1,68 @@
+<?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/>.
+
+/**
+ * Utility class for browsing of content bank files in the system context.
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski <mihail@moodle.com>
+ * @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 <mihail@moodle.com>
+ * @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 (file)
index 0000000..2df172d
--- /dev/null
@@ -0,0 +1,133 @@
+<?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/>.
+
+/**
+ * Content bank files repository helpers.
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski <mihail@moodle.com>
+ * @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 <mihail@moodle.com>
+ * @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 (file)
index 0000000..ae13ce4
--- /dev/null
@@ -0,0 +1,44 @@
+<?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/>.
+
+/**
+ * Privacy Subsystem implementation for repository_contentbank.
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski <mihail@moodle.com>
+ * @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 <mihail@moodle.com>
+ * @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 (file)
index 0000000..de28726
--- /dev/null
@@ -0,0 +1,62 @@
+<?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/>.
+
+/**
+ * Plugin capabilities.
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski
+ * @author     Mihail Geshoski <mihail@moodle.com>
+ * @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 (file)
index 0000000..ec6e327
--- /dev/null
@@ -0,0 +1,41 @@
+<?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/>.
+
+/**
+ * Installation file for the content bank repository
+ *
+ * @package   repository_contentbank
+ * @copyright 2020 Mihail Geshoski <mihail@moodle.com>
+ * @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 (file)
index 0000000..6b469d9
--- /dev/null
@@ -0,0 +1,34 @@
+<?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/>.
+
+/**
+ * Strings for component 'repository_contentbank', language 'en'.
+ *
+ * @package   repository_contentbank
+ * @copyright 2020 Mihail Geshoski
+ * @author    Mihail Geshoski <mihail@moodle.com>
+ * @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 (file)
index 0000000..3a17689
--- /dev/null
@@ -0,0 +1,147 @@
+<?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 the content bank files.
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski <mihail@moodle.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');
+
+/**
+ * repository_contentbank class is used to browse the content bank files
+ *
+ * @package   repository_contentbank
+ * @copyright 2020 Mihail Geshoski <mihail@moodle.com>
+ * @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 (file)
index 0000000..44c7274
Binary files /dev/null and b/repository/contentbank/pix/icon.png differ
diff --git a/repository/contentbank/pix/icon.svg b/repository/contentbank/pix/icon.svg
new file mode 100644 (file)
index 0000000..8f1863b
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+        viewBox="0 0 74 51" style="enable-background:new 0 0 74 51;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">
+<style type="text/css">
+       .st0{fill:#F98012;}
+       .st1{fill:#333333;}
+</style>
+<g>
+       <path class="st0" d="M61.9,50.3V27.4c0-4.8-2-7.2-5.9-7.2c-4,0-5.9,2.4-5.9,7.2v22.9H38.4V27.4c0-4.8-1.9-7.2-5.8-7.2
+               c-4,0-5.9,2.4-5.9,7.2v22.9H15V26.1c0-5,1.7-8.8,5.2-11.3c3-2.3,7.2-3.4,12.4-3.4c5.3,0,9.2,1.4,11.6,4.1c2.2-2.7,6.1-4.1,11.8-4.1
+               c5.2,0,9.3,1.1,12.4,3.4c3.5,2.6,5.2,6.3,5.2,11.3v24.3H61.9z"/>
+       <path class="st1" d="M37.6,9.5l11.6-8.5L49,0.6C28.1,3.1,18.6,4.9,0.7,15.4l0.2,0.5l1.4,0c-0.1,1.4-0.4,5-0.1,10.4
+               c-2,5.8,0,9.7,1.8,14c0.3-4.4,0.3-9.3-1.1-14.1c-0.3-5.3,0-8.8,0.1-10.2L14.9,16c0,0-0.1,3.6,0.4,7c10.7,3.7,21.4,0,27.1-9.2
+               C40.7,11.9,37.6,9.5,37.6,9.5z"/>
+</g>
+</svg>
diff --git a/repository/contentbank/tests/behat/select_content.feature b/repository/contentbank/tests/behat/select_content.feature
new file mode 100644 (file)
index 0000000..3c11346
--- /dev/null
@@ -0,0 +1,155 @@
+@repository @repository_contentbank @javascript
+Feature: Select content bank files using the content bank files repository
+  In order to re-use content bank files
+  As a user
+  I need to be able to view and select content bank files using the content bank repository
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email                |
+      | student  | Student   | 1        | student@example.com  |
+      | teacher1 | Teacher 1 | 1        | teacher1@example.com |
+      | teacher2 | Teacher 2 | 1        | teacher2@example.com |
+    And the following "categories" exist:
+      | name         | category | idnumber |
+      | Category1    | 0        | CAT1     |
+      | SubCategory1 | CAT1     | SUBCAT1  |
+    And the following "courses" exist:
+      | fullname             | shortname | category |
+      | MiscellaneousCourse1 | mscC1     | 0        |
+      | MiscellaneousCourse2 | mscC2     | 0        |
+      | Category1Course1     | cat1C1    | CAT1     |
+      | SubCategory1Course1  | subcat1C1 | SUBCAT1  |
+    And the following "contentbank content" exist:
+      | contextlevel | reference | contenttype     | user  | contentname             | filepath                                    |
+      | Course       | mscC1     | contenttype_h5p | admin | filltheblanks.h5p       | /h5p/tests/fixtures/filltheblanks.h5p       |
+      | Course       | mscC2     | contenttype_h5p | admin | find-the-words.h5p      | /h5p/tests/fixtures/find-the-words.h5p      |
+      | Course       | subcat1C1 | contenttype_h5p | admin | greeting-card-887.h5p   | /h5p/tests/fixtures/greeting-card-887.h5p   |
+      | Category     | CAT1      | contenttype_h5p | admin | ipsums.h5p              | /h5p/tests/fixtures/ipsums.h5p              |
+      | Category     | SUBCAT1   | contenttype_h5p | admin | multiple-choice-2-6.h5p | /h5p/tests/fixtures/multiple-choice-2-6.h5p |
+      | System       |           | contenttype_h5p | admin | filltheblanks.h5p       | /h5p/tests/fixtures/filltheblanks.h5p       |
+    And the following "activities" exist:
+      | activity | name       | intro      | introformat | course | idnumber |
+      | forum    | Forum      | ForumDesc  | 1           | mscC1  | forum1   |
+      | folder   | Folder     | FolderDesc | 1           | mscC1  | folder1  |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | mscC1  | editingteacher |
+      | teacher2 | mscC1  | teacher        |
+      | student  | mscC1  | student        |
+
+  Scenario: Admin can navigate and see all existing content bank files using the content bank repository
+    Given I log in as "admin"
+    And I am on "MiscellaneousCourse1" course homepage
+    And I follow "Folder"
+    And I click on "Edit" "button"
+    And I click on "Add..." "button"
+    And I should see "Content bank" in the ".fp-repo-area" "css_element"
+    When I select "Content bank" repository in file picker
+    Then I should see "System > Miscellaneous > MiscellaneousCourse1" breadcrumb navigation in repository
+    And I should see "1" elements in repository content area
+    And I should see "filltheblanks.h5p" "file" in repository content area
+    And I click on "Miscellaneous" "link" in the ".file-picker .fp-pathbar" "css_element"
+    And I should see "System > Miscellaneous" breadcrumb navigation in repository
+    And I should see "2" elements in repository content area
+    And I should see "MiscellaneousCourse1" "folder" in repository content area
+    And I should see "MiscellaneousCourse2" "folder" in repository content area
+    And I click on "MiscellaneousCourse2" "folder" in repository content area
+    And I should see "System > Miscellaneous > MiscellaneousCourse2" breadcrumb navigation in repository
+    And I should see "1" elements in repository content area
+    And I should see "find-the-words.h5p" "file" in repository content area
+    And I click on "System" "link" in the ".file-picker .fp-pathbar" "css_element"
+    And I should see "System" breadcrumb navigation in repository
+    And I should see "3" elements in repository content area
+    And I should see "filltheblanks.h5p" "file" in repository content area
+    And I should see "Miscellaneous" "folder" in repository content area
+    And I should see "Category1" "folder" in repository content area
+    And I click on "Category1" "folder" in repository content area
+    And I should see "System > Category1" breadcrumb navigation in repository
+    And I should see "3" elements in repository content area
+    And I should see "SubCategory1" "folder" in repository content area
+    And I should see "Category1Course1" "folder" in repository content area
+    And I should see "ipsums.h5p" "file" in repository content area
+    And I click on "SubCategory1" "folder" in repository content area
+    And I should see "System > Category1 > SubCategory1" breadcrumb navigation in repository
+    And I should see "2" elements in repository content area
+    And I should see "SubCategory1Course1" "folder" in repository content area
+    And I should see "multiple-choice-2-6.h5p" "file" in repository content area
+    And I click on "SubCategory1Course1" "folder" in repository content area
+    And I should see "System > Category1 > SubCategory1 > SubCategory1Course1" breadcrumb navigation in repository
+    And I should see "1" elements in repository content area
+    And I should see "greeting-card-887.h5p" "file" in repository content area
+
+  Scenario: Admin can select and re-use content bank files using the content bank repository
+    Given I log in as "admin"
+    And I am on "MiscellaneousCourse1" course homepage
+    And I follow "Folder"
+    And I click on "Edit" "button"
+    And I click on "Add..." "button"
+    And I should see "Content bank" in the ".fp-repo-area" "css_element"
+    And I select "Content bank" repository in file picker
+    And I should see "System > Miscellaneous > MiscellaneousCourse1" breadcrumb navigation in repository
+    And I click on "System" "link" in the ".file-picker .fp-pathbar" "css_element"
+    And I click on "Category1" "folder" in repository content area
+    And I should see "ipsums.h5p" "file" in repository content area
+    And I click on "ipsums.h5p" "file" in repository content area
+    And I should see "Select ipsums.h5p"
+    When I click on "Select this file" "button"
+    Then I should see "1" elements in "Files" filemanager
+    And I should see "ipsums.h5p" in the ".fp-content .fp-file" "css_element"
+
+  Scenario: Editing teacher can navigate and see content bank files available to him using the content bank repository
+    Given I log in as "teacher1"
+    And I am on "MiscellaneousCourse1" course homepage
+    And I follow "Folder"
+    And I click on "Edit" "button"
+    And I click on "Add..." "button"
+    And I should see "Content bank" in the ".fp-repo-area" "css_element"
+    When I select "Content bank" repository in file picker
+    Then I should see "System > Miscellaneous > MiscellaneousCourse1" breadcrumb navigation in repository
+    And I should see "1" elements in repository content area
+    And I should see "filltheblanks.h5p" "file" in repository content area
+    And I click on "Miscellaneous" "link" in the ".file-picker .fp-pathbar" "css_element"
+    And I should see "System > Miscellaneous" breadcrumb navigation in repository
+    And I should see "1" elements in repository content area
+    And I should see "MiscellaneousCourse1" "folder" in repository content area
+    And I click on "System" "link" in the ".file-picker .fp-pathbar" "css_element"
+    And I should see "System" breadcrumb navigation in repository
+    And I should see "2" elements in repository content area
+    And I should see "filltheblanks.h5p" "file" in repository content area
+    And I should see "Miscellaneous" "folder" in repository content area
+
+  Scenario: Editing teacher can select and re-use content bank files available to him using the content bank repository
+    Given I log in as "teacher1"
+    And I am on "MiscellaneousCourse1" course homepage
+    And I follow "Folder"
+    And I click on "Edit" "button"
+    And I click on "Add..." "button"
+    And I should see "Content bank" in the ".fp-repo-area" "css_element"
+    And I select "Content bank" repository in file picker
+    And I should see "System > Miscellaneous > MiscellaneousCourse1" breadcrumb navigation in repository
+    And I click on "System" "link" in the ".file-picker .fp-pathbar" "css_element"
+    And I should see "filltheblanks.h5p" "file" in repository content area
+    And I click on "filltheblanks.h5p" "file" in repository content area
+    And I should see "Select filltheblanks.h5p"
+    When I click on "Select this file" "button"
+    Then I should see "1" elements in "Files" filemanager
+    And I should see "filltheblanks.h5p" in the ".fp-content .fp-file" "css_element"
+
+  Scenario: Non-editing teacher can not see the content bank repository
+    Given I log in as "teacher2"
+    And I am on "MiscellaneousCourse1" course homepage
+    And I follow "Forum"
+    And I click on "Add a new discussion topic" "link"
+    And I click on "Link" "button"
+    When I click on "Browse repositories..." "button"
+    Then I should not see "Content bank" in the ".fp-repo-area" "css_element"
+
+  Scenario: Student can not see the content bank repository
+    Given I log in as "student"
+    And I am on "MiscellaneousCourse1" course homepage
+    And I follow "Forum"
+    And I click on "Add a new discussion topic" "link"
+    And I click on "Link" "button"
+    When I click on "Browse repositories..." "button"
+    Then I should not see "Content bank" in the ".fp-repo-area" "css_element"
diff --git a/repository/contentbank/tests/browser_test.php b/repository/contentbank/tests/browser_test.php
new file mode 100644 (file)
index 0000000..6a3efd3
--- /dev/null
@@ -0,0 +1,497 @@
+<?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/>.
+
+/**
+ * Content bank repository browser unit tests.
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski <mihail@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once("$CFG->dirroot/repository/lib.php");
+
+/**
+ * Tests for the content bank browser class.
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski <mihail@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class repository_contentbank_browser_testcase extends advanced_testcase {
+
+    /**
+     * Test get_content() in the system context with users that have capability to access/view content bank content
+     * within the system context. By default, every authenticated user should be able to access/view the content in
+     * the system context.
+     */
+    public function test_get_content_system_context_user_has_capabilities() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $systemcontext = \context_system::instance();
+        // Create a course category $coursecategory.
+        $coursecategory = $this->getDataGenerator()->create_category(['name' => 'Category']);
+        $coursecatcontext = \context_coursecat::instance($coursecategory->id);
+
+        // Get the default 'Miscellaneous' category.
+        $miscellaneouscat = \core_course_category::get(1);
+        $miscellaneouscatcontext = \context_coursecat::instance($miscellaneouscat->id);
+
+        // Create course.
+        $course = $this->getDataGenerator()->create_course(['category' => $coursecategory->id]);
+
+        $admin = get_admin();
+        // Create a user (not enrolled in a course).
+        $user = $this->getDataGenerator()->create_user();
+
+        // Add some content to the content bank.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        // Add some content bank files in the system context.
+        $contentbankcontents = $generator->generate_contentbank_data('contenttype_h5p', 3, $admin->id,
+            $systemcontext, true);
+
+        // Log in as admin.
+        $this->setUser($admin);
+        // Get the content bank nodes displayed to the admin in the system context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_system($systemcontext);
+        $repositorycontentnodes = $browser->get_content();
+        // All content nodes should be available to the admin user.
+        // There should be a total of 5 nodes, 3 file nodes representing the existing content bank files in the
+        // system context and 2 folder nodes representing the default course category 'Miscellaneous' and 'Category'.
+        $this->assertCount(5, $repositorycontentnodes);
+        $contextfolders = [
+            [
+                'name' => 'Miscellaneous',
+                'contextid' => $miscellaneouscatcontext->id
+            ],
+            [
+                'name' => 'Category',
+                'contextid' => $coursecatcontext->id
+            ]
+        ];
+        $expected = $this->generate_expected_content($contextfolders, $contentbankcontents);
+        $this->assertEquals($expected, $repositorycontentnodes, '', 0.0, 10, true);
+
+        // Log in as a user.
+        $this->setUser($user);
+        // Get the content bank nodes displayed to an authenticated user in the system context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_system($systemcontext);
+        $repositorycontentnodes = $browser->get_content();
+        // There should be 3 nodes representing the existing content bank files in the system context.
+        // The course category context folder node should be ignored as the user does not have an access to
+        // the content of the category's courses.
+        $this->assertCount(3, $repositorycontentnodes);
+        $expected = $this->generate_expected_content([], $contentbankcontents);
+        $this->assertEquals($expected, $repositorycontentnodes, '', 0.0, 10, true);
+
+        // Enrol the user as an editing teacher in the course.
+        $editingteacherrole = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, $editingteacherrole);
+
+         // Get the content bank nodes displayed to the editing teacher in the system context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_system($systemcontext);
+        $repositorycontentnodes = $browser->get_content();
+        // All content nodes should now be available to the editing teacher.
+        // There should be a total of 4 nodes, 3 file nodes representing the existing content bank files in the
+        // system context and 1 folder node representing the course category 'Category' (The editing teacher is now
+        // enrolled in a course from the category).
+        $this->assertCount(4, $repositorycontentnodes);
+        $contextfolders = [
+            [
+                'name' => 'Category',
+                'contextid' => $coursecatcontext->id
+            ]
+        ];
+        $expected = $this->generate_expected_content($contextfolders, $contentbankcontents);
+        $this->assertEquals($expected, $repositorycontentnodes, '', 0.0, 10, true);
+    }
+
+    /**
+     * Test get_content() in the system context with users that do not have a capability to access/view content bank
+     * content within the system context. By default, every non-authenticated user should not be able to access/view
+     * the content in the system context.
+     */
+    public function test_get_content_system_context_user_missing_capabilities() {
+        $this->resetAfterTest(true);
+
+        $systemcontext = \context_system::instance();
+
+        $admin = get_admin();
+        // Add some content to the content bank.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        // Add some content bank files in the system context.
+
+        $generator->generate_contentbank_data('contenttype_h5p', 3, $admin->id, $systemcontext, true);
+        // Log out.
+        $this->setUser();
+        // Get the content bank nodes displayed to a non-authenticated user in the system context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_system($systemcontext);
+        $repositorycontents = $browser->get_content();
+        // Content nodes should not be available to the non-authenticated user in the system context.
+        $this->assertCount(0, $repositorycontents);
+    }
+
+    /**
+     * Test get_content() in the course category context with users that have capability to access/view content
+     * bank content within the course category context. By default, every authenticated user that has access to
+     * any category course should be able to access/view the content in the course category context.
+     */
+    public function test_get_content_course_category_context_user_has_capabilities() {
+        $this->resetAfterTest(true);
+
+        // Create a course category.
+        $category = $this->getDataGenerator()->create_category(['name' => 'Category']);
+        $coursecatcontext = \context_coursecat::instance($category->id);
+        // Create course1.
+        $course1 = $this->getDataGenerator()->create_course(['fullname' => 'Course1', 'category' => $category->id]);
+        $course1context = \context_course::instance($course1->id);
+        // Create course2.
+        $course2 = $this->getDataGenerator()->create_course(['fullname' => 'Course2', 'category' => $category->id]);
+        $course2context = \context_course::instance($course2->id);
+
+        $admin = get_admin();
+        // Create editing teacher enrolled in course1.
+        $editingteacher = $this->getDataGenerator()->create_and_enrol($course1, 'editingteacher');
+
+        // Add some content to the content bank.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        // Add some content bank files in the course category context.
+        $contentbankcontents = $generator->generate_contentbank_data('contenttype_h5p', 3, $admin->id,
+            $coursecatcontext, true);
+
+        $this->setUser($admin);
+        // Get the content bank nodes displayed to the admin in the course category context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_coursecat($coursecatcontext);
+        $repositorycontents = $browser->get_content();
+        // All content nodes should be available to the admin user.
+        // There should be a total of 5 nodes, 3 file nodes representing the existing content bank files in the
+        // course category context and 2 folder nodes representing the courses 'Course1' and 'Course2'.
+        $this->assertCount(5, $repositorycontents);
+        $contextfolders = [
+            [
+                'name' => 'Course1',
+                'contextid' => $course1context->id
+            ],
+            [
+                'name' => 'Course2',
+                'contextid' => $course2context->id
+            ]
+        ];
+        $expected = $this->generate_expected_content($contextfolders, $contentbankcontents);
+        $this->assertEquals($expected, $repositorycontents, '', 0.0, 10, true);
+
+        // Log in as an editing teacher enrolled in a child course.
+        $this->setUser($editingteacher);
+        // Get the content bank nodes displayed to the editing teacher in the course category context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_coursecat($coursecatcontext);
+        $repositorycontents = $browser->get_content();
+        // There should be a total of 4 nodes, 3 file nodes representing the existing content bank files in the
+        // course category context and 1 folder node representing the course 'Course1' (The editing teacher is only
+        // enrolled in course1).
+        $this->assertCount(4, $repositorycontents);
+        $contextfolders = [
+            [
+                'name' => 'Course1',
+                'contextid' => $course1context->id
+            ]
+        ];
+        $expected = $this->generate_expected_content($contextfolders, $contentbankcontents);
+        $this->assertEquals($expected, $repositorycontents, '', 0.0, 10, true);
+    }
+
+    /**
+     * Test get_content() in the course category context with users that do not have capability to access/view content
+     * bank content within the course category context. By default, every non-authenticated user or authenticated users
+     * that cannot access/view course content from the course category should not be able to access/view the
+     * content in the course category context.
+     */
+    public function test_get_content_course_category_context_user_missing_capabilities() {
+        $this->resetAfterTest(true);
+
+         // Create a course category 'Category'.
+        $category = $this->getDataGenerator()->create_category(['name' => 'Category']);
+        // Create course1 in 'Category'.
+        $course1 = $this->getDataGenerator()->create_course(['fullname' => 'Course1', 'category' => $category->id]);
+        // Create course2 in 'Miscellaneous' by default.
+        $course2 = $this->getDataGenerator()->create_course(['fullname' => 'Course2']);
+        // Create a teacher enrolled in course1.
+        $teacher = $this->getDataGenerator()->create_and_enrol($course1, 'teacher');
+        // Create an editing teacher enrolled in course2.
+        $editingteacher = $this->getDataGenerator()->create_and_enrol($course2, 'editingteacher');
+
+        $admin = get_admin();
+        // Add some content to the content bank.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        // Add some content bank files in the 'Category' context.
+        $coursecatcontext = \context_coursecat::instance($category->id);
+        $generator->generate_contentbank_data('contenttype_h5p', 3, $admin->id,
+            $coursecatcontext, true);
+
+        // Log in as a non-editing teacher.
+        $this->setUser($teacher);
+        // Get the content bank nodes displayed to a non-editing teacher in the 'Category' context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_coursecat($coursecatcontext);
+        $repositorycontents = $browser->get_content();
+        // Content nodes should not be available to a non-editing teacher in the 'Category' context.
+        $this->assertCount(0, $repositorycontents);
+
+        // Log in as an editing teacher.
+        $this->setUser($editingteacher);
+        // Get the content bank nodes displayed to a an editing teacher in the 'Category' context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_coursecat($coursecatcontext);
+        $repositorycontents = $browser->get_content();
+        // Content nodes should not be available to an editing teacher in the 'Category' context.
+        $this->assertCount(0, $repositorycontents);
+
+        // Log out.
+        $this->setUser();
+        // Get the content bank nodes displayed to a non-authenticated user in the course category context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_coursecat($coursecatcontext);
+        $repositorycontents = $browser->get_content();
+        // Content nodes should not be available to the non-authenticated user in the course category context.
+        $this->assertCount(0, $repositorycontents);
+    }
+
+    /**
+     * Test get_content() in the course context with users that have capability to access/view content
+     * bank content within the course context. By default, admin, managers, course creators, editing teachers enrolled
+     * in the course should be able to access/view the content.
+     */
+    public function test_get_content_course_context_user_has_capabilities() {
+        $this->resetAfterTest(true);
+
+        // Create course1.
+        $course = $this->getDataGenerator()->create_course(['fullname' => 'Course']);
+        $coursecontext = \context_course::instance($course->id);
+
+        $admin = get_admin();
+        // Create editing teacher enrolled in course.
+        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
+
+        // Add some content to the content bank.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        // Add some content bank files in the course context.
+        $contentbankcontents = $generator->generate_contentbank_data('contenttype_h5p', 3, $admin->id,
+            $coursecontext, true);
+
+        $this->setUser($admin);
+        // Get the content bank nodes displayed to the admin in the course context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_course($coursecontext);
+        $repositorycontents = $browser->get_content();
+        // All content nodes should be available to the admin user.
+        // There should be 3 file nodes representing the existing content bank files in the
+        // course context.
+        $this->assertCount(3, $repositorycontents);
+        $expected = $this->generate_expected_content([], $contentbankcontents);
+        $this->assertEquals($expected, $repositorycontents, '', 0.0, 10, true);
+
+        // Log in as an editing teacher.
+        $this->setUser($editingteacher);
+        // All content nodes should also be available to the editing teacher.
+        // Get the content bank nodes displayed to the editing teacher in the course context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_course($coursecontext);
+        $repositorycontents = $browser->get_content();
+        // There should be 3 file nodes representing the existing content bank files in the
+        // course context.
+        $this->assertCount(3, $repositorycontents);
+        $expected = $this->generate_expected_content([], $contentbankcontents);
+        $this->assertEquals($expected, $repositorycontents, '', 0.0, 10, true);
+    }
+
+    /**
+     * Test get_content() in the course context with users that do not have capability to access/view content
+     * bank content within the course context. By default, every user which is not an admin, manager, course creator,
+     * editing teacher enrolled in the course should not be able to access/view the content.
+     */
+    public function test_get_content_course_context_user_missing_capabilities() {
+        $this->resetAfterTest(true);
+
+        // Create course1.
+        $course1 = $this->getDataGenerator()->create_course(['fullname' => 'Course1']);
+        $course1context = \context_course::instance($course1->id);
+        // Create course2.
+        $course2 = $this->getDataGenerator()->create_course(['fullname' => 'Course2']);
+        $course2context = \context_course::instance($course2->id);
+
+        $admin = get_admin();
+        // Create non-editing teacher enrolled in course1.
+        $teacher = $this->getDataGenerator()->create_and_enrol($course1, 'teacher');
+         // Create editing teacher enrolled in course1.
+        $editingteacher = $this->getDataGenerator()->create_and_enrol($course1, 'editingteacher');
+
+        // Add some content to the content bank.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        // Add some content bank files in the course1 context.
+        $generator->generate_contentbank_data('contenttype_h5p', 2, $admin->id,
+            $course1context, true);
+        // Add some content bank files in the course2 context.
+        $generator->generate_contentbank_data('contenttype_h5p', 3, $admin->id,
+            $course2context, true);
+
+        // Log in as a non-editing teacher.
+        $this->setUser($teacher);
+        // Get the content bank nodes displayed to the non-editing teacher in the course1 context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_course($course1context);
+        $repositorycontents = $browser->get_content();
+        // Content nodes should not be available to the teacher in the course1 context.
+        $this->assertCount(0, $repositorycontents);
+
+        // Log in as editing teacher.
+        $this->setUser($editingteacher);
+        // Get the content bank nodes displayed to the editing teacher in the course2 context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_course($course2context);
+        $repositorycontents = $browser->get_content();
+        // Content nodes should not be available to the teacher in the course2 context. The editing teacher is not
+        // enrolled in this course.
+        $this->assertCount(0, $repositorycontents);
+    }
+
+    /**
+     * Test get_navigation() in the system context.
+     */
+    public function test_get_navigation_system_context() {
+        $this->resetAfterTest(true);
+
+        $systemcontext = \context_system::instance();
+
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_system($systemcontext);
+        $navigation = $browser->get_navigation();
+        // The navigation array should contain only 1 element, representing the system navigation node.
+        $this->assertCount(1, $navigation);
+        $expected = [
+            \repository_contentbank\helper::create_navigation_node($systemcontext)
+        ];
+        $this->assertEquals($expected, $navigation);
+    }
+
+    /**
+     * Test get_navigation() in the course category context.
+     */
+    public function test_get_navigation_course_category_context() {
+        $this->resetAfterTest(true);
+
+        $systemcontext = \context_system::instance();
+        // Create a course category.
+        $category = $this->getDataGenerator()->create_category(['name' => 'category']);
+        $categorycontext = \context_coursecat::instance($category->id);
+        // Create a course subcategory.
+        $subcategory = $this->getDataGenerator()->create_category(['name' => 'subcategory', 'parent' => $category->id]);
+        $subcategorytcontext = \context_coursecat::instance($subcategory->id);
+
+        // Get navigation nodes in the category context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_coursecat($categorycontext);
+        $navigation = $browser->get_navigation();
+        // The navigation array should contain 2 elements, representing the system and course category
+        // navigation nodes.
+        $this->assertCount(2, $navigation);
+        $expected = [
+            \repository_contentbank\helper::create_navigation_node($systemcontext),
+            \repository_contentbank\helper::create_navigation_node($categorycontext)
+        ];
+        $this->assertEquals($expected, $navigation);
+
+        // Get navigation nodes in the subcategory context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_coursecat($subcategorytcontext);
+        $navigation = $browser->get_navigation();
+        // The navigation array should contain 3 elements, representing the system, category and subcategory
+        // navigation nodes.
+        $this->assertCount(3, $navigation);
+        $expected = [
+            \repository_contentbank\helper::create_navigation_node($systemcontext),
+            \repository_contentbank\helper::create_navigation_node($categorycontext),
+            \repository_contentbank\helper::create_navigation_node($subcategorytcontext)
+        ];
+        $this->assertEquals($expected, $navigation);
+    }
+
+    /**
+     * Test get_navigation() in the course context.
+     */
+    public function test_get_navigation_course_context() {
+        $this->resetAfterTest(true);
+
+        $systemcontext = \context_system::instance();
+        // Create a category.
+        $category = $this->getDataGenerator()->create_category(['name' => 'category']);
+        $categorycontext = \context_coursecat::instance($category->id);
+        // Create a subcategory.
+        $subcategory = $this->getDataGenerator()->create_category(['name' => 'category', 'parent' => $category->id]);
+        $subcategorycontext = \context_coursecat::instance($subcategory->id);
+        // Create a course in category.
+        $categorycourse = $this->getDataGenerator()->create_course(['category' => $category->id]);
+        $categorycoursecontext = \context_course::instance($categorycourse->id);
+        // Create a course in subcategory.
+        $subcategorycourse = $this->getDataGenerator()->create_course(['category' => $subcategory->id]);
+        $subcategorycoursecontext = \context_course::instance($subcategorycourse->id);
+
+        // Get navigation nodes in the category course context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_course($categorycoursecontext);
+        $navigation = $browser->get_navigation();
+        // The navigation array should contain 3 elements, representing the system, category and course
+        // navigation nodes.
+        $this->assertCount(3, $navigation);
+        $expected = [
+            \repository_contentbank\helper::create_navigation_node($systemcontext),
+            \repository_contentbank\helper::create_navigation_node($categorycontext),
+            \repository_contentbank\helper::create_navigation_node($categorycoursecontext)
+        ];
+        $this->assertEquals($expected, $navigation);
+
+        // Get navigation nodes in the subcategory course context.
+        $browser = new \repository_contentbank\browser\contentbank_browser_context_course($subcategorycoursecontext);
+        $navigation = $browser->get_navigation();
+        // The navigation array should contain 4 elements, representing the system, category, subcategory and
+        // subcategory course navigation nodes.
+        $this->assertCount(4, $navigation);
+        $expected = [
+            \repository_contentbank\helper::create_navigation_node($systemcontext),
+            \repository_contentbank\helper::create_navigation_node($categorycontext),
+            \repository_contentbank\helper::create_navigation_node($subcategorycontext),
+            \repository_contentbank\helper::create_navigation_node($subcategorycoursecontext)
+        ];
+        $this->assertEquals($expected, $navigation);
+    }
+
+    /**
+     * Generate the expected array of content bank nodes.
+     *
+     * @param array $contextfolders The array containing the expected folder nodes
+     * @param array $contentbankcontents The array containing the expected contents
+     * @return array[] The expected array of content bank nodes
+     */
+    private function generate_expected_content(array $contextfolders = [], array $contentbankcontents = []): array {
+
+        $expected = [];
+        if (!empty($contextfolders)) {
+            foreach ($contextfolders as $contextfolder) {
+                $expected[] = \repository_contentbank\helper::create_context_folder_node($contextfolder['name'],
+                    base64_encode(json_encode(['contextid' => $contextfolder['contextid']])));
+            }
+        }
+        if (!empty($contentbankcontents)) {
+            foreach ($contentbankcontents as $content) {
+                $expected[] = \repository_contentbank\helper::create_contentbank_content_node($content);
+            }
+        }
+        return $expected;
+    }
+}
diff --git a/repository/contentbank/tests/generator/lib.php b/repository/contentbank/tests/generator/lib.php
new file mode 100644 (file)
index 0000000..f03459e
--- /dev/null
@@ -0,0 +1,35 @@
+<?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/>.
+
+/**
+ * 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 (file)
index 0000000..84b83d9
--- /dev/null
@@ -0,0 +1,30 @@
+<?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/>.
+
+/**
+ * Version details
+ *
+ * @package    repository_contentbank
+ * @copyright  2020 Mihail Geshoski
+ * @author     Mihail Geshoski <mihail@moodle.com>
+ * @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).
index 368dcb4..8ec12d5 100644 (file)
@@ -307,4 +307,117 @@ class behat_filepicker extends behat_base {
 
     }
 
+    /**
+     * Selects a repository from the repository list in the file picker.
+     *
+     * @Then /^I select "(?P<repository_name_string>(?:[^"]|\\")*)" repository in file picker$/
+     * @throws ExpectationException Thrown by behat_base::find
+     * @param string $repositoryname
+     */
+    public function i_select_filepicker_repository($repositoryname) {
+        $exception = new ExpectationException(
+            "The '{$repositoryname}' repository can not be found in the file picker", $this->getSession());
+        // We look for a repository that matches a certain name in the file picker repository list.
+        $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' filepicker ')]" .
+            "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-repo-area ')]" .
+            "//span[contains(concat(' ', normalize-space(@class), ' '), ' fp-repo-name ')]" .
+            "[normalize-space(.)='{$repositoryname}']";
+
+        $repository = $this->find('xpath', $xpath, $exception);
+        // If the node exists, click on the node.
+        $repository->click();
+    }
+
+    /**
+     * Makes sure user can see the exact number of elements (files and folders) in the repository content area in
+     * the file picker.
+     *
+     * @Then /^I should see "(?P<elements_number>\d+)" elements in repository content area$/
+     * @throws ExpectationException Thrown by behat_base::find_all
+     * @param int $expectedcount
+     */
+    public function i_should_see_elements_in_filepicker_repository($expectedcount) {
+        // We look for all .fp-file elements inside the content area of the file picker repository.
+        $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' file-picker ')]" .
+            "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-content ')]" .
+            "//a[contains(concat(' ', normalize-space(@class), ' '), ' fp-file ')]";
+
+        $elements = $this->find_all('xpath', $xpath);
+        // Make sure the expected number is equal to the actual number of .fp-file elements.
+        if (count($elements) != $expectedcount) {
+            throw new ExpectationException("Found " . count($elements) .
+                " elements in filepicker repository. Expected {$expectedcount}", $this->getSession());
+        }
+    }
+
+    /**
+     * Returns a specific element (file or folder) in the repository content area in the file picker.
+     *
+     * @throws ExpectationException Thrown by behat_base::find
+     * @param string $elementname The name of the element
+     * @param string $elementtype The type of the element ("file" or "folder")
+     * @return NodeElement
+     */
+    protected function get_element_in_filepicker_repository($elementname, $elementtype) {
+        // We look for a .fp-{type} element with a certain name inside the content area of the file picker repository.
+        $exception = new ExpectationException(
+            "The '{$elementname}' {$elementtype} can not be found in the repository content area",
+            $this->getSession());
+        $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' file-picker ')]" .
+            "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-content ')]" .
+            "//a[contains(concat(' ', normalize-space(@class), ' '), ' fp-{$elementtype} ')]" .
+            "[normalize-space(.)='{$elementname}']";
+
+        return $this->find('xpath', $xpath, $exception);
+    }
+
+    /**
+     * Makes sure user can see a specific element (file or folder) in the repository content area in the file picker.
+     *
+     * @Then /^I should see "(?P<element_name_string>(?:[^"]|\\")*)" "(?P<element_type_string>(?:[^"]|\\")*)" in repository content area$/
+     * @throws ExpectationException Thrown by behat_base::find
+     * @param string $elementname The name of the element
+     * @param string $elementtype The type of the element ("file" or "folder")
+     */
+    public function i_should_see_element_in_filepicker_repository($elementname, $elementtype) {
+        $this->get_element_in_filepicker_repository($elementname, $elementtype);
+    }
+
+    /**
+     * Clicks on a specific element (file or folder) in the repository content area in the file picker.
+     *
+     * @Then /^I click on "(?P<element_name_string>(?:[^"]|\\")*)" "(?P<element_type_string>(?:[^"]|\\")*)" in repository content area$/
+     * @throws ExpectationException Thrown by behat_base::find
+     * @param string $elementname The name of the element
+     * @param string $elementtype The type of the element ("file" or "folder")
+     */
+    public function i_click_on_element_in_filepicker_repository($elementname, $elementtype) {
+        $element = $this->get_element_in_filepicker_repository($elementname, $elementtype);
+        $element->click();
+    }
+
+    /**
+     * Makes sure the user can see a specific breadcrumb navigation structure in the file picker repository.
+     *
+     * @Then /^I should see "(?P<breadcrumb_navigation_string>(?:[^"]|\\")*)" breadcrumb navigation in repository$/
+     * @throws ExpectationException Thrown by behat_base::find
+     * @param string $breadcrumbs The breadcrumb navigation structure (ex. "System > Category > Course")
+     */
+    public function i_should_see_breadcrumb_navigation_in_filepicker_repository($breadcrumbs) {
+        $breadcrumbs = preg_split('/\s*>\s*/', trim($breadcrumbs));
+        foreach ($breadcrumbs as $breadcrumb) {
+            // We look for a .fp-path-folder element with a certain name inside the breadcrumb navigation area
+            // in the repository.
+            $exception = new ExpectationException(
+                "The '{$breadcrumb}' node can not be found in the breadcrumb navigation in the repository",
+                $this->getSession()
+            );
+            $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' file-picker ')]" .
+                "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-pathbar ')]" .
+                "//span[contains(concat(' ', normalize-space(@class), ' '), ' fp-path-folder ')]" .
+                "[normalize-space(.)='{$breadcrumb}']";
+
+            $this->find('xpath', $xpath, $exception);
+        }
+    }
 }