Merge branch 'MDL-69762_master' of https://github.com/grillonbleu/moodle
authorSara Arjona <sara@moodle.com>
Tue, 16 Feb 2021 14:29:34 +0000 (15:29 +0100)
committerSara Arjona <sara@moodle.com>
Tue, 16 Feb 2021 14:29:34 +0000 (15:29 +0100)
31 files changed:
admin/settings/users.php
contentbank/amd/build/actions.min.js
contentbank/amd/build/actions.min.js.map
contentbank/amd/src/actions.js
contentbank/classes/content.php
contentbank/classes/contenttype.php
contentbank/classes/external/set_content_visibility.php [new file with mode: 0644]
contentbank/classes/output/bankcontent.php
contentbank/lib.php
contentbank/templates/bankcontent.mustache
contentbank/templates/bankcontent/search.mustache
contentbank/tests/behat/visibility.feature [new file with mode: 0644]
contentbank/tests/content_test.php
contentbank/tests/generator/lib.php
contentbank/upgrade.txt
contentbank/view.php
lang/en/contentbank.php
lang/en/error.php
lang/en/role.php
lib/behat/classes/behat_core_generator.php
lib/db/access.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/navigationlib.php
theme/boost/scss/moodle/contentbank.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css
user/classes/form/contentbank_user_preferences_form.php [new file with mode: 0644]
user/contentbank.php [new file with mode: 0644]
version.php

index c54a169..6e905dc 100644 (file)
@@ -87,6 +87,14 @@ if ($hassiteconfig
         $choices['1'] = new lang_string('trackforumsyes');
         $temp->add(new admin_setting_configselect('defaultpreference_trackforums', new lang_string('trackforums'),
             '', 0, $choices));
+
+        $choices = [];
+        $choices[\core_contentbank\content::VISIBILITY_PUBLIC] = new lang_string('visibilitychoicepublic', 'core_contentbank');
+        $choices[\core_contentbank\content::VISIBILITY_UNLISTED] = new lang_string('visibilitychoiceunlisted', 'core_contentbank');
+        $temp->add(new admin_setting_configselect('defaultpreference_core_contentbank_visibility',
+            new lang_string('visibilitypref', 'core_contentbank'),
+            new lang_string('visibilitypref_help', 'core_contentbank'),
+            \core_contentbank\content::VISIBILITY_PUBLIC, $choices));
     }
     $ADMIN->add('accounts', $temp);
 
index 584951a..c6587fd 100644 (file)
Binary files a/contentbank/amd/build/actions.min.js and b/contentbank/amd/build/actions.min.js differ
index cc73db5..7b70b8f 100644 (file)
Binary files a/contentbank/amd/build/actions.min.js.map and b/contentbank/amd/build/actions.min.js.map differ
index 47a5e10..4adb9ef 100644 (file)
@@ -40,6 +40,7 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents)
     var ACTIONS = {
         DELETE_CONTENT: '[data-action="deletecontent"]',
         RENAME_CONTENT: '[data-action="renamecontent"]',
+        SET_CONTENT_VISIBILITY: '[data-action="setcontentvisibility"]',
     };
 
     /**
@@ -181,6 +182,15 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents)
                 return;
             }).catch(Notification.exception);
         });
+
+        $(ACTIONS.SET_CONTENT_VISIBILITY).click(function(e) {
+            e.preventDefault();
+
+            var contentid = $(this).data('contentid');
+            var visibility = $(this).data('visibility');
+
+            setContentVisibility(contentid, visibility);
+        });
     };
 
     /**
@@ -262,6 +272,49 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents)
         }).catch(Notification.exception);
     }
 
+    /**
+     * Set content visibility in the content bank.
+     *
+     * @param {int} contentid The content to modify
+     * @param {int} visibility The new visibility value
+     */
+    function setContentVisibility(contentid, visibility) {
+        var request = {
+            methodname: 'core_contentbank_set_content_visibility',
+            args: {
+                contentid: contentid,
+                visibility: visibility
+            }
+        };
+        var requestType = 'success';
+        Ajax.call([request])[0].then(function(data) {
+            if (data.result) {
+                return 'contentvisibilitychanged';
+            }
+            requestType = 'error';
+            return data.warnings[0].message;
+
+        }).then(function(message) {
+            var params = null;
+            if (requestType == 'success') {
+                params = {
+                    id: contentid,
+                    statusmsg: message
+                };
+                // Redirect to the content view page and display the message as a notification.
+                window.location.href = Url.relativeUrl('contentbank/view.php', params, false);
+            } else {
+                // Fetch error notifications.
+                Notification.addNotification({
+                    message: message,
+                    type: 'error'
+                });
+                Notification.fetchNotifications();
+            }
+            return;
+        }).catch(Notification.exception);
+    }
+
     return /** @alias module:core_contentbank/actions */ {
         // Public variables and functions.
 
index 8048afe..d9ba668 100644 (file)
@@ -40,6 +40,17 @@ use core\event\contentbank_content_updated;
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 abstract class content {
+    /**
+     * @var int Visibility value. Public content is visible to all users with access to the content bank of the
+     * appropriate context.
+     */
+    public const VISIBILITY_PUBLIC = 1;
+
+    /**
+     * @var int Visibility value. Unlisted content is only visible to the author and to users with
+     * moodle/contentbank:viewunlistedcontent capability.
+     */
+    public const VISIBILITY_UNLISTED = 2;
 
     /** @var stdClass $content The content of the current instance. **/
     protected $content  = null;
@@ -250,6 +261,30 @@ abstract class content {
         return $this->content->configdata;
     }
 
+    /**
+     * Sets a new content visibility and saves it to database.
+     *
+     * @param int $visibility Must be self::PUBLIC or self::UNLISTED
+     * @return bool
+     * @throws coding_exception
+     */
+    public function set_visibility(int $visibility): bool {
+        if (!in_array($visibility, [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED])) {
+            return false;
+        }
+        $this->content->visibility = $visibility;
+        return $this->update_content();
+    }
+
+    /**
+     * Return true if the content may be shown to other users in the content bank.
+     *
+     * @return boolean
+     */
+    public function get_visibility(): int {
+        return $this->content->visibility;
+    }
+
     /**
      * Import a file as a valid content.
      *
@@ -356,8 +391,12 @@ abstract class content {
      * @return bool     True if content could be accessed. False otherwise.
      */
     public function is_view_allowed(): bool {
-        // There's no capability at content level to check,
-        // but plugins can overwrite this method in case they want to check something related to content properties.
-        return true;
+        // Plugins can overwrite this method in case they want to check something related to content properties.
+        global $USER;
+        $context = \context::instance_by_id($this->get_contextid());
+
+        return $USER->id == $this->content->usercreated ||
+            $this->get_visibility() == self::VISIBILITY_PUBLIC ||
+            has_capability('moodle/contentbank:viewunlistedcontent', $context);
     }
 }
index 8f9fec3..3295f24 100644 (file)
@@ -75,9 +75,16 @@ abstract class contenttype {
      * @return content  Object with content bank information.
      */
     public function create_content(\stdClass $record = null): content {
-        global $USER, $DB;
+        global $USER, $DB, $CFG;
 
         $entry = new \stdClass();
+        if (isset($record->visibility)) {
+            $entry->visibility = $record->visibility;
+        } else {
+            $usercreated = $record->usercreated ?? $USER->id;
+            $entry->visibility = get_user_preferences('core_contentbank_visibility',
+                $CFG->defaultpreference_core_contentbank_visibility, $usercreated);
+        }
         $entry->contenttype = $this->get_contenttype_name();
         $entry->contextid = $this->context->id;
         $entry->name = $record->name ?? '';
diff --git a/contentbank/classes/external/set_content_visibility.php b/contentbank/classes/external/set_content_visibility.php
new file mode 100644 (file)
index 0000000..dd5b880
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core_contentbank\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/externallib.php');
+
+use external_api;
+use external_function_parameters;
+use external_single_structure;
+use external_value;
+use external_warnings;
+
+/**
+ * External API to set the visibility of content bank content.
+ *
+ * @package    core_contentbank
+ * @copyright  2020 François Moreau
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class set_content_visibility extends external_api {
+    /**
+     * set_content_visibility parameters.
+     *
+     * @since  Moodle 3.11
+     * @return external_function_parameters
+     */
+    public static function execute_parameters(): external_function_parameters {
+        return new external_function_parameters(
+            [
+                'contentid' => new external_value(PARAM_INT, 'The content id to rename', VALUE_REQUIRED),
+                'visibility' => new external_value(PARAM_INT, 'The new visibility for the content', VALUE_REQUIRED),
+            ]
+        );
+    }
+
+    /**
+     * Set visibility of a content from the contentbank.
+     *
+     * @since  Moodle 3.11
+     * @param  int $contentid The content id to rename.
+     * @param  int $visibility The new visibility.
+     * @return array
+     */
+    public static function execute(int $contentid, int $visibility): array {
+        global $DB;
+
+        $result = false;
+        $warnings = [];
+
+        $params = self::validate_parameters(self::execute_parameters(), [
+            'contentid' => $contentid,
+            'visibility' => $visibility,
+        ]);
+
+        try {
+            $record = $DB->get_record('contentbank_content', ['id' => $params['contentid']], '*', MUST_EXIST);
+            $contenttypeclass = "\\$record->contenttype\\contenttype";
+            if (class_exists($contenttypeclass)) {
+                $context = \context::instance_by_id($record->contextid, MUST_EXIST);
+                self::validate_context($context);
+                $contenttype = new $contenttypeclass($context);
+                $contentclass = "\\$record->contenttype\\content";
+                $content = new $contentclass($record);
+                // Check capability.
+                if ($contenttype->can_manage($content)) {
+                    // This content's visibility can be changed.
+                    if ($content->set_visibility($params['visibility'])) {
+                        $result = true;
+                    } else {
+                        $warnings[] = [
+                            'item' => $params['contentid'],
+                            'warningcode' => 'contentvisibilitynotset',
+                            'message' => get_string('contentvisibilitynotset', 'core_contentbank')
+                        ];
+                    }
+
+                } else {
+                    // The user has no permission to manage this content.
+                    $warnings[] = [
+                        'item' => $params['contentid'],
+                        'warningcode' => 'nopermissiontomanage',
+                        'message' => get_string('nopermissiontomanage', 'core_contentbank')
+                    ];
+                }
+            }
+        } catch (\moodle_exception $e) {
+            // The content or the context don't exist.
+            $warnings[] = [
+                'item' => $params['contentid'],
+                'warningcode' => 'exception',
+                'message' => $e->getMessage()
+            ];
+        }
+
+        return [
+            'result' => $result,
+            'warnings' => $warnings
+        ];
+    }
+
+    /**
+     * set_content_visibility return.
+     *
+     * @since  Moodle 3.11
+     * @return external_single_structure
+     */
+    public static function execute_returns(): external_single_structure {
+        return new external_single_structure([
+            'result' => new external_value(PARAM_BOOL, 'The processing result'),
+            'warnings' => new external_warnings()
+        ]);
+    }
+}
index edd6356..2678eaf 100644 (file)
@@ -28,6 +28,7 @@ use renderable;
 use templatable;
 use renderer_base;
 use stdClass;
+use core_contentbank\content;
 
 /**
  * Class containing data for bank content
@@ -85,7 +86,11 @@ class bankcontent implements renderable, templatable {
             $mimetype = $file ? get_mimetype_description($file) : '';
             $contenttypeclass = $content->get_content_type().'\\contenttype';
             $contenttype = new $contenttypeclass($this->context);
-            $name = $content->get_name();
+            if ($content->get_visibility() == content::VISIBILITY_UNLISTED) {
+                $name = get_string('visibilitytitleunlisted', 'contentbank', $content->get_name());
+            } else {
+                $name = $content->get_name();
+            }
             $author = \core_user::get_user($content->get_content()->usercreated);
             $contentdata[] = array(
                 'name' => $name,
@@ -98,6 +103,7 @@ class bankcontent implements renderable, templatable {
                 'size' => display_size($filesize),
                 'type' => $mimetype,
                 'author' => fullname($author),
+                'visibilityunlisted' => $content->get_visibility() == content::VISIBILITY_UNLISTED
             );
         }
         $data->viewlist = get_user_preferences('core_contentbank_view_list');
index e709df7..f2ec543 100644 (file)
@@ -22,6 +22,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+use \core_contentbank\content;
+
 /**
  * Get the current user preferences that are available
  *
@@ -35,5 +37,10 @@ function core_contentbank_user_preferences() {
             'null' => NULL_NOT_ALLOWED,
             'default' => 'none'
         ],
+        'core_contentbank_visibility' => [
+            'choices' => [content::VISIBILITY_UNLISTED, content::VISIBILITY_PUBLIC],
+            'type' => PARAM_INT,
+            'null' => NULL_NOT_ALLOWED
+        ]
     ];
 }
index 50d1e6d..6b500e1 100644 (file)
@@ -29,7 +29,8 @@
                 "type": "Archive (H5P)",
                 "author": "Admin user",
                 "link": "http://something/contentbank/contenttype/h5p/view.php?url=http://something/pluginfile.php/1/contentbank/public/accordion.h5p",
-                "icon" : "http://something/theme/image.php/boost/core/1581597850/f/h5p-64"
+                "icon" : "http://something/theme/image.php/boost/core/1581597850/f/h5p-64",
+                "visibilityunlisted": true
             },
             {
                 "name": "resume.pdf",
@@ -40,7 +41,8 @@
                 "bytes": 716126,
                 "type": "Archive (PDF)",
                 "author": "Admin user",
-                "icon": "http://something/theme/image.php/boost/core/1584597850/f/pdf-64"
+                "icon": "http://something/theme/image.php/boost/core/1584597850/f/pdf-64",
+                "visibilityunlisted": false
             }
         ],
         "tools": [
@@ -155,7 +157,7 @@ data-region="contentbank">
                         </div>
                     </div>
                 {{#contents}}
-                    <div class="cb-listitem"
+                    <div class="cb-listitem {{#visibilityunlisted}}cb-unlisted{{/visibilityunlisted}}"
                         data-file="{{ title }}"
                         data-name="{{ name }}"
                         data-bytes="{{ bytes }}"
index 6fc2e58..0d9626c 100644 (file)
@@ -27,7 +27,5 @@
     {{$label}}{{#str}}
         searchcontentbankbyname, contentbank
     {{/str}}{{/label}}
-    {{$placeholder}}{{#str}}
-        search, core
-    {{/str}}{{/placeholder}}
+    {{$placeholder}}{{#str}} search, core {{/str}}{{/placeholder}}
 {{/ core/search_input_auto }}
diff --git a/contentbank/tests/behat/visibility.feature b/contentbank/tests/behat/visibility.feature
new file mode 100644 (file)
index 0000000..2c61901
--- /dev/null
@@ -0,0 +1,148 @@
+@core @core_contentbank @contentbank_h5p @javascript
+Feature: Make content public or unlisted
+  In order to make content public or unlisted
+  As a user
+  I need to be able to access the edition options
+
+  Background:
+    Given I log in as "admin"
+    And I am on site homepage
+    And I turn editing mode on
+    And I add the "Navigation" block if not present
+    And I configure the "Navigation" block
+    And I set the following fields to these values:
+      | Page contexts | Display throughout the entire site |
+    And I press "Save changes"
+
+  Scenario: Users can make their content public or unlisted
+    Given the following "contentbank content" exist:
+      | contextlevel | reference | contenttype     | user  | contentname             | filepath                                    | visibility |
+      | System       |           | contenttype_h5p | admin | filltheblanks.h5p       | /h5p/tests/fixtures/filltheblanks.h5p       | 1          |
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    And I click on "filltheblanks.h5p" "link"
+    And I wait until the page is ready
+    And I should not see "filltheblanks.h5p (Unlisted)" in the "h1" "css_element"
+    And I open the action menu in "region-main-settings-menu" "region"
+    And I should see "Make unlisted"
+    When I choose "Make unlisted" in the open action menu
+    And I wait until the page is ready
+    Then I should see "filltheblanks.h5p (Unlisted)" in the "h1" "css_element"
+    And I open the action menu in "region-main-settings-menu" "region"
+    And I should see "Make public"
+
+  Scenario: Unlisted content cannot be seen by other users
+    Given the following "users" exist:
+      | username  | firstname  | lastname  | email                 |
+      | teacher1  | Teacher    | 1         | teacher1@example.com  |
+      | teacher2  | Teacher    | 2         | teacher2@example.com  |
+    And the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "course enrolments" exist:
+      | user      | course  | role            |
+      | teacher1  | C1      | editingteacher  |
+      | teacher2  | C1      | editingteacher  |
+    And the following "contentbank content" exist:
+      | contextlevel | reference | contenttype     | user     | contentname             | filepath                                    | visibility |
+      | Course       | C1        | contenttype_h5p | teacher1 | filltheblanks.h5p       | /h5p/tests/fixtures/filltheblanks.h5p       | 2          |
+    And I log out
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    Then I should see "filltheblanks.h5p (Unlisted)"
+    And I log out
+    And I log in as "teacher2"
+    And I am on "Course 1" course homepage
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    Then I should not see "filltheblanks.h5p"
+
+  Scenario: Unlisted content is not found through search by other users
+    Given the following "users" exist:
+      | username  | firstname  | lastname  | email                 |
+      | teacher1  | Teacher    | 1         | teacher1@example.com  |
+      | teacher2  | Teacher    | 2         | teacher2@example.com  |
+    And the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "course enrolments" exist:
+      | user      | course  | role            |
+      | teacher1  | C1      | editingteacher  |
+      | teacher2  | C1      | editingteacher  |
+    And the following "contentbank content" exist:
+      | contextlevel | reference | contenttype     | user     | contentname             | filepath                                    | visibility |
+      | Course       | C1        | contenttype_h5p | teacher1 | filltheblanks.h5p       | /h5p/tests/fixtures/filltheblanks.h5p       | 2          |
+    And I log out
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    And I set the field "Search" to "filltheblanks.h5p"
+    And I should see "filltheblanks.h5p"
+    And I log out
+    And I log in as "teacher2"
+    And I am on "Course 1" course homepage
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    When I set the field "Search" to "filltheblanks.h5p"
+    Then I should not see "filltheblanks.h5p"
+
+  Scenario: Managers can see other users' unlisted content
+    Given the following "users" exist:
+      | username  | firstname  | lastname  | email                 |
+      | teacher1  | Teacher    | 1         | teacher1@example.com  |
+      | manager1  | Manager    | 1         | manager1@example.com  |
+    And the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "course enrolments" exist:
+      | user      | course  | role            |
+      | teacher1  | C1      | editingteacher  |
+      | manager1  | C1      | manager         |
+    And the following "contentbank content" exist:
+      | contextlevel | reference | contenttype     | user     | contentname             | filepath                                    | visibility |
+      | Course       | C1        | contenttype_h5p | teacher1 | filltheblanks.h5p       | /h5p/tests/fixtures/filltheblanks.h5p       | 2          |
+    And I log out
+    And I log in as "manager1"
+    And I am on "Course 1" course homepage
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    And I should see "filltheblanks.h5p (Unlisted)"
+    And I set the field "Search" to "filltheblanks.h5p"
+    And I should see "filltheblanks.h5p (Unlisted)"
+
+  @_file_upload
+  Scenario: Default content visibility can be set to unlisted
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And I set the following administration settings values:
+      | Default content visibility | 2 |
+    And I am on "Course 1" course homepage
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    And I click on "Upload" "link"
+    And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Upload content" filemanager
+    And I click on "Save changes" "button"
+    Then I should see "filltheblanks.h5p (Unlisted)" in the "h1" "css_element"
+
+  @_file_upload
+  Scenario: User preference concerning content visibility overrides site-wide default content visibility
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And I set the following administration settings values:
+      | Default content visibility | 2 |
+    And the following "user preferences" exist:
+      | user  | preference  | value |
+      | admin | core_contentbank_visibility  | 1  |
+    And I am on "Course 1" course homepage
+    And I click on "Site pages" "list_item" in the "Navigation" "block"
+    And I click on "Content bank" "link" in the "Navigation" "block"
+    And I click on "Upload" "link"
+    And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Upload content" filemanager
+    And I click on "Save changes" "button"
+    Then I should see "filltheblanks.h5p" in the "h1" "css_element"
+    And I should not see "filltheblanks.h5p (Unlisted)" in the "h1" "css_element"
index 7bc2e83..e51f116 100644 (file)
@@ -192,6 +192,35 @@ class core_contenttype_content_testcase extends \advanced_testcase {
         $this->assertEquals($newcontext->id, $file->get_contextid());
     }
 
+    /**
+     * Tests for set_visibility behaviour
+     *
+     * @covers ::set_visibility
+     */
+    public function test_set_visibility() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $context = context_system::instance();
+        $oldvisibility = content::VISIBILITY_PUBLIC;
+        $newvisibility = content::VISIBILITY_UNLISTED;
+        $illegalvisibility = -1;
+
+        $record = new stdClass();
+        $record->visibility = $oldvisibility;
+        $contenttype = new contenttype($context);
+        $content = $contenttype->create_content($record);
+
+        $this->assertEquals($oldvisibility, $content->get_visibility());
+
+        $content->set_visibility($newvisibility);
+
+        $this->assertEquals($newvisibility, $content->get_visibility());
+
+        $content->set_visibility($illegalvisibility);
+
+        $this->assertEquals($newvisibility, $content->get_visibility());
+    }
+
     /**
      * Tests for 'import_file' behaviour when replacing a file.
      *
@@ -299,6 +328,44 @@ class core_contenttype_content_testcase extends \advanced_testcase {
         $this->assertInstanceOf(get_class($type), $contenttype);
     }
 
+    /**
+     * Tests for 'is_view_allowed'.
+     *
+     * @covers ::is_view_allowed
+     */
+    public function test_is_view_allowed() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $context = context_system::instance();
+
+        $userauthor = $this->getDataGenerator()->create_user();
+        $userother = $this->getDataGenerator()->create_user();
+
+        $contenttype = new contenttype($context);
+
+        $unlistedrecord = new stdClass();
+        $unlistedrecord->visibility = content::VISIBILITY_UNLISTED;
+        $unlistedrecord->usercreated = $userauthor->id;
+        $unlistedcontent = $contenttype->create_content($unlistedrecord);
+
+        $publicrecord = new stdClass();
+        $publicrecord->visibility = content::VISIBILITY_PUBLIC;
+        $publicrecord->usercreated = $userauthor->id;
+        $publiccontent = $contenttype->create_content($publicrecord);
+
+        $this->setUser($userother);
+        $this->assertFalse($unlistedcontent->is_view_allowed());
+        $this->assertTrue($publiccontent->is_view_allowed());
+
+        $this->setUser($userauthor);
+        $this->assertTrue($unlistedcontent->is_view_allowed());
+        $this->assertTrue($publiccontent->is_view_allowed());
+
+        $this->setAdminUser();
+        $this->assertTrue($unlistedcontent->is_view_allowed());
+        $this->assertTrue($publiccontent->is_view_allowed());
+    }
+
     /**
      * Tests for 'get_uses' behaviour.
      *
index 4d7e022..5ca338e 100644 (file)
@@ -22,6 +22,9 @@
  * @copyright  2020 Sara Arjona <sara@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
+
+use core_contentbank\content;
+
 defined('MOODLE_INTERNAL') || die();
 
 global $CFG;
@@ -46,11 +49,12 @@ class core_contentbank_generator extends \component_generator_base {
      * @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.
+     * @param int $visibility The visibility 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',
-            string $contentname = 'Test content '): array {
+            string $contentname = 'Test content ', int $visibility = content::VISIBILITY_PUBLIC): array {
         global $DB, $USER;
 
         $records = [];
@@ -73,6 +77,7 @@ class core_contentbank_generator extends \component_generator_base {
             $record->name = ($itemstocreate === 1) ? $contentname : $contentname . $i;
             $record->configdata = '';
             $record->usercreated = $userid ?? $USER->id;
+            $record->visibility = $visibility;
 
             $content = $type->create_content($record);
             $record = $content->get_content();
index a94fbcf..8eca72b 100644 (file)
@@ -3,3 +3,4 @@ information provided here is intended especially for developers.
 
 === 3.11 ===
 * Added "get_uses()" method to content class to return places where a content is used.
+* Added set_visibility()/get_visibility() methods to let users decide if their content should be listed in the content bank.
index 5e4cf3d..8258fb2 100644 (file)
@@ -22,6 +22,8 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+use core_contentbank\content;
+
 require('../config.php');
 
 require_login();
@@ -50,22 +52,61 @@ if ($PAGE->course) {
     require_login($PAGE->course->id);
 }
 
+$cb = new \core_contentbank\contentbank();
+$content = $cb->get_content_from_id($record->id);
+$contenttype = $content->get_content_type_instance();
+$pageheading = $record->name;
+
+if (!$content->is_view_allowed()) {
+    print_error('notavailable', 'contentbank');
+}
+
+if ($content->get_visibility() == content::VISIBILITY_UNLISTED) {
+    $pageheading = get_string('visibilitytitleunlisted', 'contentbank', $record->name);
+}
+
 $PAGE->set_url(new \moodle_url('/contentbank/view.php', ['id' => $id]));
 $PAGE->set_context($context);
 $PAGE->navbar->add($record->name);
-$PAGE->set_heading($record->name);
+$PAGE->set_heading($pageheading);
 $title .= ": ".$record->name;
 $PAGE->set_title($title);
 $PAGE->set_pagetype('contentbank');
 
-$cb = new \core_contentbank\contentbank();
-$content = $cb->get_content_from_id($record->id);
-$contenttype = $content->get_content_type_instance();
-
 // Create the cog menu with all the secondary actions, such as delete, rename...
 $actionmenu = new action_menu();
 $actionmenu->set_alignment(action_menu::TR, action_menu::BR);
 if ($contenttype->can_manage($content)) {
+    // Add the visibility item to the menu.
+    switch($content->get_visibility()) {
+        case content::VISIBILITY_UNLISTED:
+            $visibilitylabel = get_string('visibilitysetpublic', 'core_contentbank');
+            $newvisibility = content::VISIBILITY_PUBLIC;
+            $visibilityicon = 't/hide';
+            break;
+        case content::VISIBILITY_PUBLIC:
+            $visibilitylabel = get_string('visibilitysetunlisted', 'core_contentbank');
+            $newvisibility = content::VISIBILITY_UNLISTED;
+            $visibilityicon = 't/show';
+            break;
+        default:
+            print_error('contentvisibilitynotfound', 'error', $returnurl, $content->get_visibility());
+            break;
+    }
+
+    $attributes = [
+        'data-action' => 'setcontentvisibility',
+        'data-visibility' => $newvisibility,
+        'data-contentid' => $content->get_id(),
+    ];
+    $actionmenu->add_secondary_action(new action_menu_link(
+        new moodle_url('#'),
+        new pix_icon($visibilityicon, $visibilitylabel),
+        $visibilitylabel,
+        false,
+        $attributes
+    ));
+
     // Add the rename content item to the menu.
     $attributes = [
         'data-action' => 'renamecontent',
@@ -130,7 +171,22 @@ if ($errormsg !== '' && get_string_manager()->string_exists($errormsg, 'core_con
     $errormsg = get_string($errormsg, 'core_contentbank');
     echo $OUTPUT->notification($errormsg);
 } else if ($statusmsg !== '' && get_string_manager()->string_exists($statusmsg, 'core_contentbank')) {
-    $statusmsg = get_string($statusmsg, 'core_contentbank');
+    if ($statusmsg == 'contentvisibilitychanged') {
+        switch ($content->get_visibility()) {
+            case content::VISIBILITY_PUBLIC:
+                $visibilitymsg = get_string('public', 'core_contentbank');
+                break;
+            case content::VISIBILITY_UNLISTED:
+                $visibilitymsg = get_string('unlisted', 'core_contentbank');
+                break;
+            default:
+                print_error('contentvisibilitynotfound', 'error', $returnurl, $content->get_visibility());
+                break;
+        }
+        $statusmsg = get_string($statusmsg, 'core_contentbank', $visibilitymsg);
+    } else {
+        $statusmsg = get_string($statusmsg, 'core_contentbank');
+    }
     echo $OUTPUT->notification($statusmsg, 'notifysuccess');
 }
 if ($contenttype->can_access()) {
index 648a474..ceeb7cb 100644 (file)
@@ -25,6 +25,7 @@
 $string['author'] = 'Author';
 $string['contentbank'] = 'Content bank';
 $string['close'] = 'Close';
+$string['contentbankpreferences'] = 'Content bank preferences';
 $string['contentdeleted'] = 'The content has been deleted.';
 $string['contentname'] = 'Content name';
 $string['contentnotdeleted'] = 'An error was encountered while trying to delete the content.';
@@ -33,6 +34,8 @@ $string['contentrenamed'] = 'The content has been renamed.';
 $string['contentsmoved'] = 'Content bank contents moved to {$a}.';
 $string['contenttypenoaccess'] = 'You cannot view this {$a} instance.';
 $string['contenttypenoedit'] = 'You can not edit this content';
+$string['contentvisibilitychanged'] = 'The content has been made {$a}.';
+$string['contentvisibilitynotset'] = 'An error was encountered while trying to set the content visibility.';
 $string['contextnotallowed'] = 'You are not allowed to access the content bank in this context.';
 $string['emptynamenotallowed'] = 'Empty name is not allowed';
 $string['eventcontentcreated'] = 'Content created';
@@ -55,6 +58,7 @@ $string['lastmodified'] = 'Last modified';
 $string['name'] = 'Content';
 $string['nocontentavailable'] = 'No content available';
 $string['nocontenttypes'] = 'No content types available';
+$string['notavailable'] = 'Sorry, this content is not available.';
 $string['nopermissiontodelete'] = 'You do not have permission to delete content.';
 $string['nopermissiontomanage'] = 'You do not have permission to manage content.';
 $string['privacy:metadata:content:contenttype'] = 'The contenttype plugin of the content in the content bank.';
@@ -76,3 +80,12 @@ $string['type'] = 'Type';
 $string['unsupported'] = 'This content type is not supported.';
 $string['upload'] = 'Upload';
 $string['uses'] = 'Places linked';
+$string['visibilitychoicepublic'] = 'Public';
+$string['visibilitychoiceunlisted'] = 'Unlisted';
+$string['public'] = 'public';
+$string['unlisted'] = 'unlisted';
+$string['visibilitypref'] = 'Default content visibility';
+$string['visibilitypref_help'] = 'Content you create in the content bank will use this visibility setting by default.';
+$string['visibilitysetpublic'] = 'Make public';
+$string['visibilitysetunlisted'] = 'Make unlisted';
+$string['visibilitytitleunlisted'] = '{$a} (Unlisted)';
index 6631f62..92ae88e 100644 (file)
@@ -182,6 +182,7 @@ $string['componentisuptodate'] = 'Component is up-to-date';
 $string['confirmationnotenabled'] = 'User confirmation is not enabled on this site';
 $string['confirmsesskeybad'] = 'Sorry, but your session key could not be confirmed to carry out this action.  This security feature prevents against accidental or malicious execution of important functions in your name.  Please make sure you really wanted to execute this function.';
 $string['contenttypenotfound'] = 'The \'{$a}\' content bank type doesn\'t exist or is not recognised.';
+$string['contentvisibilitynotfound'] = 'The content visibility with value \'{$a}\' doesn\'t exist or is not recognised.';
 $string['couldnotassignrole'] = 'A serious but unspecified error occurred while trying to assign a role to you';
 $string['couldnotupdatenoexistinguser'] = 'Cannot update the user - user doesn\'t exist';
 $string['couldnotverifyagedigitalconsent'] = 'An error occurred while trying to verify the age of digital consent.<br />Please contact administrator.';
index a9500d7..318e874 100644 (file)
@@ -156,6 +156,7 @@ $string['contentbank:deleteowncontent'] = 'Delete content from own content bank'
 $string['contentbank:downloadcontent'] = 'Download content from the content bank';
 $string['contentbank:manageanycontent'] = 'Manage any content from the content bank';
 $string['contentbank:manageowncontent'] = 'Manage content from own content bank';
+$string['contentbank:viewunlistedcontent'] = 'View unlisted content from the content bank';
 $string['contentbank:upload'] = 'Upload new content to the content bank';
 $string['contentbank:useeditor'] = 'Create or edit content using a content type editor';
 $string['context'] = 'Context';
index f36955c..8851be9 100644 (file)
@@ -925,6 +925,9 @@ class behat_core_generator extends behat_generator_base {
             $record = new stdClass();
             $record->usercreated = $data['userid'];
             $record->name = $data['contentname'];
+            if (isset($data['visibility'])) {
+                $record->visibility = $data['visibility'];
+            }
             $content = $contenttype->create_content($record);
 
             if (!empty($data['filepath'])) {
index bc812d5..d1cdce2 100644 (file)
@@ -2614,4 +2614,14 @@ $capabilities = array(
         'contextlevel' => CONTEXT_COURSE,
         'archetypes' => [],
     ],
+
+    // Allow users to view hidden content.
+    'moodle/contentbank:viewunlistedcontent' => [
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => [
+            'manager' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+        ]
+    ],
 );
index 87a8826..0dc74db 100644 (file)
         <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="contenttype" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="References context.id."/>
+        <FIELD NAME="visibility" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
         <FIELD NAME="instanceid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
         <FIELD NAME="configdata" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
         <FIELD NAME="usercreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The original author of the content"/>
index 17b2443..774fbd1 100644 (file)
@@ -2728,6 +2728,15 @@ $functions = array(
         'ajax'          => 'true',
         'capabilities'  => 'moodle/contentbank:manageowncontent',
     ],
+    'core_contentbank_set_content_visibility' => [
+        'classname'     => 'core_contentbank\external\set_content_visibility',
+        'methodname'    => 'execute',
+        'classpath'     => '',
+        'description'   => 'Set the visibility of a content in the content bank.',
+        'type'          => 'write',
+        'ajax'          => 'true',
+        'capabilities'  => 'moodle/contentbank:manageowncontent',
+    ],
     'core_create_userfeedback_action_record' => [
         'classname'     => 'core\external\record_userfeedback_action',
         'methodname'    => 'execute',
index 40efad0..1f4a4e1 100644 (file)
@@ -2393,5 +2393,19 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2021052500.55);
     }
 
+    if ($oldversion < 2021052500.59) {
+        // Define field visibility to be added to contentbank_content.
+        $table = new xmldb_table('contentbank_content');
+        $field = new xmldb_field('visibility', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'contextid');
+
+        // Conditionally launch add field visibility.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2021052500.59);
+    }
+
     return true;
 }
index 1af265b..531056e 100644 (file)
@@ -5104,12 +5104,22 @@ class settings_navigation extends navigation_node {
             }
         }
 
+        // Add "Content bank preferences" link.
+        if (isloggedin() && !isguestuser($user)) {
+            if ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext) ||
+                has_capability('moodle/user:editprofile', $usercontext)) {
+                $url = new moodle_url('/user/contentbank.php', ['id' => $user->id]);
+                $useraccount->add(get_string('contentbankpreferences', 'core_contentbank'), $url, self::TYPE_SETTING,
+                        null, 'contentbankpreferences');
+            }
+        }
+
         // View the roles settings.
-        if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override',
-                'moodle/role:manage'), $usercontext)) {
+        if (has_any_capability(['moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override',
+                'moodle/role:manage'], $usercontext)) {
             $roles = $usersetting->add(get_string('roles'), null, self::TYPE_SETTING);
 
-            $url = new moodle_url('/admin/roles/usersroles.php', array('userid'=>$user->id, 'courseid'=>$course->id));
+            $url = new moodle_url('/admin/roles/usersroles.php', ['userid' => $user->id, 'courseid' => $course->id]);
             $roles->add(get_string('thisusersroles', 'role'), $url, self::TYPE_SETTING);
 
             $assignableroles = get_assignable_roles($usercontext, ROLENAME_BOTH);
index b56ca61..92afeb4 100644 (file)
             margin-bottom: 0.5rem;
         }
 
+        .cb-listitem.cb-unlisted {
+            position: relative;
+        }
+
         @include media-breakpoint-down(sm) {
             .cb-listitem {
                 flex-basis: 50%;
             margin-right: auto;
             margin-bottom: 0.5rem;
         }
+
+        .cb-unlisted .cb-thumbnail {
+            opacity: .3;
+        }
+
+        /* Display a centered eye slash on top of unlisted content icons. */
+        .cb-unlisted::after {
+            position: absolute;
+            top: 20px;
+            left: 0;
+            width: 100%;
+            content: $fa-var-eye-slash;
+            font-family: FontAwesome;
+            font-size: 26px;
+            text-align: center;
+            opacity: 0.8;
+        }
+
         .cb-heading,
         .cb-uses,
         .cb-date,
             border-right: $border-width solid $border-color;
         }
 
+        .cb-listitem.cb-unlisted .cb-thumbnail {
+            opacity: .3;
+        }
+
+        .cb-listitem.cb-unlisted .cb-column,
+        .cb-listitem.cb-unlisted .cb-column a {
+            color: $text-muted;
+        }
+
         @include media-breakpoint-down(sm) {
             .cb-column {
                 flex: 0 0 50%;
index ce346d5..076e953 100644 (file)
@@ -13109,38 +13109,48 @@ table.calendartable caption {
   background-position: center;
   background-size: cover; }
 
-.content-bank-container.view-grid .cb-listitem {
-  margin-bottom: 0.5rem; }
-
-@media (max-width: 767.98px) {
+.content-bank-container.view-grid {
+  /* Display a centered eye slash on top of unlisted content icons. */ }
   .content-bank-container.view-grid .cb-listitem {
-    flex-basis: 50%; } }
-
-@media (min-width: 576px) {
-  .content-bank-container.view-grid .cb-listitem {
-    max-width: 120px;
-    min-width: 120px; } }
-
-.content-bank-container.view-grid .cb-name {
-  text-align: center; }
-
-.content-bank-container.view-grid .cb-file {
-  padding: 0.5rem; }
-
-.content-bank-container.view-grid .cb-thumbnail {
-  width: 64px;
-  height: 64px;
-  margin-left: auto;
-  margin-right: auto;
-  margin-bottom: 0.5rem; }
-
-.content-bank-container.view-grid .cb-heading,
-.content-bank-container.view-grid .cb-uses,
-.content-bank-container.view-grid .cb-date,
-.content-bank-container.view-grid .cb-size,
-.content-bank-container.view-grid .cb-type,
-.content-bank-container.view-grid .cb-author {
-  display: none; }
+    margin-bottom: 0.5rem; }
+  .content-bank-container.view-grid .cb-listitem.cb-unlisted {
+    position: relative; }
+  @media (max-width: 767.98px) {
+    .content-bank-container.view-grid .cb-listitem {
+      flex-basis: 50%; } }
+  @media (min-width: 576px) {
+    .content-bank-container.view-grid .cb-listitem {
+      max-width: 120px;
+      min-width: 120px; } }
+  .content-bank-container.view-grid .cb-name {
+    text-align: center; }
+  .content-bank-container.view-grid .cb-file {
+    padding: 0.5rem; }
+  .content-bank-container.view-grid .cb-thumbnail {
+    width: 64px;
+    height: 64px;
+    margin-left: auto;
+    margin-right: auto;
+    margin-bottom: 0.5rem; }
+  .content-bank-container.view-grid .cb-unlisted .cb-thumbnail {
+    opacity: .3; }
+  .content-bank-container.view-grid .cb-unlisted::after {
+    position: absolute;
+    top: 20px;
+    left: 0;
+    width: 100%;
+    content: "";
+    font-family: FontAwesome;
+    font-size: 26px;
+    text-align: center;
+    opacity: 0.8; }
+  .content-bank-container.view-grid .cb-heading,
+  .content-bank-container.view-grid .cb-uses,
+  .content-bank-container.view-grid .cb-date,
+  .content-bank-container.view-grid .cb-size,
+  .content-bank-container.view-grid .cb-type,
+  .content-bank-container.view-grid .cb-author {
+    display: none; }
 
 .content-bank-container.view-list .cb-content-wrapper {
   padding: 0 0.5rem;
@@ -13164,6 +13174,13 @@ table.calendartable caption {
 .content-bank-container.view-list .cb-column {
   border-right: 1px solid #dee2e6; }
 
+.content-bank-container.view-list .cb-listitem.cb-unlisted .cb-thumbnail {
+  opacity: .3; }
+
+.content-bank-container.view-list .cb-listitem.cb-unlisted .cb-column,
+.content-bank-container.view-list .cb-listitem.cb-unlisted .cb-column a {
+  color: #6c757d; }
+
 @media (max-width: 767.98px) {
   .content-bank-container.view-list .cb-column {
     flex: 0 0 50%;
index 7cdfba1..60e19a2 100644 (file)
@@ -13323,38 +13323,48 @@ table.calendartable caption {
   background-position: center;
   background-size: cover; }
 
-.content-bank-container.view-grid .cb-listitem {
-  margin-bottom: 0.5rem; }
-
-@media (max-width: 767.98px) {
+.content-bank-container.view-grid {
+  /* Display a centered eye slash on top of unlisted content icons. */ }
   .content-bank-container.view-grid .cb-listitem {
-    flex-basis: 50%; } }
-
-@media (min-width: 576px) {
-  .content-bank-container.view-grid .cb-listitem {
-    max-width: 120px;
-    min-width: 120px; } }
-
-.content-bank-container.view-grid .cb-name {
-  text-align: center; }
-
-.content-bank-container.view-grid .cb-file {
-  padding: 0.5rem; }
-
-.content-bank-container.view-grid .cb-thumbnail {
-  width: 64px;
-  height: 64px;
-  margin-left: auto;
-  margin-right: auto;
-  margin-bottom: 0.5rem; }
-
-.content-bank-container.view-grid .cb-heading,
-.content-bank-container.view-grid .cb-uses,
-.content-bank-container.view-grid .cb-date,
-.content-bank-container.view-grid .cb-size,
-.content-bank-container.view-grid .cb-type,
-.content-bank-container.view-grid .cb-author {
-  display: none; }
+    margin-bottom: 0.5rem; }
+  .content-bank-container.view-grid .cb-listitem.cb-unlisted {
+    position: relative; }
+  @media (max-width: 767.98px) {
+    .content-bank-container.view-grid .cb-listitem {
+      flex-basis: 50%; } }
+  @media (min-width: 576px) {
+    .content-bank-container.view-grid .cb-listitem {
+      max-width: 120px;
+      min-width: 120px; } }
+  .content-bank-container.view-grid .cb-name {
+    text-align: center; }
+  .content-bank-container.view-grid .cb-file {
+    padding: 0.5rem; }
+  .content-bank-container.view-grid .cb-thumbnail {
+    width: 64px;
+    height: 64px;
+    margin-left: auto;
+    margin-right: auto;
+    margin-bottom: 0.5rem; }
+  .content-bank-container.view-grid .cb-unlisted .cb-thumbnail {
+    opacity: .3; }
+  .content-bank-container.view-grid .cb-unlisted::after {
+    position: absolute;
+    top: 20px;
+    left: 0;
+    width: 100%;
+    content: "";
+    font-family: FontAwesome;
+    font-size: 26px;
+    text-align: center;
+    opacity: 0.8; }
+  .content-bank-container.view-grid .cb-heading,
+  .content-bank-container.view-grid .cb-uses,
+  .content-bank-container.view-grid .cb-date,
+  .content-bank-container.view-grid .cb-size,
+  .content-bank-container.view-grid .cb-type,
+  .content-bank-container.view-grid .cb-author {
+    display: none; }
 
 .content-bank-container.view-list .cb-content-wrapper {
   padding: 0 0.5rem;
@@ -13378,6 +13388,13 @@ table.calendartable caption {
 .content-bank-container.view-list .cb-column {
   border-right: 1px solid #dee2e6; }
 
+.content-bank-container.view-list .cb-listitem.cb-unlisted .cb-thumbnail {
+  opacity: .3; }
+
+.content-bank-container.view-list .cb-listitem.cb-unlisted .cb-column,
+.content-bank-container.view-list .cb-listitem.cb-unlisted .cb-column a {
+  color: #6c757d; }
+
 @media (max-width: 767.98px) {
   .content-bank-container.view-list .cb-column {
     flex: 0 0 50%;
diff --git a/user/classes/form/contentbank_user_preferences_form.php b/user/classes/form/contentbank_user_preferences_form.php
new file mode 100644 (file)
index 0000000..02782d0
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core_user\form;
+
+use \core_contentbank\content;
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->dirroot.'/lib/formslib.php');
+
+/**
+ * Form to edit a user's preferences concerning the content bank.
+ *
+ * @package core_user
+ * @copyright 2020 François Moreau
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class contentbank_user_preferences_form extends \moodleform {
+
+    /**
+     * Define the form.
+     */
+    public function definition () {
+        global $CFG, $USER;
+
+        $mform = $this->_form;
+
+        $mform->addElement('hidden', 'id');
+        $mform->setType('id', PARAM_INT);
+
+        $options = [
+            content::VISIBILITY_PUBLIC  => get_string('visibilitychoicepublic', 'core_contentbank'),
+            content::VISIBILITY_UNLISTED => get_string('visibilitychoiceunlisted', 'core_contentbank')
+        ];
+        $mform->addElement('select', 'contentvisibility', get_string('visibilitypref', 'core_contentbank'), $options);
+        $mform->addHelpButton('contentvisibility', 'visibilitypref', 'core_contentbank');
+        $this->add_action_buttons(true, get_string('savechanges'));
+    }
+}
diff --git a/user/contentbank.php b/user/contentbank.php
new file mode 100644 (file)
index 0000000..e435cff
--- /dev/null
@@ -0,0 +1,71 @@
+<?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/>.
+
+/**
+ * Allows you to edit a users profile
+ *
+ * @copyright 2020 François Moreau
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package core_user
+ */
+
+require_once('../config.php');
+require_once($CFG->dirroot.'/user/editlib.php');
+require_once($CFG->dirroot.'/user/lib.php');
+
+require_login();
+
+$userid = optional_param('id', $USER->id, PARAM_INT);    // User id.
+
+$PAGE->set_url('/user/contentbank.php', ['id' => $userid]);
+
+list($user, $course) = useredit_setup_preference_page($userid, SITEID);
+
+$form = new \core_user\form\contentbank_user_preferences_form(null, ['userid' => $user->id]);
+
+$user->contentvisibility = get_user_preferences('core_contentbank_visibility',
+    $CFG->defaultpreference_core_contentbank_visibility, $user->id);
+
+$form->set_data($user);
+
+$redirect = new moodle_url("/user/preferences.php", ['userid' => $user->id]);
+
+if ($form->is_cancelled()) {
+    redirect($redirect);
+} else if ($data = $form->get_data()) {
+    $data = $form->get_data();
+    $usernew = [
+        'id' => $user->id,
+        'preference_core_contentbank_visibility' => $data->contentvisibility
+    ];
+    useredit_update_user_preference($usernew);
+
+    \core\event\user_updated::create_from_userid($user->id)->trigger();
+    redirect($redirect);
+}
+
+$title = get_string('contentbankpreferences', 'core_contentbank');
+$userfullname = fullname($user, true);
+
+$PAGE->navbar->includesettingsbase = true;
+
+$PAGE->set_title("$course->shortname: $title");
+$PAGE->set_heading($userfullname);
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading($title);
+$form->display();
+echo $OUTPUT->footer();
index 4094cb8..8a0d56b 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2021052500.58;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2021052500.59;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
 $release  = '4.0dev (Build: 20210211)'; // Human-friendly version name