diff --git a/question/amd/build/edit_tags.min.js b/question/amd/build/edit_tags.min.js index cb3dc1915b0..f452866bfe7 100644 --- a/question/amd/build/edit_tags.min.js +++ b/question/amd/build/edit_tags.min.js @@ -1 +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("cantag"),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)}}}); \ No newline at end of file +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(a,b){a.getBody().attr("data-contextid",b)},p=function(a){return a.getBody().data("contextid")},q=function(a,b){a.getBody().attr("data-questionid",b)},r=function(a){return a.getBody().data("questionid")},s=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){t(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("cantag"),p=d.data("contextid");l.then(function(a){k(h),m(h);var c={id:e},d=b.loadFragment("question","tags_form",p,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(),q(a,e),o(a,p),a}).fail(f.exception),c.preventDefault()})},t=function(a,b){k(b),m(b);var c=l(a),d=r(a),e=p(a);return h.submitTagCreateUpdateForm(d,e,c).always(function(){n(b),j(b)}).fail(f.exception)};return{init:function(b){b=a(b),s(b)}}}); \ No newline at end of file diff --git a/question/amd/build/repository.min.js b/question/amd/build/repository.min.js index 35de00b2255..b86aedc4656 100644 --- a/question/amd/build/repository.min.js +++ b/question/amd/build/repository.min.js @@ -1 +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}}); \ No newline at end of file +define(["jquery","core/ajax"],function(a,b){var c=function(a,c,d){var e={methodname:"core_question_submit_tags_form",args:{questionid:a,contextid:c,formdata:d}};return b.call([e])[0]};return{submitTagCreateUpdateForm:c}}); \ No newline at end of file diff --git a/question/amd/src/edit_tags.js b/question/amd/src/edit_tags.js index 6bfabe03822..f4aabc1fa67 100644 --- a/question/amd/src/edit_tags.js +++ b/question/amd/src/edit_tags.js @@ -98,6 +98,46 @@ define([ loadingIconContainer.addClass('hidden'); }; + /** + * Set the context Id data attribute on the modal. + * + * @param {Promise} modal The modal promise. + * @param {int} contextId The context id. + */ + var setContextId = function(modal, contextId) { + modal.getBody().attr('data-contextid', contextId); + }; + + /** + * Get the context Id data attribute value from the modal body. + * + * @param {Promise} modal The modal promise. + * @return {int} The context id. + */ + var getContextId = function(modal) { + return modal.getBody().data('contextid'); + }; + + /** + * Set the question Id data attribute on the modal. + * + * @param {Promise} modal The modal promise. + * @param {int} questionId The question Id. + */ + var setQuestionId = function(modal, questionId) { + modal.getBody().attr('data-questionid', questionId); + }; + + /** + * Get the question Id data attribute value from the modal body. + * + * @param {Promise} modal The modal promise. + * @return {int} The question Id. + */ + var getQuestionId = function(modal) { + return modal.getBody().data('questionid'); + }; + /** * Register event listeners for the module. * @@ -186,6 +226,9 @@ define([ modal.getRoot().find(QuestionSelectors.actions.save).hide(); } + setQuestionId(modal, questionId); + setContextId(modal, contextId); + return modal; }).fail(Notification.exception); @@ -207,9 +250,11 @@ define([ startLoading(root); var formData = getFormData(modal); + var questionId = getQuestionId(modal); + var contextId = getContextId(modal); // Send the form data to the server for processing. - return Repository.submitTagCreateUpdateForm(formData) + return Repository.submitTagCreateUpdateForm(questionId, contextId, formData) .always(function() { // Regardless of success or error we should always stop // the loading icon and re-enable the buttons. diff --git a/question/amd/src/repository.js b/question/amd/src/repository.js index 42e72726bf4..6848577777d 100644 --- a/question/amd/src/repository.js +++ b/question/amd/src/repository.js @@ -31,10 +31,12 @@ define(['jquery', 'core/ajax'], function($, Ajax) { * @param {string} formdata The URL encoded values from the form * @return {promise} */ - var submitTagCreateUpdateForm = function(formdata) { + var submitTagCreateUpdateForm = function(questionId, contextId, formdata) { var request = { methodname: 'core_question_submit_tags_form', args: { + questionid: questionId, + contextid: contextId, formdata: formdata } }; diff --git a/question/classes/bank/tags_action_column.php b/question/classes/bank/tags_action_column.php index 7ff372bb048..79a7cbca5b7 100644 --- a/question/classes/bank/tags_action_column.php +++ b/question/classes/bank/tags_action_column.php @@ -56,10 +56,11 @@ class tags_action_column extends action_column_base { question_has_capability_on($question, 'view')) { $cantag = question_has_capability_on($question, 'tag'); - $category = $DB->get_record('question_categories', ['id' => $question->category], 'contextid'); - $url = $this->qbank->edit_question_url($question->id); + $qbank = $this->qbank; + $url = $qbank->edit_question_url($question->id); + $editingcontext = $qbank->get_most_specific_context(); - $this->print_tag_icon($question->id, $url, $cantag, $category->contextid); + $this->print_tag_icon($question->id, $url, $cantag, $editingcontext->id); } } diff --git a/question/classes/external.php b/question/classes/external.php index ecc48767739..f2011d45e6b 100644 --- a/question/classes/external.php +++ b/question/classes/external.php @@ -122,6 +122,8 @@ class core_question_external extends external_api { */ public static function submit_tags_form_parameters() { return new external_function_parameters([ + 'questionid' => new external_value(PARAM_INT, 'The question id'), + 'contextid' => new external_value(PARAM_INT, 'The editing context id'), 'formdata' => new external_value(PARAM_RAW, 'The data from the tag form'), ]); } @@ -129,51 +131,67 @@ class core_question_external extends external_api { /** * Handles the tags form submission. * + * @param int $questionid The question id. + * @param int $contextid The editing context id. * @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; + public static function submit_tags_form($questionid, $contextid, $formdata) { + global $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); + $params = self::validate_parameters(self::submit_tags_form_parameters(), [ + 'questionid' => $questionid, + 'contextid' => $contextid, + 'formdata' => $formdata + ]); - self::validate_context($context); + $editingcontext = \context::instance_by_id($contextid); + self::validate_context($editingcontext); parse_str($params['formdata'], $data); - if (!empty($data['id'])) { - $questionid = clean_param($data['id'], PARAM_INT); - $question = $DB->get_record('question', array('id' => $questionid)); + if (!$question = $DB->get_record_sql(' + SELECT q.*, qc.contextid + FROM {question} q + JOIN {question_categories} qc ON qc.id = q.category + WHERE q.id = ?', [$questionid])) { + print_error('questiondoesnotexist', 'question'); + } - require_once($CFG->libdir . '/questionlib.php'); - $cantag = question_has_capability_on($question, 'tag'); + require_once($CFG->libdir . '/questionlib.php'); + require_once($CFG->dirroot . '/question/type/tags_form.php'); - require_once($CFG->dirroot . '/question/type/tags_form.php'); - $mform = new \core_question\form\tags(null, null, 'post', '', null, $cantag, $data); + $cantag = question_has_capability_on($question, 'tag'); + $questioncontext = \context::instance_by_id($question->contextid); + $formoptions = [ + 'editingcontext' => $editingcontext, + 'questioncontext' => $questioncontext + ]; - 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 ($cantag) { - if (is_array($validateddata->tags)) { - $categorycontext = context::instance_by_id($validateddata->contextid); + $mform = new \core_question\form\tags(null, $formoptions, 'post', '', null, $cantag, $data); - core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id, - $categorycontext, $validateddata->tags); + if ($validateddata = $mform->get_data()) { + if ($cantag) { + if (isset($validateddata->tags)) { + // 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. + $tags = is_array($validateddata->tags) ? $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); + core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id, + $questioncontext, $tags); - $result['status'] = true; - } + $result['status'] = true; + } + + if (isset($validateddata->coursetags)) { + $coursetags = is_array($validateddata->coursetags) ? $validateddata->coursetags : []; + core_tag_tag::set_item_tags('core_question', 'question', $validateddata->id, + $editingcontext->get_course_context(false), $coursetags); + + $result['status'] = true; } } } diff --git a/question/lib.php b/question/lib.php index 842e050a95e..5a731c492c2 100644 --- a/question/lib.php +++ b/question/lib.php @@ -41,26 +41,42 @@ function core_question_output_fragment_tags_form($args) { require_once($CFG->dirroot . '/question/type/tags_form.php'); require_once($CFG->libdir . '/questionlib.php'); $id = clean_param($args['id'], PARAM_INT); + $editingcontext = $args['context']; $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); + if ($coursecontext = $editingcontext->get_course_context(false)) { + $course = $DB->get_record('course', ['id' => $coursecontext->instanceid]); + $filtercourses = [$course]; + } else { + $filtercourses = null; } + // Load the question tags and filter the course tags by the current + // course. + get_question_options($question, true, $filtercourses); + + $category = $question->categoryobject; + $questioncontext = \context::instance_by_id($category->contextid); + + $formoptions = [ + 'editingcontext' => $editingcontext, + 'questioncontext' => $questioncontext + ]; + $data = [ + 'id' => $question->id, + 'questioncategory' => $category->name, + 'questionname' => $question->name, + 'categoryid' => $category->id, + 'contextid' => $category->contextid, + 'context' => $questioncontext->get_context_name(), + 'tags' => isset($question->tags) ? $question->tags : [], + 'coursetags' => isset($question->coursetags) ? $question->coursetags : [], + ]; + $cantag = question_has_capability_on($question, 'tag'); - $mform = new \core_question\form\tags(null, null, 'post', '', null, $cantag, $toform); - $mform->set_data($toform); + $mform = new \core_question\form\tags(null, $formoptions, 'post', '', null, $cantag, $data); + $mform->set_data($data); return $mform->render(); } diff --git a/question/type/tags_form.php b/question/type/tags_form.php index a3af411f8dc..bcc2fc502a0 100644 --- a/question/type/tags_form.php +++ b/question/type/tags_form.php @@ -41,6 +41,8 @@ class tags extends \moodleform { */ public function definition() { $mform = $this->_form; + $customdata = $this->_customdata; + $mform->disable_form_change_checker(); $mform->addElement('hidden', 'id'); @@ -56,7 +58,29 @@ class tags extends \moodleform { $mform->addElement('static', 'questioncategory', get_string('categorycurrent', 'question')); $mform->addElement('static', 'context', ''); - $mform->addElement('tags', 'tags', get_string('tags'), - ['itemtype' => 'question', 'component' => 'core_question']); + if (\core_tag_tag::is_enabled('core_question', 'question')) { + $mform->addElement('tags', 'tags', get_string('tags'), + ['itemtype' => 'question', 'component' => 'core_question']); + + // Is the question category in a course context? + $qcontext = $customdata['questioncontext']; + $qcoursecontext = $qcontext->get_course_context(false); + $iscourseoractivityquestion = !empty($qcoursecontext); + // Is the current context we're editing in a course context? + $editingcontext = $customdata['editingcontext']; + $editingcoursecontext = $editingcontext->get_course_context(false); + $iseditingcontextcourseoractivity = !empty($editingcoursecontext); + + if ($iseditingcontextcourseoractivity && !$iscourseoractivityquestion) { + // If the question is being edited in a course or activity context + // and the question isn't a course or activity level question then + // allow course tags to be added to the course. + $coursetagheader = get_string('questionformtagheader', 'core_question', + $editingcoursecontext->get_context_name(true)); + $mform->addElement('tags', 'coursetags', $coursetagheader, + array('itemtype' => 'question', 'component' => 'core_question')); + + } + } } }