Merge branch 'MDL-61133-master' of git://github.com/lameze/moodle

This commit is contained in:
Andrew Nicols
2018-02-06 14:00:51 +08:00
15 changed files with 619 additions and 6 deletions
+1
View File
@@ -269,6 +269,7 @@ $string['questionsinuse'] = '(* Questions marked by an asterisk are already in u
$string['questionsmovedto'] = 'Questions still in use moved to "{$a}" in the parent course category.';
$string['questionsrescuedfrom'] = 'Questions saved from context {$a}.';
$string['questionsrescuedfrominfo'] = 'These questions (some of which may be hidden) were saved when context {$a} was deleted because they are still used by some quizzes or other activities.';
$string['questiontags'] = 'Question tags';
$string['questiontype'] = 'Question type';
$string['questionuse'] = 'Use question in this activity';
$string['questionvariant'] = 'Question variant';
@@ -372,6 +372,7 @@ class icon_system_fontawesome extends icon_system_font {
'core:t/switch_minus' => 'fa-minus',
'core:t/switch_plus' => 'fa-plus',
'core:t/switch_whole' => 'fa-square-o',
'core:t/tags' => 'fa-tags',
'core:t/unblock' => 'fa-commenting',
'core:t/unlocked' => 'fa-unlock-alt',
'core:t/unlock' => 'fa-lock',
+7
View File
@@ -1114,6 +1114,13 @@ $functions = array(
'capabilities' => 'moodle/question:flag',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_question_submit_tags_form' => array(
'classname' => 'core_question_external',
'methodname' => 'submit_tags_form',
'description' => 'Update the question tags.',
'type' => 'write',
'ajax' => true,
),
'core_rating_get_item_ratings' => array(
'classname' => 'core_rating_external',
'methodname' => 'get_item_ratings',
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/fragment","core/str","core/modal_events","core/modal_factory","core/notification","core/custom_interaction_events","core_question/repository","core_question/selectors"],function(a,b,c,d,e,f,g,h,i){var j=function(a){a.find(i.actions.save).prop("disabled",!1)},k=function(a){a.find(i.actions.save).prop("disabled",!0)},l=function(a){return a.getBody().find("form").serialize()},m=function(a){var b=a.find(i.containers.loadingIcon);b.removeClass("hidden")},n=function(a){var b=a.find(i.containers.loadingIcon);b.addClass("hidden")},o=function(h){var l=e.create({type:e.types.SAVE_CANCEL,large:!1},[h,i.actions.edittags]).then(function(a){return c.get_string("questiontags","question").then(function(b){return a.setTitle(b),b}).fail(f.exception),a.getRoot().on(d.save,function(b){var c=a.getBody().find("form");c.submit(),b.preventDefault()}),a.getRoot().on("submit","form",function(b){p(a,h).then(function(){a.hide()}).fail(f.exception),b.preventDefault(),b.stopPropagation()}),a});h.on(g.events.activate,i.actions.edittags,function(c){var d=a(c.currentTarget),e=d.data("questionid"),g=!!d.data("canedit"),o=d.data("contextid");l.then(function(a){k(h),m(h);var c={id:e},d=b.loadFragment("question","tags_form",o,c);return a.setBody(d),d.then(function(){j(h)}).always(function(){n(h)}).fail(f.exception),g?a.getRoot().find(i.actions.save).show():a.getRoot().find(i.actions.save).hide(),a}).fail(f.exception),c.preventDefault()})},p=function(a,b){k(b),m(b);var c=l(a);return h.submitTagCreateUpdateForm(c).always(function(){n(b),j(b)}).fail(f.exception)};return{init:function(b){b=a(b),o(b)}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/ajax"],function(a,b){var c=function(a){var c={methodname:"core_question_submit_tags_form",args:{formdata:a}};return b.call([c])[0]};return{submitTagCreateUpdateForm:c}});
+1
View File
@@ -0,0 +1 @@
define([],function(){return{actions:{save:'[data-action="save"]',edittags:'[data-action="edittags"]'},containers:{loadingIcon:'[data-region="overlay-icon-container"]'}}});
+229
View File
@@ -0,0 +1,229 @@
// 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/>.
/**
* A javascript module to handle question tags editing.
*
* @module core_question/edit_tags
* @copyright 2018 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/fragment',
'core/str',
'core/modal_events',
'core/modal_factory',
'core/notification',
'core/custom_interaction_events',
'core_question/repository',
'core_question/selectors',
],
function(
$,
Fragment,
Str,
ModalEvents,
ModalFactory,
Notification,
CustomEvents,
Repository,
QuestionSelectors
) {
/**
* Enable the save button in the footer.
*
* @param {object} root The container element.
* @method enableSaveButton
*/
var enableSaveButton = function(root) {
root.find(QuestionSelectors.actions.save).prop('disabled', false);
};
/**
* Disable the save button in the footer.
*
* @param {object} root The container element.
* @method disableSaveButton
*/
var disableSaveButton = function(root) {
root.find(QuestionSelectors.actions.save).prop('disabled', true);
};
/**
* Get the serialised form data.
*
* @method getFormData
* @param {object} modal The modal object.
* @return {string} serialised form data
*/
var getFormData = function(modal) {
return modal.getBody().find('form').serialize();
};
/**
* Set the element state to loading.
*
* @param {object} root The container element
* @method startLoading
*/
var startLoading = function(root) {
var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);
loadingIconContainer.removeClass('hidden');
};
/**
* Remove the loading state from the element.
*
* @param {object} root The container element
* @method stopLoading
*/
var stopLoading = function(root) {
var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);
loadingIconContainer.addClass('hidden');
};
/**
* Register event listeners for the module.
*
* @param {object} root The calendar root element
*/
var registerEventListeners = function(root) {
var modalPromise = ModalFactory.create(
{
type: ModalFactory.types.SAVE_CANCEL,
large: false
},
[root, QuestionSelectors.actions.edittags]
).then(function(modal) {
// All of this code only executes once, when the modal is
// first created. This allows us to add any code that should
// only be run once, such as adding event handlers to the modal.
Str.get_string('questiontags', 'question')
.then(function(string) {
modal.setTitle(string);
return string;
})
.fail(Notification.exception);
modal.getRoot().on(ModalEvents.save, function(e) {
var form = modal.getBody().find('form');
form.submit();
e.preventDefault();
});
modal.getRoot().on('submit', 'form', function(e) {
save(modal, root).then(function() {
modal.hide();
return;
}).fail(Notification.exception);
// Stop the form from actually submitting and prevent it's
// propagation because we have already handled the event.
e.preventDefault();
e.stopPropagation();
});
return modal;
});
// We need to add an event handler to the tags link because there are
// multiple links on the page and without adding a listener we don't know
// which one the user clicked on the show the modal.
root.on(CustomEvents.events.activate, QuestionSelectors.actions.edittags, function(e) {
var currentTarget = $(e.currentTarget);
var questionId = currentTarget.data('questionid'),
canEdit = !!currentTarget.data('canedit'),
contextId = currentTarget.data('contextid');
// This code gets called each time the user clicks the tag link
// so we can use it to reload the contents of the tag modal.
modalPromise.then(function(modal) {
// Display spinner and disable save button.
disableSaveButton(root);
startLoading(root);
var args = {
id: questionId
};
var tagsFragment = Fragment.loadFragment('question', 'tags_form', contextId, args);
modal.setBody(tagsFragment);
tagsFragment.then(function() {
enableSaveButton(root);
return;
})
.always(function() {
// Always hide the loading spinner when the request
// has completed.
stopLoading(root);
return;
})
.fail(Notification.exception);
// Show or hide the save button depending on whether the user
// has the capability to edit the tags.
if (canEdit) {
modal.getRoot().find(QuestionSelectors.actions.save).show();
} else {
modal.getRoot().find(QuestionSelectors.actions.save).hide();
}
return modal;
}).fail(Notification.exception);
e.preventDefault();
});
};
/**
* Send the form data to the server to save question tags.
*
* @method save
* @param {object} modal The modal object.
* @param {object} root The container element.
* @return {object} A promise
*/
var save = function(modal, root) {
// Display spinner and disable save button.
disableSaveButton(root);
startLoading(root);
var formData = getFormData(modal);
// Send the form data to the server for processing.
return Repository.submitTagCreateUpdateForm(formData)
.always(function() {
// Regardless of success or error we should always stop
// the loading icon and re-enable the buttons.
stopLoading(root);
enableSaveButton(root);
return;
})
.fail(Notification.exception);
};
return {
init: function(root) {
root = $(root);
registerEventListeners(root);
}
};
});
+48
View File
@@ -0,0 +1,48 @@
// 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/>.
/**
* A javascript module to handle question ajax actions.
*
* @module core_question/repository
* @class repository
* @package core_question
* @copyright 2017 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax'], function($, Ajax) {
/**
* Submit the form data for the question tags form.
*
* @method submitTagCreateUpdateForm
* @param {string} formdata The URL encoded values from the form
* @return {promise}
*/
var submitTagCreateUpdateForm = function(formdata) {
var request = {
methodname: 'core_question_submit_tags_form',
args: {
formdata: formdata
}
};
return Ajax.call([request])[0];
};
return {
submitTagCreateUpdateForm: submitTagCreateUpdateForm
};
});
+34
View File
@@ -0,0 +1,34 @@
// 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/>.
/**
* The purpose of this module is to centralize selectors related to question.
*
* @module core_question/question_selectors
* @package core_question
* @copyright 2018 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
return {
actions: {
save: '[data-action="save"]',
edittags: '[data-action="edittags"]',
},
containers: {
loadingIcon: '[data-region="overlay-icon-container"]',
},
};
});
@@ -0,0 +1,86 @@
<?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/>.
/**
* The question tags column subclass.
*
* @package core_question
* @copyright 2018 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\bank;
defined('MOODLE_INTERNAL') || die();
/**
* Action to add and remove tags to questions.
*
* @package core_question
* @copyright 2018 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tags_action_column extends action_column_base {
/**
* Return the name for this column.
*
* @return string
*/
public function get_name() {
return 'tagsaction';
}
/**
* Display tags column content.
*
* @param object $question The question database record.
* @param string $rowclasses
*/
protected function display_content($question, $rowclasses) {
global $DB;
if (\core_tag_tag::is_enabled('core_question', 'question') &&
question_has_capability_on($question, 'view')) {
$canedit = question_has_capability_on($question, 'edit');
$category = $DB->get_record('question_categories', ['id' => $question->category], 'contextid');
$url = $this->qbank->edit_question_url($question->id);
$this->print_tag_icon($question->id, $url, $canedit, $category->contextid);
}
}
/**
* Build and print the tags icon.
*
* @param int $id The question ID.
* @param string $url Editing question url.
* @param bool $canedit Whether the user can edit questions or not.
* @param int $contextid Question category context ID.
*/
protected function print_tag_icon($id, $url, $canedit, $contextid) {
global $OUTPUT;
$params = [
'data-action' => 'edittags',
'data-canedit' => $canedit,
'data-contextid' => $contextid,
'data-questionid' => $id
];
echo \html_writer::link($url, $OUTPUT->pix_icon('t/tags', get_string('managetags', 'tag')), $params);
}
}
+6 -5
View File
@@ -124,10 +124,9 @@ class view {
if (empty($CFG->questionbankcolumns)) {
$questionbankcolumns = array('checkbox_column', 'question_type_column',
'question_name_column', 'edit_action_column', 'copy_action_column',
'preview_action_column', 'delete_action_column',
'creator_name_column',
'modifier_name_column');
'question_name_column', 'tags_action_column', 'edit_action_column',
'copy_action_column', 'preview_action_column', 'delete_action_column',
'creator_name_column', 'modifier_name_column');
} else {
$questionbankcolumns = explode(',', $CFG->questionbankcolumns);
}
@@ -481,6 +480,8 @@ class view {
$this->baseurl, $cat, $this->cm,
null, $page, $perpage, $showhidden, $showquestiontext,
$this->contexts->having_cap('moodle/question:add'));
$PAGE->requires->js_call_amd('core_question/edit_tags', 'init', ['#questionscontainer']);
}
protected function print_choose_category_message($categoryandcontext) {
@@ -702,7 +703,7 @@ class view {
echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
echo \html_writer::input_hidden_params($this->baseurl);
echo '<div class="categoryquestionscontainer">';
echo '<div class="categoryquestionscontainer" id="questionscontainer">';
$this->start_table();
$rowcount = 0;
foreach ($questions as $question) {
+75
View File
@@ -114,4 +114,79 @@ class core_question_external extends external_api {
)
);
}
/**
* Returns description of method parameters.
*
* @return external_function_parameters.
*/
public static function submit_tags_form_parameters() {
return new external_function_parameters([
'formdata' => new external_value(PARAM_RAW, 'The data from the tag form'),
]);
}
/**
* Handles the tags form submission.
*
* @param string $formdata The question tag form data in a URI encoded param string
* @return array The created or modified question tag
* @throws moodle_exception
*/
public static function submit_tags_form($formdata) {
global $USER, $DB, $CFG;
$data = [];
$result = ['status' => false];
// Parameter validation.
$params = self::validate_parameters(self::submit_tags_form_parameters(), ['formdata' => $formdata]);
$context = \context_user::instance($USER->id);
self::validate_context($context);
parse_str($params['formdata'], $data);
if (!empty($data['id'])) {
$questionid = clean_param($data['id'], PARAM_INT);
$question = $DB->get_record('question', array('id' => $questionid));
require_once($CFG->libdir . '/questionlib.php');
$canedit = question_has_capability_on($question, 'edit');
require_once($CFG->dirroot . '/question/type/tags_form.php');
$mform = new \core_question\form\tags(null, null, 'post', '', null, $canedit, $data);
if ($validateddata = $mform->get_data()) {
// Due to a mform bug, if there's no tags set on the tag element, it submits the name as the value.
// The only way to discover is checking if the tag element is an array.
if ($canedit) {
if (is_array($validateddata->tags)) {
$categorycontext = context::instance_by_id($validateddata->contextid);
core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id,
$categorycontext, $validateddata->tags);
$result['status'] = true;
} else {
// If the tags element is not array, this means we don't have any tags to be set.
// This is the only way to assume the user removed all tags from the question.
core_tag_tag::remove_all_item_tags('core_question', 'question', $validateddata->id);
$result['status'] = true;
}
}
}
}
return $result;
}
/**
* Returns description of method result value.
*/
public static function submit_tags_form_returns() {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'status: true if success')
]);
}
}
+67
View File
@@ -0,0 +1,67 @@
<?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/>.
/**
* Question related functions.
*
* This file was created just because Fragment API expects callbacks to be defined on lib.php.
*
* Please, do not add new functions to this file.
*
* @package core_question
* @copyright 2018 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Question tags fragment callback.
*
* @param array $args Arguments to the form.
* @return null|string The rendered form.
*/
function core_question_output_fragment_tags_form($args) {
if (!empty($args['id'])) {
global $CFG, $DB;
require_once($CFG->dirroot . '/question/type/tags_form.php');
require_once($CFG->libdir . '/questionlib.php');
$id = clean_param($args['id'], PARAM_INT);
$question = $DB->get_record('question', ['id' => $id]);
$category = $DB->get_record('question_categories', array('id' => $question->category));
$context = \context::instance_by_id($category->contextid);
$toform = new stdClass();
$toform->id = $question->id;
$toform->questioncategory = $category->name;
$toform->questionname = $question->name;
$toform->categoryid = $category->id;
$toform->contextid = $category->contextid;
$toform->context = $context->get_context_name();
if (core_tag_tag::is_enabled('core_question', 'question')) {
$toform->tags = core_tag_tag::get_item_tags_array('core_question', 'question', $question->id);
}
$canedit = question_has_capability_on($question, 'edit');
$mform = new \core_question\form\tags(null, null, 'post', '', null, $canedit, $toform);
$mform->set_data($toform);
return $mform->render();
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mform to manage question tags.
*
* @package core_question
* @copyright 2018 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/lib/formslib.php');
/**
* The mform class for manage question tags.
*
* @copyright 2018 Simey Lameze <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tags extends \moodleform {
/**
* The form definition
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'categoryid');
$mform->setType('categoryid', PARAM_INT);
$mform->addElement('hidden', 'contextid');
$mform->setType('contextid', PARAM_INT);
$mform->addElement('static', 'questionname', get_string('questionname', 'question'));
$mform->addElement('static', 'questioncategory', get_string('categorycurrent', 'question'));
$mform->addElement('static', 'context', '');
$mform->addElement('tags', 'tags', get_string('tags'),
['itemtype' => 'question', 'component' => 'core_question']);
}
}
+1 -1
View File
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2018020100.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2018020100.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.