Merge branch 'MDL-63805-master' of git://github.com/jleyva/moodle into master
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 14 Oct 2020 19:22:02 +0000 (21:22 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 14 Oct 2020 19:22:02 +0000 (21:22 +0200)
mod/glossary/classes/external.php
mod/glossary/classes/external/prepare_entry.php [new file with mode: 0644]
mod/glossary/classes/external/update_entry.php [new file with mode: 0644]
mod/glossary/db/services.php
mod/glossary/edit.php
mod/glossary/lib.php
mod/glossary/tests/external/prepare_entry.php [new file with mode: 0644]
mod/glossary/tests/external/update_entry.php [new file with mode: 0644]
mod/glossary/tests/lib_test.php
mod/glossary/version.php

index 352c702..98acf59 100644 (file)
@@ -1397,7 +1397,7 @@ class mod_glossary_external extends external_api {
 
         // Get and validate the glossary.
         $entry = $DB->get_record('glossary_entries', array('id' => $id), '*', MUST_EXIST);
-        list($glossary, $context) = self::validate_glossary($entry->glossaryid);
+        list($glossary, $context, $course, $cm) = self::validate_glossary($entry->glossaryid);
 
         if (empty($entry->approved) && $entry->userid != $USER->id && !has_capability('mod/glossary:approve', $context)) {
             throw new invalid_parameter_exception('invalidentry');
@@ -1409,6 +1409,7 @@ class mod_glossary_external extends external_api {
         // Permissions (for entry edition).
         $permissions = [
             'candelete' => mod_glossary_can_delete_entry($entry, $glossary, $context),
+            'canupdate' => mod_glossary_can_update_entry($entry, $glossary, $context, $cm),
         ];
 
         return array(
@@ -1433,6 +1434,7 @@ class mod_glossary_external extends external_api {
             'permissions' => new external_single_structure(
                 [
                     'candelete' => new external_value(PARAM_BOOL, 'Whether the user can delete the entry.'),
+                    'canupdate' => new external_value(PARAM_BOOL, 'Whether the user can update the entry.'),
                 ],
                 'User permissions for the managing the entry.', VALUE_OPTIONAL
             ),
diff --git a/mod/glossary/classes/external/prepare_entry.php b/mod/glossary/classes/external/prepare_entry.php
new file mode 100644 (file)
index 0000000..77134f4
--- /dev/null
@@ -0,0 +1,153 @@
+<?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 is the external method for preparing a entry for edition.
+ *
+ * @package    mod_glossary
+ * @since      Moodle 3.10
+ * @copyright  2020 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_glossary\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/externallib.php');
+require_once($CFG->dirroot . '/mod/glossary/lib.php');
+
+use external_api;
+use external_function_parameters;
+use external_multiple_structure;
+use external_single_structure;
+use external_value;
+use external_warnings;
+
+/**
+ * This is the external method for preparing a entry for edition.
+ *
+ * @copyright  2020 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class prepare_entry extends external_api {
+    /**
+     * Parameters.
+     *
+     * @return external_function_parameters
+     */
+    public static function execute_parameters(): external_function_parameters {
+        return new external_function_parameters([
+            'entryid' => new external_value(PARAM_INT, 'Glossary entry id to update'),
+        ]);
+    }
+
+    /**
+     * Prepare for update the indicated entry from the glossary.
+     *
+     * @param  int $entryid The entry to update
+     * @return array with result and warnings
+     * @throws moodle_exception
+     */
+    public static function execute(int $entryid): array {
+        global $DB;
+
+        $params = self::validate_parameters(self::execute_parameters(), compact('entryid'));
+        $id = $params['entryid'];
+
+        // Get and validate the glossary.
+        $entry = $DB->get_record('glossary_entries', ['id' => $id], '*', MUST_EXIST);
+        list($glossary, $context, $course, $cm) = \mod_glossary_external::validate_glossary($entry->glossaryid);
+
+        // Check permissions.
+        mod_glossary_can_update_entry($entry, $glossary, $context, $cm, false);
+
+        list($definitionoptions, $attachmentoptions) = glossary_get_editor_and_attachment_options($course, $context, $entry);
+
+        $entry->aliases = '';
+        $entry->categories = [];
+        $entry = mod_glossary_prepare_entry_for_edition($entry);
+        $entry = file_prepare_standard_editor($entry, 'definition', $definitionoptions, $context, 'mod_glossary', 'entry',
+            $entry->id);
+        $entry = file_prepare_standard_filemanager($entry, 'attachment', $attachmentoptions, $context, 'mod_glossary', 'attachment',
+            $entry->id);
+
+        // Just get a structure compatible with external API.
+        array_walk($definitionoptions, function(&$item, $key) use (&$definitionoptions) {
+            if (!is_scalar($item)) {
+                unset($definitionoptions[$key]);
+                return;
+            }
+            $item = ['name' => $key, 'value' => $item];
+        });
+
+        array_walk($attachmentoptions, function(&$item, $key) use (&$attachmentoptions) {
+            if (!is_scalar($item)) {
+                unset($attachmentoptions[$key]);
+                return;
+            }
+            $item = ['name' => $key, 'value' => $item];
+        });
+
+        return [
+            'inlineattachmentsid' => $entry->definition_editor['itemid'],
+            'attachmentsid' => $entry->attachment_filemanager,
+            'areas' => [
+                [
+                    'area' => 'definition',
+                    'options' => $definitionoptions,
+                ],
+                [
+                    'area' => 'attachment',
+                    'options' => $attachmentoptions,
+                ],
+            ],
+            'aliases' => explode("\n", trim($entry->aliases)),
+            'categories' => $entry->categories,
+        ];
+    }
+
+    /**
+     * Return.
+     *
+     * @return external_single_structure
+     */
+    public static function execute_returns(): external_single_structure {
+        return new external_single_structure([
+            'inlineattachmentsid' => new external_value(PARAM_INT, 'Draft item id for the text editor.'),
+            'attachmentsid' => new external_value(PARAM_INT, 'Draft item id for the file manager.'),
+            'areas' => new external_multiple_structure(
+                new external_single_structure(
+                    [
+                        'area' => new external_value(PARAM_ALPHA, 'File area name.'),
+                        'options' => new external_multiple_structure(
+                            new external_single_structure(
+                                [
+                                    'name' => new external_value(PARAM_RAW, 'Name of option.'),
+                                    'value' => new external_value(PARAM_RAW, 'Value of option.'),
+                                ]
+                            ), 'Draft file area options.'
+                        )
+                    ]
+                ), 'File areas including options'
+            ),
+            'aliases' => new external_multiple_structure(new external_value(PARAM_RAW, 'Alias name.')),
+            'categories' => new external_multiple_structure(new external_value(PARAM_INT, 'Category id')),
+            'warnings' => new external_warnings(),
+        ]);
+    }
+}
diff --git a/mod/glossary/classes/external/update_entry.php b/mod/glossary/classes/external/update_entry.php
new file mode 100644 (file)
index 0000000..695fbb7
--- /dev/null
@@ -0,0 +1,176 @@
+<?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 is the external method for updating a glossary entry.
+ *
+ * @package    mod_glossary
+ * @since      Moodle 3.10
+ * @copyright  2020 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_glossary\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/externallib.php');
+require_once($CFG->dirroot . '/mod/glossary/lib.php');
+
+use external_api;
+use external_function_parameters;
+use external_multiple_structure;
+use external_single_structure;
+use external_value;
+use external_format_value;
+use external_warnings;
+use core_text;
+use moodle_exception;
+
+/**
+ * This is the external method for updating a glossary entry.
+ *
+ * @copyright  2020 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class update_entry extends external_api {
+    /**
+     * Parameters.
+     *
+     * @return external_function_parameters
+     */
+    public static function execute_parameters(): external_function_parameters {
+        return new external_function_parameters([
+            'entryid' => new external_value(PARAM_INT, 'Glossary entry id to update'),
+            'concept' => new external_value(PARAM_TEXT, 'Glossary concept'),
+            'definition' => new external_value(PARAM_RAW, 'Glossary concept definition'),
+            'definitionformat' => new external_format_value('definition'),
+            'options' => new external_multiple_structure (
+                new external_single_structure(
+                    [
+                        'name' => new external_value(PARAM_ALPHANUM,
+                            'The allowed keys (value format) are:
+                            inlineattachmentsid (int); the draft file area id for inline attachments
+                            attachmentsid (int); the draft file area id for attachments
+                            categories (comma separated int); comma separated category ids
+                            aliases (comma separated str); comma separated aliases
+                            usedynalink (bool); whether the entry should be automatically linked.
+                            casesensitive (bool); whether the entry is case sensitive.
+                            fullmatch (bool); whether to match whole words only.'),
+                        'value' => new external_value(PARAM_RAW, 'the value of the option (validated inside the function)')
+                    ]
+                ), 'Optional settings', VALUE_DEFAULT, []
+            )
+        ]);
+    }
+
+    /**
+     * Update the indicated glossary entry.
+     *
+     * @param  int $entryid The entry to update
+     * @param string $concept    the glossary concept
+     * @param string $definition the concept definition
+     * @param int $definitionformat the concept definition format
+     * @param array  $options    additional settings
+     * @return array with result and warnings
+     * @throws moodle_exception
+     */
+    public static function execute(int $entryid, string $concept, string $definition, int $definitionformat,
+            array $options = []): array {
+
+        global $DB;
+
+        $params = self::validate_parameters(self::execute_parameters(), compact('entryid', 'concept', 'definition',
+            'definitionformat', 'options'));
+        $id = $params['entryid'];
+
+        // Get and validate the glossary entry.
+        $entry = $DB->get_record('glossary_entries', ['id' => $id], '*', MUST_EXIST);
+        list($glossary, $context, $course, $cm) = \mod_glossary_external::validate_glossary($entry->glossaryid);
+
+        // Check if the user can update the entry.
+        mod_glossary_can_update_entry($entry, $glossary, $context, $cm, false);
+
+        // Check for duplicates if the concept changes.
+        if (!$glossary->allowduplicatedentries &&
+                core_text::strtolower($entry->concept) != core_text::strtolower(trim($params['concept']))) {
+
+            if (glossary_concept_exists($glossary, $params['concept'])) {
+                throw new moodle_exception('errconceptalreadyexists', 'glossary');
+            }
+        }
+
+        // Prepare the entry object.
+        $entry->aliases = '';
+        $entry = mod_glossary_prepare_entry_for_edition($entry);
+        $entry->concept = $params['concept'];
+        $entry->definition_editor = [
+            'text' => $params['definition'],
+            'format' => $params['definitionformat'],
+        ];
+        // Options.
+        foreach ($params['options'] as $option) {
+            $name = trim($option['name']);
+            switch ($name) {
+                case 'inlineattachmentsid':
+                    $entry->definition_editor['itemid'] = clean_param($option['value'], PARAM_INT);
+                    break;
+                case 'attachmentsid':
+                    $entry->attachment_filemanager = clean_param($option['value'], PARAM_INT);
+                    break;
+                case 'categories':
+                    $entry->categories = clean_param($option['value'], PARAM_SEQUENCE);
+                    $entry->categories = explode(',', $entry->categories);
+                    break;
+                case 'aliases':
+                    $entry->aliases = clean_param($option['value'], PARAM_NOTAGS);
+                    // Convert to the expected format.
+                    $entry->aliases = str_replace(",", "\n", $entry->aliases);
+                    break;
+                case 'usedynalink':
+                case 'casesensitive':
+                case 'fullmatch':
+                    // Only allow if linking is enabled.
+                    if ($glossary->usedynalink) {
+                        $entry->{$name} = clean_param($option['value'], PARAM_BOOL);
+                    }
+                    break;
+                default:
+                    throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
+            }
+        }
+
+        $entry = glossary_edit_entry($entry, $course, $cm, $glossary, $context);
+
+        return [
+            'result' => true,
+            'warnings' => [],
+        ];
+    }
+
+    /**
+     * Return.
+     *
+     * @return external_single_structure
+     */
+    public static function execute_returns(): external_single_structure {
+        return new external_single_structure([
+            'result' => new external_value(PARAM_BOOL, 'The update result'),
+            'warnings' => new external_warnings()
+        ]);
+    }
+}
index 74c4f5d..1584e01 100644 (file)
@@ -170,4 +170,22 @@ $functions = array(
         'type'          => 'write',
         'services'      => [MOODLE_OFFICIAL_MOBILE_SERVICE]
     ],
+
+    'mod_glossary_update_entry' => [
+        'classname'     => 'mod_glossary\external\update_entry',
+        'methodname'    => 'execute',
+        'classpath'     => '',
+        'description'   => 'Updates the given glossary entry.',
+        'type'          => 'write',
+        'services'      => [MOODLE_OFFICIAL_MOBILE_SERVICE]
+    ],
+
+    'mod_glossary_prepare_entry_for_edition' => [
+        'classname'     => 'mod_glossary\external\prepare_entry',
+        'methodname'    => 'execute',
+        'classpath'     => '',
+        'description'   => 'Prepares the given entry for edition returning draft item areas and file areas information.',
+        'type'          => 'read',
+        'services'      => [MOODLE_OFFICIAL_MOBILE_SERVICE]
+    ],
 );
index 1e5461f..15d06e5 100644 (file)
@@ -38,23 +38,10 @@ if ($id) { // if entry is specified
         print_error('invalidentry');
     }
 
-    $ineditperiod = ((time() - $entry->timecreated <  $CFG->maxeditingtime) || $glossary->editalways);
-    if (!has_capability('mod/glossary:manageentries', $context) and !($entry->userid == $USER->id and ($ineditperiod and has_capability('mod/glossary:write', $context)))) {
-        if ($USER->id != $entry->userid) {
-            print_error('errcannoteditothers', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$id");
-        } elseif (!$ineditperiod) {
-            print_error('erredittimeexpired', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$id");
-        }
-    }
-
-    //prepare extra data
-    if ($aliases = $DB->get_records_menu("glossary_alias", array("entryid"=>$id), '', 'id, alias')) {
-        $entry->aliases = implode("\n", $aliases) . "\n";
-    }
-    if ($categoriesarr = $DB->get_records_menu("glossary_entries_categories", array('entryid'=>$id), '', 'id, categoryid')) {
-        // TODO: this fetches cats from both main and secondary glossary :-(
-        $entry->categories = array_values($categoriesarr);
-    }
+    // Check if the user can update the entry (trigger exception if he can't).
+    mod_glossary_can_update_entry($entry, $glossary, $context, $cm, false);
+    // Prepare extra data.
+    $entry = mod_glossary_prepare_entry_for_edition($entry);
 
 } else { // new entry
     require_capability('mod/glossary:write', $context);
index a89b01d..afae7fe 100644 (file)
@@ -4455,3 +4455,60 @@ function mod_glossary_delete_entry($entry, $glossary, $cm, $context, $course, $h
         \mod_glossary\local\concept_cache::reset_glossary($glossary);
     }
 }
+
+/**
+ * Checks if the current user can update the given glossary entry.
+ *
+ * @since Moodle 3.10
+ * @param stdClass $entry the entry database object
+ * @param stdClass $glossary the glossary database object
+ * @param stdClass $context the glossary context
+ * @param object $cm the course module object (cm record or cm_info instance)
+ * @param bool $return Whether to return a boolean value or stop the execution (exception)
+ * @return bool if the user can update the entry
+ * @throws moodle_exception
+ */
+function mod_glossary_can_update_entry(stdClass $entry, stdClass $glossary, stdClass $context, object $cm,
+        bool $return = true): bool {
+
+    global $USER, $CFG;
+
+    $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
+    if (!has_capability('mod/glossary:manageentries', $context) and
+            !($entry->userid == $USER->id and ($ineditperiod and has_capability('mod/glossary:write', $context)))) {
+
+        if ($USER->id != $entry->userid) {
+            if ($return) {
+                return false;
+            }
+            throw new moodle_exception('errcannoteditothers', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id");
+        } else if (!$ineditperiod) {
+            if ($return) {
+                return false;
+            }
+            throw new moodle_exception('erredittimeexpired', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id");
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Prepares an entry for editing, adding aliases and category information.
+ *
+ * @param  stdClass $entry the entry being edited
+ * @return stdClass the entry with the additional data
+ */
+function mod_glossary_prepare_entry_for_edition(stdClass $entry): stdClass {
+    global $DB;
+
+    if ($aliases = $DB->get_records_menu("glossary_alias", ["entryid" => $entry->id], '', 'id, alias')) {
+        $entry->aliases = implode("\n", $aliases) . "\n";
+    }
+    if ($categoriesarr = $DB->get_records_menu("glossary_entries_categories", ['entryid' => $entry->id], '', 'id, categoryid')) {
+        // TODO: this fetches cats from both main and secondary glossary :-(
+        $entry->categories = array_values($categoriesarr);
+    }
+
+    return $entry;
+}
diff --git a/mod/glossary/tests/external/prepare_entry.php b/mod/glossary/tests/external/prepare_entry.php
new file mode 100644 (file)
index 0000000..b5870a5
--- /dev/null
@@ -0,0 +1,83 @@
+<?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/>.
+
+/**
+ * External function test for prepare_entry.
+ *
+ * @package    mod_glossary
+ * @category   external
+ * @since      Moodle 3.10
+ * @copyright  2020 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_glossary\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+use external_api;
+use externallib_advanced_testcase;
+use mod_glossary_external;
+use context_module;
+use context_user;
+use external_util;
+
+/**
+ * External function test for prepare_entry.
+ *
+ * @package    mod_glossary
+ * @copyright  2020 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class prepare_entry_testcase extends externallib_advanced_testcase {
+
+    /**
+     * test_prepare_entry
+     */
+    public function test_prepare_entry() {
+        global $USER;
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
+        $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
+
+        $this->setAdminUser();
+        $aliases = ['alias1', 'alias2'];
+        $entry = $gg->create_content(
+            $glossary,
+            ['approved' => 1, 'userid' => $USER->id],
+            $aliases
+        );
+
+        $cat1 = $gg->create_category($glossary, [], [$entry]);
+        $gg->create_category($glossary);
+
+        $return = prepare_entry::execute($entry->id);
+        $return = external_api::clean_returnvalue(prepare_entry::execute_returns(), $return);
+
+        $this->assertNotEmpty($return['inlineattachmentsid']);
+        $this->assertNotEmpty($return['attachmentsid']);
+        $this->assertEquals($aliases, $return['aliases']);
+        $this->assertEquals([$cat1->id], $return['categories']);
+        $this->assertCount(2, $return['areas']);
+        $this->assertNotEmpty($return['areas'][0]['options']);
+        $this->assertNotEmpty($return['areas'][1]['options']);
+    }
+}
diff --git a/mod/glossary/tests/external/update_entry.php b/mod/glossary/tests/external/update_entry.php
new file mode 100644 (file)
index 0000000..ed6ffc5
--- /dev/null
@@ -0,0 +1,297 @@
+<?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/>.
+
+/**
+ * External function test for update_entry.
+ *
+ * @package    mod_glossary
+ * @category   external
+ * @since      Moodle 3.10
+ * @copyright  2020 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_glossary\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+use external_api;
+use externallib_advanced_testcase;
+use mod_glossary_external;
+use context_module;
+use context_user;
+use external_util;
+
+/**
+ * External function test for update_entry.
+ *
+ * @package    mod_glossary
+ * @copyright  2020 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class update_entry_testcase extends externallib_advanced_testcase {
+
+    /**
+     * test_update_entry_without_optional_settings
+     */
+    public function test_update_entry_without_optional_settings() {
+        global $CFG, $DB;
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
+
+        $this->setAdminUser();
+        $concept = 'A concept';
+        $definition = '<p>A definition</p>';
+        $return = mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML);
+        $return = external_api::clean_returnvalue(mod_glossary_external::add_entry_returns(), $return);
+        $entryid = $return['entryid'];
+
+        // Updates the entry.
+        $concept .= ' Updated!';
+        $definition .= ' <p>Updated!</p>';
+        $return = update_entry::execute($entryid, $concept, $definition, FORMAT_HTML);
+        $return = external_api::clean_returnvalue(update_entry::execute_returns(), $return);
+
+        // Get entry from DB.
+        $entry = $DB->get_record('glossary_entries', ['id' => $entryid]);
+
+        $this->assertEquals($concept, $entry->concept);
+        $this->assertEquals($definition, $entry->definition);
+        $this->assertEquals($CFG->glossary_linkentries, $entry->usedynalink);
+        $this->assertEquals($CFG->glossary_casesensitive, $entry->casesensitive);
+        $this->assertEquals($CFG->glossary_fullmatch, $entry->fullmatch);
+        $this->assertEmpty($DB->get_records('glossary_alias', ['entryid' => $entryid]));
+        $this->assertEmpty($DB->get_records('glossary_entries_categories', ['entryid' => $entryid]));
+    }
+
+    /**
+     * test_update_entry_duplicated
+     */
+    public function test_update_entry_duplicated() {
+        global $CFG, $DB;
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id, 'allowduplicatedentries' => 1]);
+
+        // Create three entries.
+        $this->setAdminUser();
+        $concept = 'A concept';
+        $definition = '<p>A definition</p>';
+        mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML);
+
+        $concept = 'B concept';
+        $definition = '<p>B definition</p>';
+        mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML);
+
+        $concept = 'Another concept';
+        $definition = '<p>Another definition</p>';
+        $return = mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML);
+        $return = external_api::clean_returnvalue(mod_glossary_external::add_entry_returns(), $return);
+        $entryid = $return['entryid'];
+
+        // Updates the entry using an existing entry name when duplicateds are allowed.
+        $concept = 'A concept';
+        update_entry::execute($entryid, $concept, $definition, FORMAT_HTML);
+
+        // Updates the entry using an existing entry name when duplicateds are NOT allowed.
+        $DB->set_field('glossary', 'allowduplicatedentries', 0, ['id' => $glossary->id]);
+        $concept = 'B concept';
+        $this->expectExceptionMessage(get_string('errconceptalreadyexists', 'glossary'));
+        update_entry::execute($entryid, $concept, $definition, FORMAT_HTML);
+    }
+
+    /**
+     * test_update_entry_with_aliases
+     */
+    public function test_update_entry_with_aliases() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
+
+        $this->setAdminUser();
+        $concept = 'A concept';
+        $definition = 'A definition';
+        $paramaliases = 'abc, def, gez';
+        $options = [
+            [
+                'name' => 'aliases',
+                'value' => $paramaliases,
+            ]
+        ];
+        $return = mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML, $options);
+        $return = external_api::clean_returnvalue(mod_glossary_external::add_entry_returns(), $return);
+        $entryid = $return['entryid'];
+
+        // Updates the entry.
+        $newaliases = 'abz, xyz';
+        $options[0]['value'] = $newaliases;
+        $return = update_entry::execute($entryid, $concept, $definition, FORMAT_HTML, $options);
+        $return = external_api::clean_returnvalue(update_entry::execute_returns(), $return);
+
+        $aliases = $DB->get_records('glossary_alias', ['entryid' => $entryid]);
+        $this->assertCount(2, $aliases);
+        foreach ($aliases as $alias) {
+            $this->assertContains($alias->alias, $newaliases);
+        }
+    }
+
+    /**
+     * test_update_entry_in_categories
+     */
+    public function test_update_entry_in_categories() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
+        $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
+        $cat1 = $gg->create_category($glossary);
+        $cat2 = $gg->create_category($glossary);
+        $cat3 = $gg->create_category($glossary);
+
+        $this->setAdminUser();
+        $concept = 'A concept';
+        $definition = 'A definition';
+        $paramcategories = "$cat1->id, $cat2->id";
+        $options = [
+            [
+                'name' => 'categories',
+                'value' => $paramcategories,
+            ]
+        ];
+        $return = mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML, $options);
+        $return = external_api::clean_returnvalue(mod_glossary_external::add_entry_returns(), $return);
+        $entryid = $return['entryid'];
+
+        // Updates the entry.
+        $newcategories = "$cat1->id, $cat3->id";
+        $options[0]['value'] = $newcategories;
+        $return = update_entry::execute($entryid, $concept, $definition, FORMAT_HTML, $options);
+        $return = external_api::clean_returnvalue(update_entry::execute_returns(), $return);
+
+        $categories = $DB->get_records('glossary_entries_categories', ['entryid' => $entryid]);
+        $this->assertCount(2, $categories);
+        foreach ($categories as $category) {
+            $this->assertContains($category->categoryid, $newcategories);
+        }
+    }
+
+    /**
+     * test_update_entry_with_attachments
+     */
+    public function test_update_entry_with_attachments() {
+        global $DB, $USER;
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
+        $context = context_module::instance($glossary->cmid);
+
+        $this->setAdminUser();
+        $concept = 'A concept';
+        $definition = 'A definition';
+
+        // Draft files.
+        $draftidinlineattach = file_get_unused_draft_itemid();
+        $draftidattach = file_get_unused_draft_itemid();
+        $usercontext = context_user::instance($USER->id);
+        $filerecordinline = [
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $draftidinlineattach,
+            'filepath'  => '/',
+            'filename'  => 'shouldbeanimage.png',
+        ];
+        $fs = get_file_storage();
+
+        // Create a file in a draft area for regular attachments.
+        $filerecordattach = $filerecordinline;
+        $attachfilename = 'attachment.txt';
+        $filerecordattach['filename'] = $attachfilename;
+        $filerecordattach['itemid'] = $draftidattach;
+        $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
+        $fs->create_file_from_string($filerecordattach, 'simple text attachment');
+
+        $options = [
+            [
+                'name' => 'inlineattachmentsid',
+                'value' => $draftidinlineattach,
+            ],
+            [
+                'name' => 'attachmentsid',
+                'value' => $draftidattach,
+            ]
+        ];
+        $return = mod_glossary_external::add_entry($glossary->id, $concept, $definition, FORMAT_HTML, $options);
+        $return = external_api::clean_returnvalue(mod_glossary_external::add_entry_returns(), $return);
+        $entryid = $return['entryid'];
+        $entry = $DB->get_record('glossary_entries', ['id' => $entryid]);
+
+        list($definitionoptions, $attachmentoptions) = glossary_get_editor_and_attachment_options($course, $context, $entry);
+
+        $entry = file_prepare_standard_editor($entry, 'definition', $definitionoptions, $context, 'mod_glossary', 'entry',
+            $entry->id);
+        $entry = file_prepare_standard_filemanager($entry, 'attachment', $attachmentoptions, $context, 'mod_glossary', 'attachment',
+            $entry->id);
+
+        $inlineattachmentsid = $entry->definition_editor['itemid'];
+        $attachmentsid = $entry->attachment_filemanager;
+
+        // Change the file areas.
+
+        // Delete one inline editor file.
+        $selectedfile = (object)[
+            'filename' => $filerecordinline['filename'],
+            'filepath' => $filerecordinline['filepath'],
+        ];
+        $return = repository_delete_selected_files($usercontext, 'user', 'draft', $inlineattachmentsid, [$selectedfile]);
+
+        // Add more files.
+        $filerecordinline['filename'] = 'newvideo.mp4';
+        $filerecordinline['itemid'] = $inlineattachmentsid;
+
+        $filerecordattach['filename'] = 'newattach.txt';
+        $filerecordattach['itemid'] = $attachmentsid;
+
+        $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
+        $fs->create_file_from_string($filerecordattach, 'simple text attachment');
+
+        // Updates the entry.
+        $options[0]['value'] = $inlineattachmentsid;
+        $options[1]['value'] = $attachmentsid;
+        $return = update_entry::execute($entryid, $concept, $definition, FORMAT_HTML, $options);
+        $return = external_api::clean_returnvalue(update_entry::execute_returns(), $return);
+
+        $editorfiles = external_util::get_area_files($context->id, 'mod_glossary', 'entry', $entryid);
+        $attachmentfiles = external_util::get_area_files($context->id, 'mod_glossary', 'attachment', $entryid);
+
+        $this->assertCount(1, $editorfiles);
+        $this->assertCount(2, $attachmentfiles);
+
+        $this->assertEquals('newvideo.mp4', $editorfiles[0]['filename']);
+        $this->assertEquals('attachment.txt', $attachmentfiles[0]['filename']);
+        $this->assertEquals('newattach.txt', $attachmentfiles[1]['filename']);
+    }
+}
index 74353eb..24b0811 100644 (file)
@@ -670,4 +670,100 @@ class mod_glossary_lib_testcase extends advanced_testcase {
         // Tags.
         $this->assertEmpty(core_tag_tag::get_by_name(0, 'Cats'));
     }
+
+    public function test_mod_glossary_can_update_entry_users() {
+        $this->resetAfterTest();
+
+        // Create required data.
+        $course = $this->getDataGenerator()->create_course();
+        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
+        $anotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
+        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
+        $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id));
+
+        $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
+        $this->setUser($student);
+        $entry = $gg->create_content($glossary);
+        $context = context_module::instance($glossary->cmid);
+        $cm = get_coursemodule_from_instance('glossary', $glossary->id);
+
+        // Test student can update.
+        $this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
+
+        // Test teacher can update.
+        $this->setUser($teacher);
+        $this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
+
+        // Test admin can update.
+        $this->setAdminUser();
+        $this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
+
+        // Test a different student is not able to update.
+        $this->setUser($anotherstudent);
+        $this->assertFalse(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
+
+        // Test exception.
+        $this->expectExceptionMessage(get_string('errcannoteditothers', 'glossary'));
+        mod_glossary_can_update_entry($entry, $glossary, $context, $cm, false);
+    }
+
+    public function test_mod_glossary_can_update_entry_edit_period() {
+        global $CFG;
+        $this->resetAfterTest();
+
+        // Create required data.
+        $course = $this->getDataGenerator()->create_course();
+        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
+        $glossary = $this->getDataGenerator()->create_module('glossary', array('course' => $course->id, 'editalways' => 1));
+
+        $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
+        $this->setUser($student);
+        $entry = $gg->create_content($glossary);
+        $context = context_module::instance($glossary->cmid);
+        $cm = get_coursemodule_from_instance('glossary', $glossary->id);
+
+        // Test student can always update when edit always is set to 1.
+        $entry->timecreated = time() - 2 * $CFG->maxeditingtime;
+        $this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
+
+        // Test student cannot update old entries when edit always is set to 0.
+        $glossary->editalways = 0;
+        $this->assertFalse(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
+
+        // Test student can update recent entries when edit always is set to 0.
+        $entry->timecreated = time();
+        $this->assertTrue(mod_glossary_can_update_entry($entry, $glossary, $context, $cm));
+
+        // Check exception.
+        $entry->timecreated = time() - 2 * $CFG->maxeditingtime;
+        $this->expectExceptionMessage(get_string('erredittimeexpired', 'glossary'));
+        mod_glossary_can_update_entry($entry, $glossary, $context, $cm, false);
+    }
+
+    public function test_prepare_entry_for_edition() {
+        global $USER;
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $glossary = $this->getDataGenerator()->create_module('glossary', ['course' => $course->id]);
+        $gg = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
+
+        $this->setAdminUser();
+        $aliases = ['alias1', 'alias2'];
+        $entry = $gg->create_content(
+            $glossary,
+            ['approved' => 1, 'userid' => $USER->id],
+            $aliases
+        );
+
+        $cat1 = $gg->create_category($glossary, [], [$entry]);
+        $gg->create_category($glossary);
+
+        $entry = mod_glossary_prepare_entry_for_edition($entry);
+        $this->assertCount(1, $entry->categories);
+        $this->assertEquals($cat1->id, $entry->categories[0]);
+        $returnedaliases = array_values(explode("\n", trim($entry->aliases)));
+        sort($returnedaliases);
+        $this->assertEquals($aliases, $returnedaliases);
+    }
 }
index 6e2b843..666054c 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2021052501;       // The current module version (Date: YYYYMMDDXX)
+$plugin->version   = 2021052502;       // The current module version (Date: YYYYMMDDXX)
 $plugin->requires  = 2021052500;    // Requires this Moodle version
 $plugin->component = 'mod_glossary';   // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 0;