Merge branch 'MDL-67813-master-1' of git://github.com/mihailges/moodle
authorVíctor Déniz Falcón <victor@moodle.com>
Thu, 28 May 2020 12:00:16 +0000 (13:00 +0100)
committerVíctor Déniz Falcón <victor@moodle.com>
Thu, 28 May 2020 12:00:16 +0000 (13:00 +0100)
contentbank/tests/generator/lib.php
lib/templates/filemanager_default_searchform.mustache
lib/tests/behat/behat_general.php
repository/contentbank/classes/contentbank_search.php [new file with mode: 0644]
repository/contentbank/lib.php
repository/contentbank/tests/behat/search_content.feature [new file with mode: 0644]
repository/contentbank/tests/search_test.php [new file with mode: 0644]
repository/tests/behat/behat_filepicker.php

index 8bb9ae0..4d7e022 100644 (file)
@@ -45,10 +45,12 @@ class core_contentbank_generator extends \component_generator_base {
      * @param context $context The context where the content will be created.
      * @param bool $convert2class Whether the class should return stdClass or plugin instance.
      * @param string $filepath The filepath of the file associated to the content to create.
+     * @param string $contentname The name of the content that will be created.
      * @return array An array with all the records added to the content bank.
      */
     public function generate_contentbank_data(?string $contenttype, int $itemstocreate = 1, int $userid = 0,
-            ?\context $context = null, bool $convert2class = true, string $filepath = 'contentfile.h5p'): array {
+            ?\context $context = null, bool $convert2class = true, string $filepath = 'contentfile.h5p',
+            string $contentname = 'Test content '): array {
         global $DB, $USER;
 
         $records = [];
@@ -67,7 +69,8 @@ class core_contentbank_generator extends \component_generator_base {
         for ($i = 0; $i < $itemstocreate; $i++) {
             // Create content.
             $record = new stdClass();
-            $record->name = 'Test content ' . $i;
+            // If only 1 item is being created, do not add a number suffix to the content name.
+            $record->name = ($itemstocreate === 1) ? $contentname : $contentname . $i;
             $record->configdata = '';
             $record->usercreated = $userid ?? $USER->id;
 
index 1e6b24a..2bf592e 100644 (file)
@@ -23,6 +23,6 @@
     {}
 }}
 <div class="fp-def-search form-group">
-    <label class="sr-only">{{#str}}searchrepo, repository{{/str}}</label>
+    <label class="sr-only" for="reposearch">{{#str}}searchrepo, repository{{/str}}</label>
     <input type="search" class="form-control" id="reposearch" name="s" placeholder="{{#str}}search, repository{{/str}}"/>
 </div>
index e3830b7..f211d9f 100644 (file)
@@ -1898,4 +1898,19 @@ EOF;
                     $this->getSession());
         }
     }
+
+    /**
+     * Manually press enter key.
+     *
+     * @When /^I press enter/
+     * @throws DriverException
+     */
+    public function i_manually_press_enter() {
+        if (!$this->running_javascript()) {
+            throw new DriverException('Enter press step is not available with Javascript disabled');
+        }
+
+        $value = [\WebDriver\Key::ENTER];
+        $this->getSession()->getDriver()->getWebDriverSession()->activeElement()->postValue(['value' => $value]);
+    }
 }
diff --git a/repository/contentbank/classes/contentbank_search.php b/repository/contentbank/classes/contentbank_search.php
new file mode 100644 (file)
index 0000000..ace3f81
--- /dev/null
@@ -0,0 +1,61 @@
+<?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 searching 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;
+
+/**
+ * Represents the content bank search related functionality.
+ *
+ * @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_search {
+
+    /**
+     * Generate and return content nodes for all content bank files that match the search criteria
+     * and can be viewed/accessed by the user.
+     *
+     * @param string $search The search string
+     * @return array[] The array containing all content file nodes that match the search criteria. Each content node is
+     *                 an array with keys: shorttitle, title, datemodified, datecreated, author, license, isref, source,
+     *                 icon, thumbnail.
+     */
+    public static function get_search_contents(string $search): array {
+        $contentbank = new \core_contentbank\contentbank();
+        // Return all content bank content that matches the search criteria and can be viewed/accessed by the user.
+        $contents = $contentbank->search_contents($search);
+        return array_reduce($contents, function($list, $content) {
+            $contentcontext = \context::instance_by_id($content->get_content()->contextid);
+            $browser = \repository_contentbank\helper::get_contentbank_browser($contentcontext);
+            // If the user can access the content and content node can be created, add the node into the
+            // search results list.
+            if ($browser->can_access_content() &&
+                    $contentnode = \repository_contentbank\helper::create_contentbank_content_node($content)) {
+                $list[] = $contentnode;
+            }
+            return $list;
+        }, []);
+    }
+}
index 3a17689..0982faa 100644 (file)
@@ -47,7 +47,7 @@ class repository_contentbank extends repository {
 
         $ret = [];
         $ret['dynload'] = true;
-        $ret['nosearch'] = true;
+        $ret['nosearch'] = false;
         $ret['nologin'] = true;
 
         // Return the parameters from the encoded path if the encoded path is not empty.
@@ -144,4 +144,19 @@ class repository_contentbank extends repository {
 
         return false;
     }
+
+    /**
+     * Return search results.
+     *
+     * @param string $search
+     * @param int $page
+     * @return array
+     */
+    public function search($search, $page = 0) {
+        $ret = [];
+        $ret['nologin'] = true;
+        $ret['list'] = \repository_contentbank\contentbank_search::get_search_contents($search);
+
+        return $ret;
+    }
 }
diff --git a/repository/contentbank/tests/behat/search_content.feature b/repository/contentbank/tests/behat/search_content.feature
new file mode 100644 (file)
index 0000000..a975751
--- /dev/null
@@ -0,0 +1,121 @@
+@repository @repository_contentbank @javascript
+Feature: Search content bank files using the content bank files repository
+  In order to find the content I need to select in the file picker
+  As a user
+  I need to be able to search in the content bank files repository by content name
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email                |
+      | student  | Student   | 1        | student@example.com  |
+      | teacher  | Teacher   | 1        | teacher1@example.com |
+    And the following "categories" exist:
+      | name      | category | idnumber |
+      | Category1 | 0        | CAT1     |
+      | Category2 | 0        | CAT2     |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course1  | C1        | CAT1     |
+      | Course2  | C2        | CAT2     |
+    And the following "contentbank content" exist:
+      | contextlevel | reference | contenttype     | user  | contentname          | filepath                                    |
+      | Course       | C1        | contenttype_h5p | admin | coursecontent1.h5p   | /h5p/tests/fixtures/filltheblanks.h5p       |
+      | Course       | C2        | contenttype_h5p | admin | coursecontent2.h5p   | /h5p/tests/fixtures/find-the-words.h5p      |
+      | Category     | CAT1      | contenttype_h5p | admin | categorycontent1.h5p | /h5p/tests/fixtures/ipsums.h5p              |
+      | Category     | CAT2      | contenttype_h5p | admin | categorycontent2.h5p | /h5p/tests/fixtures/multiple-choice-2-6.h5p |
+      | System       |           | contenttype_h5p | admin | systemcontent.h5p    | /h5p/tests/fixtures/greeting-card-887.h5p   |
+    And the following "activities" exist:
+      | activity | name       | intro      | introformat | course | idnumber |
+      | folder   | Folder     | FolderDesc | 1           | C1     | folder   |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher  | C1     | editingteacher |
+
+  Scenario: User can see a search field and reset search button in the content bank files repository
+    Given I log in as "admin"
+    And I am on "Course1" 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 "Search repository" "field" should be visible
+    And "Refresh" "link" should be visible
+
+  Scenario: User can see search results when there is content that matches the search criteria
+    Given I log in as "admin"
+    And I am on "Course1" 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 set the field "Search repository" to "content"
+    When I press enter
+    Then I should see "5" elements in repository content area
+    And I should see "systemcontent.h5p" "file" in repository content area
+    And I should see "categorycontent1.h5p" "file" in repository content area
+    And I should see "categorycontent2.h5p" "file" in repository content area
+    And I should see "coursecontent1.h5p" "file" in repository content area
+    And I should see "coursecontent2.h5p" "file" in repository content area
+
+  Scenario: User can see search results when there is content that matches the search criteria ignoring case sensitivity
+    Given I log in as "admin"
+    And I am on "Course1" 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 set the field "Search repository" to "COURSE"
+    When I press enter
+    Then I should see "2" elements in repository content area
+    And I should see "coursecontent1.h5p" "file" in repository content area
+    And I should see "coursecontent2.h5p" "file" in repository content area
+
+  Scenario: User can not see any search results when there is not a content that matches the search criteria
+    Given I log in as "admin"
+    And I am on "Course1" 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 set the field "Search repository" to "somecontent"
+    When I press enter
+    Then I should see "0" elements in repository content area
+    And I should see "No files available" in the ".filepicker .fp-content" "css_element"
+
+  Scenario: User can reset search criteria and see all content displayed prior the search action
+    Given I log in as "admin"
+    And I am on "Course1" 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 "1" elements in repository content area
+    And I should see "coursecontent1.h5p" "file" in repository content area
+    And I set the field "Search repository" to "category"
+    And I press enter
+    And I should see "2" elements in repository content area
+    And I should see "categorycontent1.h5p" "file" in repository content area
+    And I should see "categorycontent2.h5p" "file" in repository content area
+    When I click on "Refresh" "link"
+    Then I should see "1" elements in repository content area
+    And I should see "coursecontent1.h5p" "file" in repository content area
+
+  Scenario: Editing teacher can see search results when the content is available to him and matches the search criteria
+    Given I log in as "teacher"
+    And I am on "Course1" 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 set the field "Search repository" to "content"
+    When I press enter
+    Then I should see "3" elements in repository content area
+    And I should see "coursecontent1.h5p" "file" in repository content area
+    And I should see "categorycontent1.h5p" "file" in repository content area
+    And I should see "systemcontent.h5p" "file" in repository content area
diff --git a/repository/contentbank/tests/search_test.php b/repository/contentbank/tests/search_test.php
new file mode 100644 (file)
index 0000000..84089f4
--- /dev/null
@@ -0,0 +1,241 @@
+<?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 search 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 search 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_search_testcase extends advanced_testcase {
+
+    /**
+     * Test get_search_contents() by searching through some existing content using different search terms.
+     *
+     * @dataProvider get_search_contents_provider
+     * @param array $contentnames The array containing the names of the content that needs to be generated
+     * @param string $search The search string
+     * @param array $expected The array containing the expected content names that should be returned by the search
+     */
+    public function test_get_search_contents(array $contentnames, string $search, array $expected) {
+        $this->resetAfterTest();
+
+        $admin = get_admin();
+        $systemcontext = \context_system::instance();
+        // Add some content to the content bank.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        foreach ($contentnames as $contentname) {
+            $generator->generate_contentbank_data('contenttype_h5p', 1, $admin->id,
+                $systemcontext, true, 'file.h5p', $contentname);
+        }
+        // Log in as admin.
+        $this->setUser($admin);
+        // Search for content bank content items which have the search pattern within the name.
+        $searchcontentnodes = \repository_contentbank\contentbank_search::get_search_contents($search);
+        // Get the content name of the each content returned after performing the search.
+        $actual = array_map(function($searchcontentnode) {
+            return $searchcontentnode['shorttitle'];
+        }, $searchcontentnodes);
+
+        $this->assertEquals($expected, $actual, '', 0.0, 10, true);
+    }
+
+    /**
+     * Data provider for test_get_search_contents().
+     *
+     * @return array
+     */
+    public function get_search_contents_provider(): array {
+        return [
+            'Search for existing pattern found within the name of content items' => [
+                [
+                    'systemcontentfile1',
+                    'systemcontentfile2',
+                    'somesystemfile'
+                ],
+                'content',
+                [
+                    'systemcontentfile1',
+                    'systemcontentfile2'
+                ]
+            ],
+            'Search for existing pattern found at the beginning of the name of content items' => [
+                [
+                    'systemcontentfile1',
+                    'systemcontentfile2',
+                    'somesystemfile'
+                ],
+                'some',
+                [
+                    'somesystemfile',
+                ]
+            ],
+            'Search for existing pattern found at the end of the name of content items' => [
+                [
+                    'systemcontentfile1',
+                    'systemcontentfile2',
+                    'somesystemfile'
+                ],
+                'file2',
+                [
+                    'systemcontentfile2',
+                ]
+            ],
+            'Search for a pattern which does not exist within the name of any content item' => [
+                [
+                    'systemcontentfile1',
+                    'somesystemfile'
+                ],
+                'somefile',
+                []
+            ],
+            'Case-insensitive search for a pattern which exists within the name of content items' => [
+                [
+                    'systemcontentfile1',
+                    'systemcontentfile2',
+                    'somesystemfile'
+                ],
+                'CONTENT',
+                [
+                    'systemcontentfile1',
+                    'systemcontentfile2'
+                ]
+            ]
+        ];
+    }
+
+    /**
+     * Test get_search_contents() by searching for content with users that have capability to access/view
+     * all existing content bank content. By default, admins, managers should be able to view every existing content
+     * that matches the search criteria.
+     */
+    public function test_get_search_contents_user_can_access_all_content() {
+        $this->resetAfterTest(true);
+
+        // Create a course in 'Miscellaneous' category by default.
+        $course = $this->getDataGenerator()->create_course();
+        $coursecontext = \context_course::instance($course->id);
+        // Create a course category without a course.
+        $category = $this->getDataGenerator()->create_category();
+        $categorycontext = \context_coursecat::instance($category->id);
+
+        $admin = get_admin();
+        // Add some content to the content bank in different contexts.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        // Add a content bank file in the category context.
+        $categorycontents = $generator->generate_contentbank_data('contenttype_h5p', 1, $admin->id,
+            $categorycontext, true, 'file.h5p', 'categorycontentfile');
+        $categorycontent = reset($categorycontents);
+        // Add a content bank file in the course context.
+        $coursecontents = $generator->generate_contentbank_data('contenttype_h5p', 1, $admin->id,
+            $coursecontext, true, 'file.h5p', 'coursecontentfile');
+        $coursecontent = reset($coursecontents);
+
+        // Log in as admin.
+        $this->setUser($admin);
+
+        // Search for content bank content items which have the pattern 'contentfile' within the name.
+        $search = 'contentfile';
+        $searchcontentnodes = \repository_contentbank\contentbank_search::get_search_contents($search);
+        // All content files which name matches the search criteria should be available to the admin user.
+        // The search should return 2 file nodes.
+        $this->assertCount(2, $searchcontentnodes);
+        $expected = [
+            \repository_contentbank\helper::create_contentbank_content_node($categorycontent),
+            \repository_contentbank\helper::create_contentbank_content_node($coursecontent),
+        ];
+        $this->assertEquals($expected, $searchcontentnodes, '', 0.0, 10, true);
+    }
+
+    /**
+     * Test get_search_contents() by searching for content with users that have capability to access/view only
+     * certain existing content bank content. By default, editing teachers should be able to view content that matches
+     * the search criteria AND is in the courses they are enrolled, course categories of the enrolled courses
+     * and system content. Other authenticated users should be able to access only the system content.
+     */
+    public function test_get_search_contents_user_can_access_certain_content() {
+        $this->resetAfterTest(true);
+
+        $systemcontext = \context_system::instance();
+        // Create course1.
+        $course1 = $this->getDataGenerator()->create_course();
+        $course1context = \context_course::instance($course1->id);
+        // Create course2.
+        $course2 = $this->getDataGenerator()->create_course();
+        $course2context = \context_course::instance($course2->id);
+
+        $admin = get_admin();
+        // Create and enrol an editing teacher in course1.
+        $editingteacher = $this->getDataGenerator()->create_and_enrol($course1, 'editingteacher');
+        // Create and enrol a teacher in course2.
+        $teacher = $this->getDataGenerator()->create_and_enrol($course2, 'teacher');
+
+        // Add some content to the content bank in different contexts.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        // Add a content bank file in the system context.
+        $systemcontents = $generator->generate_contentbank_data('contenttype_h5p', 1, $admin->id,
+            $systemcontext, true, 'file.h5p', 'systemcontentfile');
+        $systemcontent = reset($systemcontents);
+        // Add a content bank file in the course1 context.
+        $course1contents = $generator->generate_contentbank_data('contenttype_h5p', 1, $admin->id,
+            $course1context, true, 'file.h5p', 'coursecontentfile1');
+        $course1content = reset($course1contents);
+        // Add a content bank file in the course2 context.
+        $generator->generate_contentbank_data('contenttype_h5p', 1, $admin->id,
+            $course2context, true, 'file.h5p', 'coursecontentfile2');
+
+        // Log in as an editing teacher.
+        $this->setUser($editingteacher);
+        // Search for content bank content items which have the pattern 'contentfile' within the name.
+        $search = 'contentfile';
+        $searchcontentnodes = \repository_contentbank\contentbank_search::get_search_contents($search);
+        // The search should return 2 file nodes. The editing teacher does not have access to the content of course2
+        // and therefore, the course2 content should be skipped.
+        $this->assertCount(2, $searchcontentnodes);
+        $expected = [
+            \repository_contentbank\helper::create_contentbank_content_node($systemcontent),
+            \repository_contentbank\helper::create_contentbank_content_node($course1content),
+        ];
+        $this->assertEquals($expected, $searchcontentnodes, '', 0.0, 10, true);
+
+        // Log in as a teacher.
+        $this->setUser($teacher);
+        // Search for content bank content items which have the pattern 'contentfile' within the name.
+        $search = 'contentfile';
+        $searchcontentnodes = \repository_contentbank\contentbank_search::get_search_contents($search);
+        // The search should return 1 file node. The teacher should only be able to view system content.
+        $this->assertCount(1, $searchcontentnodes);
+        $expected = [
+            \repository_contentbank\helper::create_contentbank_content_node($systemcontent),
+        ];
+        $this->assertEquals($expected, $searchcontentnodes, '', 0.0, 10, true);
+    }
+}
index 8ec12d5..2a56474 100644 (file)
@@ -28,6 +28,7 @@
 require_once(__DIR__ . '/../../../lib/behat/core_behat_file_helper.php');
 
 use Behat\Mink\Exception\ExpectationException as ExpectationException,
+    Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
     Behat\Gherkin\Node\TableNode as TableNode;
 
 /**
@@ -342,7 +343,12 @@ class behat_filepicker extends behat_base {
             "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-content ')]" .
             "//a[contains(concat(' ', normalize-space(@class), ' '), ' fp-file ')]";
 
-        $elements = $this->find_all('xpath', $xpath);
+        try {
+            $elements = $this->find_all('xpath', $xpath);
+        } catch (ElementNotFoundException $e) {
+            $elements = [];
+        }
+
         // 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) .