Rev 1 | AutorÃa | Comparar con el anterior | Ultima modificación | Ver Log |
<?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_managecategories\form;
use context;
use context_module;
use context_course;
use qbank_managecategories\helper;
use moodle_exception;
use moodle_url;
use core_question\local\bank\question_edit_contexts;
use qbank_managecategories\output\category;
use core_question\category_manager;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->libdir . '/questionlib.php');
/**
* Defines the form for editing question categories.
*
* Form for editing questions categories (name, description, etc.)
*
* @package qbank_managecategories
* @copyright 2007 Jamie Pratt me@jamiep.org
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_category_edit_form extends \core_form\dynamic_form {
/** @var ?category_manager $manager */
protected ?category_manager $manager = null;
/**
* Return the category manager.
*
* Since we cannot override the constructor, using this method ensures the manager has always been initialised
* before access.
*
* @return category_manager
*/
protected function get_manager(): category_manager {
if (is_null($this->manager)) {
$this->manager = new category_manager();
}
return $this->manager;
}
/**
* Get the category, contexts, course and cmid based on the data provided via AJAX.
*
* @return array
* @throws \coding_exception
*/
protected function get_current_data() {
// If categoryid is set, we are editing an existing category.
$currentcat = isset($this->_ajaxformdata['categoryid']) ? (int)$this->_ajaxformdata['categoryid'] : 0;
// Determine the context based on the provided IDs.
$cmid = isset($this->_ajaxformdata['cmid']) ? (int)$this->_ajaxformdata['cmid'] : 0;
$courseid = isset($this->_ajaxformdata['courseid']) ? (int)$this->_ajaxformdata['courseid'] : 0;
if ($cmid !== 0) {
$thiscontext = context_module::instance($cmid);
}
if ($courseid !== 0) {
$thiscontext = context_course::instance($courseid);
}
if ($courseid === 0 && $cmid === 0) {
$parentcontext = (int)explode(',', $this->_ajaxformdata['parent'])[1];
$contextid = $parentcontext === 0 ? $this->_ajaxformdata['contextid'] : (int)$parentcontext;
$thiscontext = context::instance_by_id($contextid);
}
if ($thiscontext) {
$contexts = new question_edit_contexts($thiscontext);
$contexts = $contexts->all();
}
return [$currentcat, $cmid, $courseid, $thiscontext, $contexts];
}
/**
* Build the form definition.
*
* This adds all the form fields that the manage categories feature needs.
* @throws \coding_exception
*/
protected function definition() {
$mform = $this->_form;
[$currentcat, $cmid, $courseid, $thiscontext, $contexts] = $this->get_current_data();
$mform->addElement('hidden', 'contextid', $thiscontext->id);
$mform->setType('contextid', PARAM_INT);
$mform->addElement('hidden', 'courseid', $courseid);
$mform->setType('courseid', PARAM_INT);
$mform->addElement('hidden', 'cmid', $cmid);
$mform->setType('cmid', PARAM_INT);
$mform->addElement('hidden', 'currentparent');
$mform->setDefault('currentparent', $this->_ajaxformdata['parent'] ?? '');
$mform->setType('currentparent', PARAM_SEQUENCE);
$mform->addElement(
'questioncategory',
'parent',
get_string('parentcategory', 'question'),
[
'contexts' => $contexts,
'top' => true,
'currentcat' => $currentcat,
'nochildrenof' => $currentcat,
],
);
$mform->setType('parent', PARAM_SEQUENCE);
if ($this->get_manager()->is_only_child_of_top_category_in_context($currentcat)) {
$mform->hardFreeze('parent');
}
$mform->addHelpButton('parent', 'parentcategory', 'question');
$mform->addElement('text', 'name', get_string('name'), 'maxlength="254" size="50"');
$mform->setDefault('name', '');
$mform->addRule('name', get_string('categorynamecantbeblank', 'question'), 'required', null, 'client');
$mform->setType('name', PARAM_TEXT);
$mform->addElement('editor', 'info', get_string('categoryinfo', 'question'),
['rows' => 10], ['noclean' => 1]);
$mform->setDefault('info', '');
$mform->setType('info', PARAM_RAW);
$mform->addElement('text', 'idnumber', get_string('idnumber', 'question'), 'maxlength="100" size="10"');
$mform->addHelpButton('idnumber', 'idnumber', 'question');
$mform->setType('idnumber', PARAM_RAW);
$mform->addElement('hidden', 'id', 0);
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'questioncount', $this->_ajaxformdata['questioncount'] ?? 0);
$mform->setType('questioncount', PARAM_INT);
$mform->addElement('hidden', 'sortorder', $this->_ajaxformdata['sortorder'] ?? 0);
$mform->setType('sortorder', PARAM_INT);
}
/**
* Validation.
*
* Ensure that we aren't trying to move the only child of a top category to a different context.
* Ensure that the ID Number is unique within the target context.
*
* @param array $data
* @param array $files
* @return array the errors that were found
* @throws \dml_exception|\coding_exception
*/
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
$currentrec = $DB->get_record('question_categories', ['id' => $data['id']]);
$currentparent = $data['currentparent'];
$data['parent'] ??= $currentparent; // If no parent was submitted, use the current parent.
[$parentid, $contextid] = explode(',', $data['parent']);
if ($currentrec) {
$currentparent = $currentrec->parent . ',' . $currentrec->contextid;
// Cannot move the last category in a context to another parent.
$lastcategoryinthiscontext = $this->get_manager()->is_only_child_of_top_category_in_context($data['id']);
if ($lastcategoryinthiscontext && $currentparent !== $data['parent']) {
if ($parentid !== $this->_ajaxformdata['id']) {
$errors['parent'] = get_string('lastcategoryinthiscontext', 'qbank_managecategories');
}
}
// Cannot move category in same category.
if ($currentrec->id === $parentid && $currentrec->contextid === $contextid) {
$errors['parent'] = get_string('categoryincategory', 'qbank_managecategories');
}
}
// Add field validation check for duplicate idnumber.
$id = $data['id'] ?? null;
if (!empty($data['idnumber']) && !$this->get_manager()->idnumber_is_unique_in_context($data['idnumber'], $contextid, $id)) {
$errors['idnumber'] = get_string('idnumbertaken', 'error');
}
return $errors;
}
#[\Override]
protected function get_context_for_dynamic_submission(): context {
$contextid = $this->optional_param('contextid', 0, PARAM_INT);
if ($contextid === 0) {
$contextid = $this->_ajaxformdata['contextid'];
}
return context::instance_by_id($contextid);
}
#[\Override]
protected function check_access_for_dynamic_submission(): void {
$this->get_manager()->require_manage_category($this->get_context_for_dynamic_submission());
}
/**
* Process the category form
*
* Either add or update a question category, then return a reactive state update for the category.
*
* @return array[] Reactive state update.
* {@link https://moodledev.io/docs/4.2/guides/javascript/reactive#controlling-the-state-from-the-backend}
* @throws \coding_exception
* @throws \dml_exception
* @throws moodle_exception
*/
public function process_dynamic_submission(): array {
global $DB, $OUTPUT;
$values = $this->get_data();
$newinfo = format_text($values->info['text'], (int)$values->info['format'], ['noclean' => false]);
$idnumber = $values->idnumber;
if ((string)$idnumber === '') {
$idnumber = null;
}
if (empty($values->id)) {
$values->id = $this->get_manager()->add_category(
$values->parent,
$values->name,
$newinfo,
(int)$values->info['format'],
$idnumber,
);
} else {
$this->get_manager()->update_category(
$values->id,
$values->parent ?? '', // If we can't change the parent, it is not passed through he form.
$values->name,
$newinfo,
(int)$values->info['format'],
$idnumber,
);
}
$record = $DB->get_record('question_categories', ['id' => $values->id]);
// The question count will never change, we just need it passed through to re-render the category.
$record->questioncount = $values->questioncount;
$category = new category(
$record,
context::instance_by_id($record->contextid),
$values->cmid ?? 0,
$values->courseid ?? 0,
);
return [
[
'name' => 'categories',
'action' => 'put',
'fields' => [
'id' => $record->id,
'name' => $record->name,
'parent' => $record->parent,
'sortorder' => $record->sortorder,
'draghandle' => $category->get_canreorder(),
'templatecontext' => $category->export_for_template($OUTPUT),
],
],
];
}
#[\Override]
public function set_data_for_dynamic_submission(): void {
$categoryid = isset($this->_ajaxformdata['categoryid']) ? (int)$this->_ajaxformdata['categoryid'] : 0;
if ($categoryid !== 0) {
global $DB;
$cattoset = $DB->get_record('question_categories', ['id' => $categoryid]);
$this->set_data((object)[
'id' => (int)$cattoset->id,
'name' => $cattoset->name,
'contextid' => (int)$cattoset->contextid,
'info' => [
'format' => FORMAT_HTML,
'text' => $cattoset->info,
],
'infoformat' => (int)$cattoset->infoformat,
'parent' => (int)$cattoset->parent . ',' . (int)$cattoset->contextid,
'idnumber' => $cattoset->idnumber,
]);
}
}
#[\Override]
protected function get_page_url_for_dynamic_submission(): moodle_url {
$params = [];
$cmid = isset($this->_ajaxformdata['cmid']) ? (int)$this->_ajaxformdata['cmid'] : 0;
$courseid = isset($this->_ajaxformdata['courseid']) ? (int)$this->_ajaxformdata['courseid'] : 0;
if ($cmid !== 0) {
$params['cmid'] = $cmid;
}
if ($courseid !== 0) {
$params['courseid'] = $courseid;
}
return new moodle_url('/question/bank/managecategories/category.php', $params);
}
}