diff --git a/lang/en/question.php b/lang/en/question.php index 30a1fb45330..3b4ef630d4b 100644 --- a/lang/en/question.php +++ b/lang/en/question.php @@ -235,7 +235,7 @@ $string['movedquestionsandcategories'] = 'Moved questions and question categorie $string['movelinksonly'] = 'Just change where links point to, do not move or copy files.'; $string['moveq'] = 'Move question(s)'; $string['moveqtoanothercontext'] = 'Move question to another context.'; -$string['moveto'] = 'Move to >>'; +$string['moveto'] = 'Move to'; $string['movingcategory'] = 'Moving category'; $string['movingcategoryandfiles'] = 'Are you sure you want to move category {$a->name} and all child categories to context for "{$a->contextto}"?
We have detected {$a->urlcount} files linked from questions in {$a->fromareaname}, would you like to copy or move these to {$a->toareaname}?'; $string['movingcategorynofiles'] = 'Are you sure you want to move category "{$a->name}" and all child categories to context for "{$a->contextto}"?'; diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php index 96b5dbe84d5..dd74d02af8d 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -1950,6 +1950,7 @@ class core_plugin_manager { ), 'qbank' => [ + 'bulkmove', 'comment', 'deletequestion', 'editquestion', diff --git a/mod/quiz/classes/question/bank/custom_view.php b/mod/quiz/classes/question/bank/custom_view.php index 4f4f4036e3a..6e86dd37fca 100644 --- a/mod/quiz/classes/question/bank/custom_view.php +++ b/mod/quiz/classes/question/bank/custom_view.php @@ -142,7 +142,7 @@ class custom_view extends \core_question\local\bank\view { return $out; } - protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts): void { + protected function display_bottom_controls(\context $catcontext): void { $cmoptions = new \stdClass(); $cmoptions->hasattempts = !empty($this->quizhasattempts); diff --git a/mod/quiz/edit.php b/mod/quiz/edit.php index 4950902156e..49f71597ca0 100644 --- a/mod/quiz/edit.php +++ b/mod/quiz/edit.php @@ -171,7 +171,6 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) { // Get the question bank view. $questionbank = new mod_quiz\question\bank\custom_view($contexts, $thispageurl, $course, $cm, $quiz); $questionbank->set_quiz_has_attempts($quizhasattempts); -$questionbank->process_actions(); // End of process commands =====================================================. diff --git a/question/bank/bulkmove/classes/bulk_move_action.php b/question/bank/bulkmove/classes/bulk_move_action.php new file mode 100644 index 00000000000..60bbf1065d4 --- /dev/null +++ b/question/bank/bulkmove/classes/bulk_move_action.php @@ -0,0 +1,47 @@ +. + +namespace qbank_bulkmove; + +/** + * Class bulk_move_action is the base class for moving questions. + * + * @package qbank_bulkmove + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class bulk_move_action extends \core_question\local\bank\bulk_action_base { + + public function get_bulk_action_title(): string { + return get_string('movetobulkaction', 'qbank_bulkmove'); + } + + public function get_bulk_action_key(): string { + return 'move'; + } + + public function get_bulk_action_url(): \moodle_url { + return new \moodle_url('/question/bank/bulkmove/move.php'); + } + + public function get_bulk_action_capabilities(): ?array { + return [ + 'moodle/question:moveall', + 'moodle/question:add', + ]; + } +} diff --git a/question/bank/bulkmove/classes/helper.php b/question/bank/bulkmove/classes/helper.php new file mode 100644 index 00000000000..879ee906bcb --- /dev/null +++ b/question/bank/bulkmove/classes/helper.php @@ -0,0 +1,90 @@ +. + +namespace qbank_bulkmove; + +/** + * Bulk move helper. + * + * @package qbank_bulkmove + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class helper { + + /** + * Bulk move questions to a category. + * + * @param string $movequestionselected comma separated string of questions to be moved. + * @param \stdClass $tocategory the category where the questions will be moved to. + */ + public static function bulk_move_questions(string $movequestionselected, \stdClass $tocategory): void { + global $DB; + if ($questionids = explode(',', $movequestionselected)) { + list($usql, $params) = $DB->get_in_or_equal($questionids); + $sql = "SELECT q.*, c.contextid + FROM {question} q + JOIN {question_categories} c ON c.id = q.category + WHERE q.id + {$usql}"; + $questions = $DB->get_records_sql($sql, $params); + foreach ($questions as $question) { + question_require_capability_on($question, 'move'); + } + question_move_questions_to_category($questionids, $tocategory->id); + } + } + + /** + * Get the display data for the move form. + * + * @param array $addcontexts the array of contexts to be considered in order to render the category select menu. + * @param \moodle_url $moveurl the url where the move script will point to. + * @param \moodle_url $returnurl return url in case the form is cancelled. + * @return array the data to be rendered in the mustache where it contains the dropdown, move url and return url. + */ + public static function get_displaydata(array $addcontexts, \moodle_url $moveurl, \moodle_url $returnurl): array { + $displaydata = []; + $displaydata ['categorydropdown'] = \qbank_managecategories\helper::question_category_select_menu($addcontexts, + false, 0, '', -1, true); + $displaydata ['moveurl'] = $moveurl; + $displaydata['returnurl'] = $returnurl; + return $displaydata; + } + + /** + * Process the question came from the form post. + * + * @param array $rawquestions raw questions came as a part of post. + * @return array question ids got from the post are processed and structured in an array. + */ + public static function process_question_ids(array $rawquestions): array { + $questionids = []; + $questionlist = ''; + foreach ($rawquestions as $key => $notused) { + // Parse input for question ids. + if (preg_match('!^q([0-9]+)$!', $key, $matches)) { + $key = $matches[1]; + $questionids[] = $key; + } + } + if (!empty($questionids)) { + $questionlist = implode(',', $questionids); + } + return [$questionids, $questionlist]; + } +} diff --git a/question/bank/bulkmove/classes/output/renderer.php b/question/bank/bulkmove/classes/output/renderer.php new file mode 100644 index 00000000000..b57a214da28 --- /dev/null +++ b/question/bank/bulkmove/classes/output/renderer.php @@ -0,0 +1,39 @@ +. + +namespace qbank_bulkmove\output; + +/** + * Class renderer. + * + * @package qbank_bulkmove + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class renderer extends \plugin_renderer_base { + + /** + * Render bulk move. + * + * @param array $displaydata + * @return string + */ + public function render_bulk_move_form($displaydata) { + return $this->render_from_template('qbank_bulkmove/bulk_move', $displaydata); + } + +} diff --git a/question/bank/bulkmove/classes/plugin_feature.php b/question/bank/bulkmove/classes/plugin_feature.php new file mode 100644 index 00000000000..4c9eda185f3 --- /dev/null +++ b/question/bank/bulkmove/classes/plugin_feature.php @@ -0,0 +1,34 @@ +. + +namespace qbank_bulkmove; + +use core_question\local\bank\bulk_action_base; +use core_question\local\bank\plugin_features_base; + +/** + * Class plugin_feature is the entrypoint for the features. + * + * @package qbank_bulkmove + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class plugin_feature extends plugin_features_base { + public function get_bulk_actions(): ?bulk_action_base { + return new bulk_move_action(); + } +} diff --git a/question/bank/bulkmove/classes/privacy/provider.php b/question/bank/bulkmove/classes/privacy/provider.php new file mode 100644 index 00000000000..e17cec73b85 --- /dev/null +++ b/question/bank/bulkmove/classes/privacy/provider.php @@ -0,0 +1,32 @@ +. + +namespace qbank_bulkmove\privacy; + +/** + * Privacy Subsystem for qbank_deletequestion implementing null_provider. + * + * @package qbank_bulkmove + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements \core_privacy\local\metadata\null_provider { + + public static function get_reason(): string { + return 'privacy:metadata'; + } +} diff --git a/question/bank/bulkmove/lang/en/qbank_bulkmove.php b/question/bank/bulkmove/lang/en/qbank_bulkmove.php new file mode 100644 index 00000000000..a99ad3271d6 --- /dev/null +++ b/question/bank/bulkmove/lang/en/qbank_bulkmove.php @@ -0,0 +1,31 @@ +. + +/** + * Strings for component qbank_bulkmove, language 'en' + * + * @package qbank_bulkmove + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['bulkmoveheader'] = 'Move the selected questions'; +$string['close'] = 'Close'; +$string['movequestions'] = 'Move questions'; +$string['movetobulkaction'] = 'Move to...'; +$string['pluginname'] = 'Bulk move questions'; +$string['privacy:metadata'] = 'The bulk move questions plugin does not store any personal data.'; diff --git a/question/bank/bulkmove/move.php b/question/bank/bulkmove/move.php new file mode 100644 index 00000000000..26f97558144 --- /dev/null +++ b/question/bank/bulkmove/move.php @@ -0,0 +1,103 @@ +. + +/** + * Move questions page. + * + * @package qbank_bulkmove + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__ . '/../../../config.php'); +require_once(__DIR__ . '/../../editlib.php'); +global $DB, $OUTPUT, $PAGE, $COURSE; + +$moveselected = optional_param('move', false, PARAM_BOOL); +$returnurl = optional_param('returnurl', 0, PARAM_LOCALURL); +$cmid = optional_param('cmid', 0, PARAM_INT); +$courseid = optional_param('courseid', 0, PARAM_INT); +$category = optional_param('category', null, PARAM_SEQUENCE); +$confirm = optional_param('confirm', '', PARAM_ALPHANUM); +$movequestionselected = optional_param('movequestionsselected', null, PARAM_RAW); + +if ($returnurl) { + $returnurl = new moodle_url($returnurl); +} + +\core_question\local\bank\helper::require_plugin_enabled('qbank_bulkmove'); + +if ($cmid) { + list($module, $cm) = get_module_from_cmid($cmid); + require_login($cm->course, false, $cm); + $thiscontext = context_module::instance($cmid); +} else if ($courseid) { + require_login($courseid, false); + $thiscontext = context_course::instance($courseid); +} else { + throw new moodle_exception('missingcourseorcmid', 'question'); +} + +$contexts = new question_edit_contexts($thiscontext); +$url = new moodle_url('/question/bank/bulkmove/move.php'); + +$PAGE->set_url($url); +$streditingquestions = get_string('movequestions', 'qbank_bulkmove'); +$PAGE->set_title($streditingquestions); +$PAGE->set_heading($COURSE->fullname); + +if ($category) { + list($tocategoryid, $contextid) = explode(',', $category); + if (! $tocategory = $DB->get_record('question_categories', + ['id' => $tocategoryid, 'contextid' => $contextid])) { + throw new \moodle_exception('cannotfindcate', 'question'); + } +} + +if ($movequestionselected && $confirm && confirm_sesskey()) { + if ($confirm == md5($movequestionselected)) { + \qbank_bulkmove\helper::bulk_move_questions($movequestionselected, $tocategory); + } + redirect(new moodle_url($returnurl, ['category' => "{$tocategoryid},{$contextid}"])); +} + +echo $OUTPUT->header(); + +if ($moveselected) { + $rawquestions = $_REQUEST; + list($questionids, $questionlist) = \qbank_bulkmove\helper::process_question_ids($rawquestions); + // No questions were selected. + if (!$questionids) { + redirect($returnurl); + } + // Create the urls. + $moveparam = [ + 'movequestionsselected' => $questionlist, + 'confirm' => md5($questionlist), + 'sesskey' => sesskey(), + 'returnurl' => $returnurl, + 'cmid' => $cmid, + 'courseid' => $courseid, + ]; + $moveurl = new \moodle_url($url, $moveparam); + + $addcontexts = $contexts->having_cap('moodle/question:add'); + $displaydata = \qbank_bulkmove\helper::get_displaydata($addcontexts, $moveurl, $returnurl); + echo $PAGE->get_renderer('qbank_bulkmove')->render_bulk_move_form($displaydata); +} + +echo $OUTPUT->footer(); diff --git a/question/bank/bulkmove/templates/bulk_move.mustache b/question/bank/bulkmove/templates/bulk_move.mustache new file mode 100644 index 00000000000..0c44b41d6ac --- /dev/null +++ b/question/bank/bulkmove/templates/bulk_move.mustache @@ -0,0 +1,43 @@ +{{! + 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 . +}} +{{! + @template qbank_bulkmove/bulk_move + + The move form to move selested questions. + + Context variables required for this template: + * categorydropdown - dropdown html from the managecategories plugin for the list of categories + * moveurl - the url to post the selected category + * returnurl - the base page to return to + + Example context (json): + { + "categorydropdown": "", + "moveurl": "/question/bank/bulkmove/move.php?courseid=2", + "returnurl": "/question/edit.php?courseid=2" + } +}} + +
+

{{#str}} bulkmoveheader, qbank_bulkmove {{/str}}

+
+
+ {{{categorydropdown}}} + + {{#str}} close, qbank_bulkmove {{/str}} +
diff --git a/question/bank/bulkmove/tests/behat/bulk_move.feature b/question/bank/bulkmove/tests/behat/bulk_move.feature new file mode 100644 index 00000000000..c901035dda9 --- /dev/null +++ b/question/bank/bulkmove/tests/behat/bulk_move.feature @@ -0,0 +1,36 @@ +@qbank @qbank_bulkmove +Feature: Use the qbank plugin manager page for bulkmove + In order to check the plugin behaviour with enable and disable + + Background: + Given the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "activities" exist: + | activity | name | course | idnumber | + | quiz | Test quiz | C1 | quiz1 | + And the following "question categories" exist: + | contextlevel | reference | name | + | Course | C1 | Test questions | + And the following "questions" exist: + | questioncategory | qtype | name | questiontext | + | Test questions | truefalse | First question | Answer the first question | + + @javascript + Scenario: Enable/disable bulk move questions bulk action from the base view + Given I log in as "admin" + When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration + And I should see "Bulk move questions" + And I click on "Disable" "link" in the "Bulk move questions" "table_row" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I click on "First question" "checkbox" + And I click on "With selected" "button" + Then I should not see question bulk action "move" + And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration + And I click on "Enable" "link" in the "Bulk move questions" "table_row" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I click on "First question" "checkbox" + And I click on "With selected" "button" + And I should see question bulk action "move" diff --git a/question/bank/bulkmove/tests/helper_test.php b/question/bank/bulkmove/tests/helper_test.php new file mode 100644 index 00000000000..22591569c41 --- /dev/null +++ b/question/bank/bulkmove/tests/helper_test.php @@ -0,0 +1,214 @@ +. + +namespace qbank_bulkmove; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/question/editlib.php'); + +/** + * Bulk move helper tests. + * + * @package qbank_bulkmove + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \qbank_bulkmove\helper + */ +class helper_test extends \advanced_testcase { + + /** + * @var false|object|\stdClass|null $cat + */ + protected $cat; + + /** + * @var \stdClass $questiondata1 + */ + protected $questiondata1; + + /** + * @var \stdClass $questiondata2 + */ + protected $questiondata2; + + /** + * @var bool|\context|\context_course $context + */ + protected $context; + + /** + * @var \question_edit_contexts $contexts + */ + protected $contexts; + + /** + * @var \stdClass $course + */ + protected $course; + + /** + * @var array $rawdata + */ + protected $rawdata; + + /** + * @var object $secondcategory + */ + protected $secondcategory; + + /** + * Setup the test. + */ + protected function helper_setup(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + /** @var \core_question_generator $questiongenerator */ + $questiongenerator = $generator->get_plugin_generator('core_question'); + + // Create a course. + $this->course = $generator->create_course(); + $this->context = \context_course::instance($this->course->id); + + // Create a question in the default category. + $this->contexts = new \question_edit_contexts($this->context); + $this->cat = question_make_default_categories($this->contexts->all()); + $this->questiondata1 = $questiongenerator->create_question('numerical', null, + ['name' => 'Example question', 'category' => $this->cat->id]); + + // Create a second category to move questions. + $this->secondcategory = $questiongenerator->create_question_category(['contextid' => $this->context->id, + 'parent' => $this->cat->id]); + + // Ensure the question is not in the cache. + $cache = \cache::make('core', 'questiondata'); + $cache->delete($this->questiondata1->id); + + $this->questiondata2 = $questiongenerator->create_question('numerical', null, + ['name' => 'Example question second', 'category' => $this->cat->id]); + + // Ensure the question is not in the cache. + $cache = \cache::make('core', 'questiondata'); + $cache->delete($this->questiondata2->id); + + // Posted raw data. + $this->rawdata = [ + 'courseid' => $this->course->id, + 'cat' => "{$this->cat->id},{$this->context->id}", + 'qpage' => '0', + "q{$this->questiondata1->id}" => '1', + "q{$this->questiondata2->id}" => '1', + 'move' => 'Move to' + ]; + } + + /** + * Test bulk move of questions. + * + * @covers ::bulk_move_questions + */ + public function test_bulk_move_questions() { + $this->helper_setup(); + // Verify that the questions are available in the current view. + $view = new \core_question\local\bank\view($this->contexts, new \moodle_url('/'), $this->course); + ob_start(); + $pagevars = [ + 'qpage' => 0, + 'qperpage' => 20, + 'cat' => $this->cat->id . ',' . $this->context->id, + 'recurse' => false, + 'showhidden' => false, + 'qbshowtext' => false + ]; + $view->display($pagevars, 'editq'); + $html = ob_get_clean(); + $this->assertStringContainsString('Example question', $html); + $this->assertStringContainsString('Example question second', $html); + + // Get the processed question ids. + $questionlist = $this->process_question_ids_test(); + + helper::bulk_move_questions($questionlist, $this->secondcategory); + + // Verify the questions are not in the current category. + $view = new \core_question\local\bank\view($this->contexts, new \moodle_url('/'), $this->course); + ob_start(); + $pagevars = [ + 'qpage' => 0, + 'qperpage' => 20, + 'cat' => $this->cat->id . ',' . $this->context->id, + 'recurse' => false, + 'showhidden' => false, + 'qbshowtext' => false + ]; + $view->display($pagevars, 'editq'); + $html = ob_get_clean(); + $this->assertStringNotContainsString('Example question', $html); + $this->assertStringNotContainsString('Example question second', $html); + + // Verify the questions are in the new category. + $view = new \core_question\local\bank\view($this->contexts, new \moodle_url('/'), $this->course); + ob_start(); + $pagevars = [ + 'qpage' => 0, + 'qperpage' => 20, + 'cat' => $this->secondcategory->id . ',' . $this->context->id, + 'category' => $this->secondcategory->id . ',' . $this->context->id, + 'recurse' => false, + 'showhidden' => false, + 'qbshowtext' => false + ]; + $view->display($pagevars, 'editq'); + $html = ob_get_clean(); + $this->assertStringContainsString('Example question', $html); + $this->assertStringContainsString('Example question second', $html); + } + + /** + * Test the question processing and return the question list. + * + * @return mixed + * @covers ::process_question_ids + */ + protected function process_question_ids_test() { + // Test the raw data processing. + list($questionids, $questionlist) = helper::process_question_ids($this->rawdata); + $this->assertEquals([$this->questiondata1->id, $this->questiondata2->id], $questionids); + $this->assertEquals("{$this->questiondata1->id},{$this->questiondata2->id}", $questionlist); + return $questionlist; + } + + /** + * Test the question displaydata. + * + * @covers ::get_displaydata + */ + public function test_get_displaydata() { + $this->helper_setup(); + $coursecontext = \context_course::instance($this->course->id); + $contexts = new \question_edit_contexts($coursecontext); + $addcontexts = $contexts->having_cap('moodle/question:add'); + $url = new \moodle_url('/question/bank/bulkmove/move.php'); + $displaydata = \qbank_bulkmove\helper::get_displaydata($addcontexts, $url, $url); + $this->assertStringContainsString('Test question category 1', $displaydata['categorydropdown']); + $this->assertStringContainsString('Default for Category 1', $displaydata['categorydropdown']); + $this->assertEquals($url, $displaydata ['moveurl']); + $this->assertEquals($url, $displaydata ['returnurl']); + } +} diff --git a/question/bank/bulkmove/version.php b/question/bank/bulkmove/version.php new file mode 100644 index 00000000000..604239a5403 --- /dev/null +++ b/question/bank/bulkmove/version.php @@ -0,0 +1,31 @@ +. + +/** + * Version information for qbank_bulkmove. + * + * @package qbank_bulkmove + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->component = 'qbank_bulkmove'; +$plugin->version = 2021092600; +$plugin->requires = 2021052500; +$plugin->maturity = MATURITY_STABLE; diff --git a/question/bank/deletequestion/classes/bulk_delete_action.php b/question/bank/deletequestion/classes/bulk_delete_action.php new file mode 100644 index 00000000000..f2fc6931064 --- /dev/null +++ b/question/bank/deletequestion/classes/bulk_delete_action.php @@ -0,0 +1,46 @@ +. + +namespace qbank_deletequestion; + +/** + * Class bulk_delete_action is the base class for delete bulk actions ui. + * + * @package qbank_deletequestion + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class bulk_delete_action extends \core_question\local\bank\bulk_action_base { + + public function get_bulk_action_title(): string { + return get_string('delete'); + } + + public function get_bulk_action_key(): string { + return 'deleteselected'; + } + + public function get_bulk_action_url(): \moodle_url { + return new \moodle_url('/question/bank/deletequestion/delete.php'); + } + + public function get_bulk_action_capabilities(): ?array { + return [ + 'moodle/question:editall', + ]; + } +} diff --git a/question/bank/deletequestion/classes/plugin_feature.php b/question/bank/deletequestion/classes/plugin_feature.php index a7d4039674b..c11fae01d21 100644 --- a/question/bank/deletequestion/classes/plugin_feature.php +++ b/question/bank/deletequestion/classes/plugin_feature.php @@ -25,6 +25,7 @@ namespace qbank_deletequestion; +use core_question\local\bank\bulk_action_base; use core_question\local\bank\plugin_features_base; /** @@ -41,4 +42,8 @@ class plugin_feature extends plugin_features_base { new delete_action_column($qbank), ]; } + + public function get_bulk_actions(): ?bulk_action_base { + return new bulk_delete_action(); + } } diff --git a/question/bank/deletequestion/tests/behat/delete_question_column.feature b/question/bank/deletequestion/tests/behat/delete_question_column.feature new file mode 100644 index 00000000000..67d4f429f16 --- /dev/null +++ b/question/bank/deletequestion/tests/behat/delete_question_column.feature @@ -0,0 +1,63 @@ +@qbank @qbank_deletequestion +Feature: Use the qbank plugin manager page for deletequestion + In order to check the plugin behaviour with enable and disable + + Background: + Given the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "activities" exist: + | activity | name | course | idnumber | + | quiz | Test quiz | C1 | quiz1 | + And the following "question categories" exist: + | contextlevel | reference | name | + | Course | C1 | Test questions | + And the following "questions" exist: + | questioncategory | qtype | name | questiontext | + | Test questions | truefalse | First question | Answer the first question | + | Test questions | truefalse | First question second | Answer the first question | + + Scenario: Enable/disable delete question column from the base view + Given I log in as "admin" + When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration + And I should see "Delete question" + And I click on "Disable" "link" in the "Delete question" "table_row" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I click on "#action-menu-toggle-2" "css_element" in the "First question" "table_row" + Then I should not see "Delete" in the "region-main" "region" + And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration + And I click on "Enable" "link" in the "Delete question" "table_row" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I click on "#action-menu-toggle-2" "css_element" in the "First question" "table_row" + And I should see "Delete" in the "region-main" "region" + + @javascript + Scenario: Enable/disable delete questions bulk action from the base view + Given I log in as "admin" + When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration + And I should see "Delete question" + And I click on "Disable" "link" in the "Delete question" "table_row" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I click on "With selected" "button" + Then I should not see question bulk action "deleteselected" + And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration + And I click on "Enable" "link" in the "Delete question" "table_row" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I click on "With selected" "button" + And I should see question bulk action "deleteselected" + + @javascript + Scenario: I should not see the deleted questions in the base view + Given I log in as "admin" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I click on "Select all" "checkbox" + And I click on "With selected" "button" + And I click on question bulk action "deleteselected" + And I click on "Delete" "button" in the "Confirm" "dialogue" + Then I should not see "First question" + And I should not see "First question second" diff --git a/question/bank/exportquestions/classes/plugin_feature.php b/question/bank/exportquestions/classes/plugin_feature.php index e2f245dc23d..bde2f6dfd0e 100644 --- a/question/bank/exportquestions/classes/plugin_feature.php +++ b/question/bank/exportquestions/classes/plugin_feature.php @@ -27,6 +27,8 @@ namespace qbank_exportquestions; +use core_question\local\bank\navigation_node_base; + /** * Class plugin_feature. * @@ -37,7 +39,7 @@ namespace qbank_exportquestions; */ class plugin_feature extends \core_question\local\bank\plugin_features_base { - public function get_navigation_node(): ?object { + public function get_navigation_node(): ?navigation_node_base { return new navigation(); } } diff --git a/question/bank/importquestions/classes/plugin_feature.php b/question/bank/importquestions/classes/plugin_feature.php index fc876200366..f16f321047b 100644 --- a/question/bank/importquestions/classes/plugin_feature.php +++ b/question/bank/importquestions/classes/plugin_feature.php @@ -25,6 +25,8 @@ namespace qbank_importquestions; +use core_question\local\bank\navigation_node_base; + /** * Class plugin_feature. * @@ -35,7 +37,7 @@ namespace qbank_importquestions; */ class plugin_feature extends \core_question\local\bank\plugin_features_base { - public function get_navigation_node(): ?object { + public function get_navigation_node(): ?navigation_node_base { return new navigation(); } } diff --git a/question/bank/managecategories/classes/helper.php b/question/bank/managecategories/classes/helper.php index f84abf45ed8..cb4f1080ce0 100644 --- a/question/bank/managecategories/classes/helper.php +++ b/question/bank/managecategories/classes/helper.php @@ -206,32 +206,34 @@ class helper { * @param string $selected optionally, the id of a category to be selected by * default in the dropdown. * @param int $nochildrenof + * @param bool $return to return the string of the select menu or echo that from the method * @throws \coding_exception|\dml_exception */ public static function question_category_select_menu(array $contexts, bool $top = false, int $currentcat = 0, - string $selected = "", int $nochildrenof = -1): void { + string $selected = "", int $nochildrenof = -1, bool $return = false) { $categoriesarray = self::question_category_options($contexts, $top, $currentcat, false, $nochildrenof, false); - if ($selected) { - $choose = ''; - } else { - $choose = 'choosedots'; - } + $choose = ''; $options = []; foreach ($categoriesarray as $group => $opts) { $options[] = [$group => $opts]; } - echo html_writer::label(get_string('questioncategory', 'core_question'), - 'id_movetocategory', false, ['class' => 'accesshide']); + $outputhtml = html_writer::label(get_string('questioncategory', 'core_question'), + 'id_movetocategory', false, ['class' => 'accesshide']); $attrs = [ 'id' => 'id_movetocategory', 'class' => 'custom-select', 'data-action' => 'toggle', 'data-togglegroup' => 'qbank', 'data-toggle' => 'action', - 'disabled' => true, + 'disabled' => false, ]; - echo html_writer::select($options, 'category', $selected, $choose, $attrs); + $outputhtml .= html_writer::select($options, 'category', $selected, $choose, $attrs); + if ($return) { + return $outputhtml; + } else { + echo $outputhtml; + } } /** diff --git a/question/bank/managecategories/classes/plugin_feature.php b/question/bank/managecategories/classes/plugin_feature.php index 312ba63195e..81fc258e357 100644 --- a/question/bank/managecategories/classes/plugin_feature.php +++ b/question/bank/managecategories/classes/plugin_feature.php @@ -16,6 +16,8 @@ namespace qbank_managecategories; +use core_question\local\bank\navigation_node_base; + /** * Class plugin_feature. * @@ -29,7 +31,7 @@ namespace qbank_managecategories; */ class plugin_feature extends \core_question\local\bank\plugin_features_base { - public function get_navigation_node(): ?object { + public function get_navigation_node(): ?navigation_node_base { return new navigation(); } } diff --git a/question/bank/managecategories/tests/helper_test.php b/question/bank/managecategories/tests/helper_test.php index a875edbc09c..b033c211241 100644 --- a/question/bank/managecategories/tests/helper_test.php +++ b/question/bank/managecategories/tests/helper_test.php @@ -192,7 +192,6 @@ class helper_test extends \advanced_testcase { // Test the select menu of question categories output. $this->assertStringContainsString('Question category', $output); - $this->assertStringContainsString('', $output); $this->assertStringContainsString('Test this question category', $output); } diff --git a/question/classes/local/bank/bulk_action_base.php b/question/classes/local/bank/bulk_action_base.php new file mode 100644 index 00000000000..775c5a3cd5a --- /dev/null +++ b/question/classes/local/bank/bulk_action_base.php @@ -0,0 +1,71 @@ +. + +namespace core_question\local\bank; + +/** + * Class bulk_action_base is the base class for bulk actions ui. + * + * Every plugin wants to implement a bulk action, should extend this class, add appropriate values to the methods + * and finally pass this object via plugin_feature class. + * + * @package core_question + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class bulk_action_base { + + /** + * Title of the bulk action. + * Every bulk action will have a string to show in the list. + * + * @return string + */ + abstract public function get_bulk_action_title(): string; + + /** + * A unique key for the bulk action, this will be used in the api to identify the action data. + * Every bulk must have a unique key to perform the action as a part of the form post in the base view. + * When questions are selected, it will post according to the key its selected from the dropdown. + * + * @return string + */ + abstract public function get_bulk_action_key(): string; + + /** + * URL of the bulk action redirect page. + * Bulk action can be performed by redirecting to a page and doing the appropriate selection + * and finally doing the action. The url will be url of the page where users will be redirected to + * select what to do with the selected questions. + * + * @return \moodle_url + */ + abstract public function get_bulk_action_url(): \moodle_url; + + /** + * Get the capabilities for the bulk action. + * The bulk actions might have some capabilities to action them as a user. + * This method helps to get those caps which will be used by the base view before actioning the bulk action. + * For ex: ['moodle/question:moveall', 'moodle/question:add'] + * At least one of the cap need to be true for the user to use this action. + * + * @return array|null + */ + public function get_bulk_action_capabilities(): ?array { + return null; + } +} diff --git a/question/classes/local/bank/plugin_features_base.php b/question/classes/local/bank/plugin_features_base.php index 6c8bccde55f..de06126dd74 100644 --- a/question/classes/local/bank/plugin_features_base.php +++ b/question/classes/local/bank/plugin_features_base.php @@ -38,7 +38,7 @@ namespace core_question\local\bank; class plugin_features_base { /** - * This method will return the array of objects to be rendered as a prt of question bank columns/actions. + * This method will return the array of objects to be rendered as a part of question bank columns/actions. * * @param view $qbank * @return array @@ -50,9 +50,18 @@ class plugin_features_base { /** * This method will return the object for the navigation node. * - * @return null|object + * @return null|navigation_node_base */ - public function get_navigation_node(): ?object { + public function get_navigation_node(): ?navigation_node_base { + return null; + } + + /** + * This method will return the array objects for the bulk actions ui. + * + * @return null|bulk_action_base + */ + public function get_bulk_actions(): ?bulk_action_base { return null; } diff --git a/question/classes/local/bank/view.php b/question/classes/local/bank/view.php index c89c21532cb..fb1405e80de 100644 --- a/question/classes/local/bank/view.php +++ b/question/classes/local/bank/view.php @@ -149,6 +149,11 @@ class view { */ public $customfilterobjects = null; + /** + * @var array $bulkactions to identify the bulk actions for the api. + */ + public $bulkactions = []; + /** * Constructor for view. * @@ -178,6 +183,32 @@ class view { $this->init_columns($this->wanted_columns(), $this->heading_column()); $this->init_sort(); $this->init_search_conditions(); + $this->init_bulk_actions(); + } + + /** + * Initialize bulk actions. + */ + protected function init_bulk_actions(): void { + $plugins = \core_component::get_plugin_list_with_class('qbank', 'plugin_feature', 'plugin_feature.php'); + foreach ($plugins as $componentname => $plugin) { + $pluginentrypoint = new $plugin(); + $pluginentrypointobject = $pluginentrypoint->get_bulk_actions(); + // Don't need the plugins without bulk actions. + if ($pluginentrypointobject === null) { + unset($plugins[$componentname]); + continue; + } + if (!\core\plugininfo\qbank::is_plugin_enabled($componentname)) { + unset($plugins[$componentname]); + continue; + } + $this->bulkactions[$pluginentrypointobject->get_bulk_action_key()] = [ + 'title' => $pluginentrypointobject->get_bulk_action_title(), + 'url' => $pluginentrypointobject->get_bulk_action_url(), + 'capabilities' => $pluginentrypointobject->get_bulk_action_capabilities() + ]; + } } /** @@ -696,11 +727,6 @@ class view { echo \html_writer::start_div('questionbankwindow boxwidthwide boxaligncenter'); - // This one will become redundant after implementing bulk actions plugin. - if ($this->process_actions_needing_ui()) { - return; - } - $editcontexts = $this->contexts->having_one_edit_tab_cap($tabname); // Show the filters and search options. @@ -933,7 +959,7 @@ class view { $this->display_top_pagnation($OUTPUT->render($pagingbar)); // This html will be refactored in the bulk actions implementation. - echo \html_writer::start_tag('form', ['action' => $pageurl, 'method' => 'post']); + echo \html_writer::start_tag('form', ['action' => $pageurl, 'method' => 'post', 'id' => 'questionsubmit']); echo \html_writer::start_tag('fieldset', ['class' => 'invisiblefieldset', 'style' => "display: block;"]); echo \html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()]); echo \html_writer::input_hidden_params($this->baseurl); @@ -942,7 +968,7 @@ class view { $this->display_bottom_pagination($OUTPUT->render($pagingbar), $totalnumber, $perpage, $pageurl); - $this->display_bottom_controls($totalnumber, $recurse, $category, $catcontext, $addcontexts); + $this->display_bottom_controls($catcontext); echo \html_writer::end_tag('fieldset'); echo \html_writer::end_tag('form'); @@ -999,53 +1025,44 @@ class view { /** * Display the controls at the bottom of the list of questions. - * @param int $totalnumber Total number of questions that might be shown (if it was not for paging). - * @param bool $recurse Whether to include subcategories. - * @param \stdClass $category The question_category row from the database. - * @param \context $catcontext The context of the category being displayed. - * @param array $addcontexts contexts where the user is allowed to add new questions. + * + * @param \context $catcontext The context of the category being displayed. */ - protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts): void { + protected function display_bottom_controls(\context $catcontext): void { $caneditall = has_capability('moodle/question:editall', $catcontext); $canuseall = has_capability('moodle/question:useall', $catcontext); $canmoveall = has_capability('moodle/question:moveall', $catcontext); - - echo \html_writer::start_tag('div', ['class' => "modulespecificbuttonscontainer"]); if ($caneditall || $canmoveall || $canuseall) { - $withselectedcontent = ' ' . get_string('withselected', 'question') . ':'; - echo \html_writer::tag('strong', $withselectedcontent); - echo \html_writer::empty_tag('br'); + global $PAGE; + $bulkactiondatas = []; + $params = $this->base_url()->params(); + $params['returnurl'] = $this->base_url(); + foreach ($this->bulkactions as $key => $action) { + // Check capabilities. + $capcount = 0; + foreach ($action['capabilities'] as $capability) { + if (has_capability($capability, $catcontext)) { + $capcount ++; + } + } + // At least one cap need to be there. + if ($capcount === 0) { + unset($this->bulkactions[$key]); + continue; + } + $actiondata = new \stdClass(); + $actiondata->actionname = $action['title']; + $actiondata->actionkey = $key; + $actiondata->actionurl = new \moodle_url($action['url'], $params); + $bulkactiondata[] = $actiondata; - // Print delete and move selected question. - if ($caneditall) { - echo \html_writer::empty_tag('input', [ - 'type' => 'submit', - 'class' => 'btn btn-secondary mr-1', - 'name' => 'deleteselected', - 'value' => get_string('delete'), - 'data-action' => 'toggle', - 'data-togglegroup' => 'qbank', - 'data-toggle' => 'action', - 'disabled' => true, - ]); + $bulkactiondatas ['bulkactionitems'] = $bulkactiondata; } - - if ($canmoveall && count($addcontexts)) { - echo \html_writer::empty_tag('input', [ - 'type' => 'submit', - 'class' => 'btn btn-secondary mr-1', - 'name' => 'move', - 'value' => get_string('moveto', 'question'), - 'data-action' => 'toggle', - 'data-togglegroup' => 'qbank', - 'data-toggle' => 'action', - 'disabled' => true, - ]); - helper::question_category_select_menu($addcontexts, false, 0, "{$category->id},{$category->contextid}"); + // We dont need to show this section if none of the plugins are enabled. + if (!empty($bulkactiondatas)) { + echo $PAGE->get_renderer('core_question', 'bank')->render_bulk_actions_ui($bulkactiondatas); } } - - echo \html_writer::end_tag('div'); } /** @@ -1094,7 +1111,7 @@ class view { * * @deprecated since Moodle 4.0 * @see print_table() - * @todo Final deprecation of this function in moodle 4.4 + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ protected function start_table() { debugging('Function start_table() is deprecated, please use print_table() instead.', DEBUG_DEVELOPER); @@ -1110,7 +1127,7 @@ class view { * * @deprecated since Moodle 4.0 * @see print_table() - * @todo Final deprecation of this function in moodle 4.4 + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ protected function end_table() { debugging('Function end_table() is deprecated, please use print_table() instead.', DEBUG_DEVELOPER); @@ -1170,126 +1187,27 @@ class view { /** * Process actions for the selected action. - * + * @deprecated since Moodle 4.0 + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ public function process_actions(): void { - global $DB; - // Now, check for commands on this page and modify variables as necessary. - if (optional_param('move', false, PARAM_BOOL) and confirm_sesskey()) { - // Move selected questions to new category. - $category = required_param('category', PARAM_SEQUENCE); - list($tocategoryid, $contextid) = explode(',', $category); - if (! $tocategory = $DB->get_record('question_categories', - ['id' => $tocategoryid, 'contextid' => $contextid])) { - throw new \moodle_exception('cannotfindcate', 'question'); - } - $tocontext = \context::instance_by_id($contextid); - require_capability('moodle/question:add', $tocontext); - $rawdata = (array) data_submitted(); - $questionids = []; - foreach ($rawdata as $key => $value) { - // Parse input for question ids. - if (preg_match('!^q([0-9]+)$!', $key, $matches)) { - $key = $matches[1]; - $questionids[] = $key; - } - } - if ($questionids) { - list($usql, $params) = $DB->get_in_or_equal($questionids); - $questions = $DB->get_records_sql(" - SELECT q.*, c.contextid - FROM {question} q - JOIN {question_categories} c ON c.id = q.category - WHERE q.id {$usql}", $params); - foreach ($questions as $question) { - question_require_capability_on($question, 'move'); - } - question_move_questions_to_category($questionids, $tocategory->id); - redirect($this->baseurl->out(false, ['category' => "{$tocategoryid},{$contextid}"])); - } - } - - if (optional_param('deleteselected', false, PARAM_BOOL)) { - // Delete selected questions from the category. - // If teacher has already confirmed the action. - if (($confirm = optional_param('confirm', '', PARAM_ALPHANUM)) and confirm_sesskey()) { - $deleteselected = required_param('deleteselected', PARAM_RAW); - if ($confirm == md5($deleteselected)) { - if ($questionlist = explode(',', $deleteselected)) { - // For each question either hide it if it is in use or delete it. - foreach ($questionlist as $questionid) { - $questionid = (int)$questionid; - question_require_capability_on($questionid, 'edit'); - if (questions_in_use([$questionid])) { - $DB->set_field('question', 'hidden', 1, ['id' => $questionid]); - } else { - question_delete_question($questionid); - } - } - } - redirect($this->baseurl); - } else { - throw new \moodle_exception('invalidconfirm', 'question'); - } - } - } - - // Unhide a question. - if (($unhide = optional_param('unhide', '', PARAM_INT)) and confirm_sesskey()) { - question_require_capability_on($unhide, 'edit'); - $DB->set_field('question', 'hidden', 0, ['id' => $unhide]); - - // Purge these questions from the cache. - \question_bank::notify_question_edited($unhide); - - redirect($this->baseurl); - } + debugging('Function process_actions() is deprecated and its code has been completely deleted. + Please, remove the call from your code and check core_question\local\bank\bulk_action_base + to learn more about bulk actions in qbank.', DEBUG_DEVELOPER); + // Associated code is deleted to make sure any incorrect call doesnt not cause any data loss. } /** * Process actions with ui. * @return bool + * @deprecated since Moodle 4.0 + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ public function process_actions_needing_ui(): bool { - global $DB, $OUTPUT; - if (optional_param('deleteselected', false, PARAM_BOOL)) { - // Make a list of all the questions that are selected. - $rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted. - $questionlist = ''; // Comma separated list of ids of questions to be deleted. - $questionnames = ''; // String with names of questions separated by
with an asterix - // in front of those that are in use. - $inuse = false; // Set to true if at least one of the questions is in use. - foreach ($rawquestions as $key => $value) { // Parse input for question ids. - if (preg_match('!^q([0-9]+)$!', $key, $matches)) { - $key = $matches[1]; - $questionlist .= $key.','; - question_require_capability_on((int)$key, 'edit'); - if (questions_in_use([$key])) { - $questionnames .= '* '; - $inuse = true; - } - $questionnames .= $DB->get_field('question', 'name', ['id' => $key]) . '
'; - } - } - if (!$questionlist) { // No questions were selected. - redirect($this->baseurl); - } - $questionlist = rtrim($questionlist, ','); - - // Add an explanation about questions in use. - if ($inuse) { - $questionnames .= '
'.get_string('questionsinuse', 'question'); - } - $baseurl = new \moodle_url($this->baseurl, $this->baseurl->params()); - $deleteurl = new \moodle_url($baseurl, ['deleteselected' => $questionlist, 'confirm' => md5($questionlist), - 'sesskey' => sesskey()]); - - $continue = new \single_button($deleteurl, get_string('delete'), 'post'); - echo $OUTPUT->confirm(get_string('deletequestionscheck', 'question', $questionnames), $continue, $baseurl); - - return true; - } - + debugging('Function process_actions_needing_ui() is deprecated and its code has been completely deleted. + Please, remove the call from your code and check core_question\local\bank\bulk_action_base + to learn more about bulk actions in qbank.', DEBUG_DEVELOPER); + // Associated code is deleted to make sure any incorrect call doesnt not cause any data loss. return false; } diff --git a/question/edit.php b/question/edit.php index a5444594f95..b0d5ade4ae0 100644 --- a/question/edit.php +++ b/question/edit.php @@ -38,9 +38,6 @@ $PAGE->set_url($url); $questionbank = new core_question\local\bank\view($contexts, $thispageurl, $COURSE, $cm); -// TODO MDL-72076 - this one will become redundant after implementing bulk actions UI. -$questionbank->process_actions(); - $context = $contexts->lowest(); $streditingquestions = get_string('editquestions', 'question'); $PAGE->set_title($streditingquestions); diff --git a/question/renderer.php b/question/renderer.php index 7b8ca0bb0a2..8d4cb3c5a1b 100644 --- a/question/renderer.php +++ b/question/renderer.php @@ -156,6 +156,16 @@ class core_question_bank_renderer extends plugin_renderer_base { return $this->render_from_template('core_question/showtext_checkbox', $displaydata); } + /** + * Render bulk actions ui. + * + * @param array $displaydata + * @return bool|string + */ + public function render_bulk_actions_ui($displaydata) { + return $this->render_from_template('core_question/bulk_actions_ui', $displaydata); + } + /** * Build the HTML for the question chooser javascript popup. * diff --git a/question/templates/bulk_actions_ui.mustache b/question/templates/bulk_actions_ui.mustache new file mode 100644 index 00000000000..4601cd09c1b --- /dev/null +++ b/question/templates/bulk_actions_ui.mustache @@ -0,0 +1,43 @@ +{{! + 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 . +}} +{{! + @template core_question/bulk_actions_ui + + Example context (json): + { + "displaydata": [ + { + "bulkactionitems": "", + "actionname": "Move to", + "actionkey": "deleteselected", + "actionurl": "/question/bank/bulkmove/move.php?courseid=2" + } + ] + } +}} + +
+ + +
diff --git a/question/tests/behat/behat_question.php b/question/tests/behat/behat_question.php index 6a7c097fe54..1985da7bea1 100644 --- a/question/tests/behat/behat_question.php +++ b/question/tests/behat/behat_question.php @@ -103,4 +103,40 @@ class behat_question extends behat_question_base { $this->execute("behat_general::i_click_on_in_the", [$action, 'link', $questionname, 'table_row']); } + + /** + * A particular bulk action is visible in the question bank UI. + * + * @When I should see question bulk action :action + * @param string $action the value of the input for the action. + */ + public function i_should_see_question_bulk_action($action) { + // Check if its visible. + $this->execute("behat_general::should_be_visible", + ["#bulkactionsui-container input[name='$action']", "css_element"]); + } + + /** + * A particular bulk action should not be visible in the question bank UI. + * + * @When I should not see question bulk action :action + * @param string $action the value of the input for the action. + */ + public function i_should_not_see_question_bulk_action($action) { + // Check if its visible. + $this->execute("behat_general::should_not_be_visible", + ["#bulkactionsui-container input[name='$action']", "css_element"]); + } + + /** + * A click on a particular bulk action in the question bank UI. + * + * @When I click on question bulk action :action + * @param string $action the value of the input for the action. + */ + public function i_click_on_question_bulk_action($action) { + // Click the bulk action. + $this->execute("behat_general::i_click_on", + ["#bulkactionsui-container input[name='$action']", "css_element"]); + } } diff --git a/question/tests/behat/question_categories.feature b/question/tests/behat/question_categories.feature index bf2dffeff2a..42381d44ae9 100644 --- a/question/tests/behat/question_categories.feature +++ b/question/tests/behat/question_categories.feature @@ -31,8 +31,10 @@ Feature: A teacher can move questions between categories in the question bank When I navigate to "Question bank > Questions" in current page administration And I set the field "Select a category" to "Used category" And I click on "Test question to be moved" "checkbox" in the "Test question to be moved" "table_row" + And I click on "With selected" "button" + And I click on question bulk action "move" And I set the field "Question category" to "Subcategory" - And I press "Move to >>" + And I press "Move to" Then I should see "Test question to be moved" And the field "Select a category" matches value "      Subcategory (1)" And the "Select a category" select box should contain "Used category" diff --git a/question/tests/behat/question_categories_idnumber.feature b/question/tests/behat/question_categories_idnumber.feature index 88bee950185..1bcf0409ae6 100644 --- a/question/tests/behat/question_categories_idnumber.feature +++ b/question/tests/behat/question_categories_idnumber.feature @@ -84,8 +84,10 @@ Feature: A teacher can put questions with idnumbers in categories in the questio And I press "submitbutton" # Javascript is required for the next step. And I click on "Test question 3" "checkbox" in the "Test question 3" "table_row" - And I set the field "Question category" to "Used category" - And I press "Move to >>" + And I click on "With selected" "button" + And I click on question bulk action "move" + And I set the field "Question category" to "Subcategory" + And I press "Move to" And I choose "Edit question" action for "Test question 3" in the question bank # The question just moved into this category needs to have a unique idnumber, so a number is appended. Then the field "ID number" matches value "q1_1" diff --git a/question/tests/behat/select_questions.feature b/question/tests/behat/select_questions.feature index b4b766e3f5f..90ff84c6432 100644 --- a/question/tests/behat/select_questions.feature +++ b/question/tests/behat/select_questions.feature @@ -49,8 +49,11 @@ Feature: The questions in the question bank can be selected in various ways @javascript Scenario: The action button can be disabled when the question not be chosen in the list of questions - Given the "Delete" "button" should be disabled - And the "Move to >>" "button" should be disabled - When I click on "Select all" "checkbox" - Then the "Delete" "button" should be enabled - And the "Move to >>" "button" should be enabled + Given the field "Select all" matches value "" + When I click on "With selected" "button" + And I should not see "Delete" + And I should not see "Move to..." + And I click on "Select all" "checkbox" + And I click on "With selected" "button" + Then I should see question bulk action "move" + And I should see question bulk action "deleteselected" diff --git a/question/upgrade.txt b/question/upgrade.txt index 0bddd1d756a..ea09fa1c2ba 100644 --- a/question/upgrade.txt +++ b/question/upgrade.txt @@ -73,6 +73,12 @@ This files describes API changes for code that uses the question API. restart_preview() => qbank_previewquestion\helper::restart_preview(), core_question_output_fragment_tags_form() => /question/bank/qbank_tagquestion/lib.php. +10)The qbank api now allows bulk actions from qbank plugins. Its possible to implement a qbank plugin and pass the bulk + bulk action object from the plugin feature using the get_bulk_actions(). The base class for this feature is bulk_action_base. + The following methods are deprecated as a part of bulk actions ui implementation and its no more required to call these methods + anymore as the api is now self sufficient, calling the api will fetch all the features: + process_actions(), process_actions_needing_ui(). + === 3.9 == 1) For years, the ..._questions_in_use callback has been the right way for plugins to