MDL-72076 bulk actions ui: Implement bulk actions in qbank API

This implementation will introduce bulk actions qbank
plugins which can grab the selected question and perform
actions according to the selected option from dropdown
in the base view.

Co-Authored-By: Safat Shahin <safatshahin@catalyst-au.net>
Co-Authored-By: Matt Porritt <mattp@catalyst-au.net>
This commit is contained in:
Safat Shahin 2021-09-26 18:09:01 +10:00
parent 7013bda35b
commit 0805e387b7
34 changed files with 1107 additions and 189 deletions

View File

@ -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}"?<br /> 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}"?';

View File

@ -1950,6 +1950,7 @@ class core_plugin_manager {
),
'qbank' => [
'bulkmove',
'comment',
'deletequestion',
'editquestion',

View File

@ -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);

View File

@ -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 =====================================================.

View File

@ -0,0 +1,47 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace 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 <safatshahin@catalyst-au.net>
* @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',
];
}
}

View File

@ -0,0 +1,90 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_bulkmove;
/**
* Bulk move helper.
*
* @package qbank_bulkmove
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @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];
}
}

View File

@ -0,0 +1,39 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_bulkmove\output;
/**
* Class renderer.
*
* @package qbank_bulkmove
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @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);
}
}

View File

@ -0,0 +1,34 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace 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 <safatshahin@catalyst-au.net>
* @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();
}
}

View File

@ -0,0 +1,32 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace qbank_bulkmove\privacy;
/**
* Privacy Subsystem for qbank_deletequestion implementing null_provider.
*
* @package qbank_bulkmove
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @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';
}
}

View File

@ -0,0 +1,31 @@
<?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/>.
/**
* Strings for component qbank_bulkmove, language 'en'
*
* @package qbank_bulkmove
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @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.';

View File

@ -0,0 +1,103 @@
<?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/>.
/**
* Move questions page.
*
* @package qbank_bulkmove
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @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();

View File

@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@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": "<select class='select custom-select custom-select'><optgroup label='Course: tes'><option value='2,13'>Default for test (5)</option></optgroup></select>",
"moveurl": "/question/bank/bulkmove/move.php?courseid=2",
"returnurl": "/question/edit.php?courseid=2"
}
}}
<div class="bulkmovequestion-header">
<h3>{{#str}} bulkmoveheader, qbank_bulkmove {{/str}}</h3>
</div>
<form action="{{{moveurl}}}" method="post" id="bulkmovequestion">
{{{categorydropdown}}}
<input type="submit" value="{{#str}} moveto, question {{/str}}" class="btn btn-primary" name="move" data-action="toggle" data-togglegroup="qbank"
data-toggle="action" form="bulkmovequestion">
<a href="{{{returnurl}}}" class="btn btn-secondary">{{#str}} close, qbank_bulkmove {{/str}}</a>
</form>

View File

@ -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"

View File

@ -0,0 +1,214 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace 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 <safatshahin@catalyst-au.net>
* @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']);
}
}

View File

@ -0,0 +1,31 @@
<?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/>.
/**
* Version information for qbank_bulkmove.
*
* @package qbank_bulkmove
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @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;

View File

@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace 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 <safatshahin@catalyst-au.net>
* @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',
];
}
}

View File

@ -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();
}
}

View File

@ -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"

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
/**

View File

@ -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();
}
}

View File

@ -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('<option selected="selected" value="">choosedots</option>', $output);
$this->assertStringContainsString('Test this question category', $output);
}

View File

@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
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 <safatshahin@catalyst-au.net>
* @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;
}
}

View File

@ -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;
}

View File

@ -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 = '&nbsp;' . 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 <br/> 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]) . '<br />';
}
}
if (!$questionlist) { // No questions were selected.
redirect($this->baseurl);
}
$questionlist = rtrim($questionlist, ',');
// Add an explanation about questions in use.
if ($inuse) {
$questionnames .= '<br />'.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;
}

View File

@ -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);

View File

@ -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.
*

View File

@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@template core_question/bulk_actions_ui
Example context (json):
{
"displaydata": [
{
"bulkactionitems": "<input value='Move to'>",
"actionname": "Move to",
"actionkey": "deleteselected",
"actionurl": "/question/bank/bulkmove/move.php?courseid=2"
}
]
}
}}
<div class="btn-group dropup mt-2 modulespecificbuttonscontainer" id="bulkactionsui-container">
<button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="bulkactionsui-selector">
{{#str}} withselected, question{{/str}}
</button>
<div class="dropdown-menu" aria-labelledby="bulkactionsui-selector">
{{#bulkactionitems}}
<input type="submit" value="{{actionname}}" class="dropdown-item" name="{{actionkey}}" data-action="toggle" data-togglegroup="qbank"
data-toggle="action" form="questionsubmit" formaction="{{{actionurl}}}" disabled="disabled">
{{/bulkactionitems}}
</div>
</div>

View File

@ -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"]);
}
}

View File

@ -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 "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Subcategory (1)"
And the "Select a category" select box should contain "Used category"

View File

@ -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"

View File

@ -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"

View File

@ -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