Commit 0805e387 authored by Safat Shahin's avatar Safat Shahin
Browse files

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: default avatarSafat Shahin <safatshahin@catalyst-au.net>
Co-Authored-By: default avatarMatt Porritt <mattp@catalyst-au.net>
parent 7013bda3
......@@ -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}"?';
......
......@@ -1950,6 +1950,7 @@ class core_plugin_manager {
),
'qbank' => [
'bulkmove',
'comment',
'deletequestion',
'editquestion',
......
......@@ -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);
......
......@@ -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 =====================================================.
......
<?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',
];
}
}
<?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];
}
}
<?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);
}
}
<?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();
}
}
<?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';
}
}
<?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.';
<?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();
{{!
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>
@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"
<?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,