Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generated_id_element #150

Merged
merged 2 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 25 additions & 16 deletions classes/form/context/array_render_context.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class array_render_context extends render_context {
* Initializes a new array-based context.
*
* @param render_context $parent context containing this group
* @param string $prefix prefix for the names of elements in this context
* @param string $prefix prefix for the names of elements in this context
*/
public function __construct(render_context $parent, string $prefix) {
$this->parent = $parent;
Expand All @@ -82,8 +82,8 @@ public function __construct(render_context $parent, string $prefix) {
/**
* Create, add and return an element.
*
* @param string $type the type name of the element, as per the Moodle docs.
* @param string $name the name of the generated form element.
* @param string $type the type name of the element, as per the Moodle docs.
* @param string $name the name of the generated form element.
* @param mixed ...$args remaining arguments specific to the element type.
* @return object the created element. Really an instance of {@see \HTML_QuickForm_element}, but the return type of
* {@see \MoodleQuickForm::addElement()} is also an object.
Expand All @@ -109,7 +109,7 @@ public function set_type(string $name, string $type): void {
/**
* Sets the default of an element which has been (or will be) added independently.
*
* @param string $name the name of the target element.
* @param string $name the name of the target element.
* @param mixed $default default value for the element.
* @see \MoodleQuickForm::setDefault()
*/
Expand All @@ -122,14 +122,14 @@ public function set_default(string $name, $default): void {
*
* Must be called *after* the element was added using {@see add_element}.
*
* @param string $name the name of the target element.
* @param string|null $message message to display for invalid data.
* @param string $type rule type, use getRegisteredRules() to get types.
* @param string|null $format required for extra rule data.
* @param string $name the name of the target element.
* @param string|null $message message to display for invalid data.
* @param string $type rule type, use getRegisteredRules() to get types.
* @param string|null $format required for extra rule data.
* @param string|null $validation where to perform validation: "server", "client".
* @param bool $reset client-side validation: reset the form element to its original value if there is
* @param bool $reset client-side validation: reset the form element to its original value if there is
* an error?
* @param bool $force force the rule to be applied, even if the target form element does not exist.
* @param bool $force force the rule to be applied, even if the target form element does not exist.
* @see \MoodleQuickForm::addRule()
*/
public function add_rule(string $name, ?string $message, string $type, ?string $format = null,
Expand All @@ -144,9 +144,9 @@ public function add_rule(string $name, ?string $message, string $type, ?string $
* Adds a condition which will disable the named element if met.
*
* @param string $dependant name of the element which has the dependency on another element
* @param string $dependency absolute name of the element which is depended on
* @param string $operator one of a fixed set of conditions, as in {@see MoodleQuickForm::disabledIf}
* @param mixed $value for conditions requiring it, the value to compare with. Ignored otherwise.
* @param string $dependency absolute name of the element which is depended on
* @param string $operator one of a fixed set of conditions, as in {@see MoodleQuickForm::disabledIf}
* @param mixed $value for conditions requiring it, the value to compare with. Ignored otherwise.
* @see \MoodleQuickForm::disabledIf()
*/
public function disable_if(string $dependant, string $dependency, string $operator, $value = null): void {
Expand All @@ -157,9 +157,9 @@ public function disable_if(string $dependant, string $dependency, string $operat
* Adds a condition which will hide the named element if met.
*
* @param string $dependant name of the element which has the dependency on another element
* @param string $dependency absolute name of the element which is depended on
* @param string $operator one of a fixed set of conditions, as in {@see MoodleQuickForm::hideIf}
* @param mixed $value for conditions requiring it, the value to compare with. Ignored otherwise.
* @param string $dependency absolute name of the element which is depended on
* @param string $operator one of a fixed set of conditions, as in {@see MoodleQuickForm::hideIf}
* @param mixed $value for conditions requiring it, the value to compare with. Ignored otherwise.
* @see \MoodleQuickForm::hideIf()
*/
public function hide_if(string $dependant, string $dependency, string $operator, $value = null): void {
Expand Down Expand Up @@ -187,4 +187,13 @@ public function next_unique_int(): int {
public function contextualize(?string $text): ?string {
return $this->parent->contextualize($text);
}

/**
* Generate a new UUID. Probably uses {@see uuid}, but may be overridden for tests.
*
* @return string
*/
public function generate_uuid(): string {
return $this->parent->generate_uuid();
}
}
1 change: 1 addition & 0 deletions classes/form/context/mform_render_context.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

namespace qtype_questionpy\form\context;

use Closure;
use qtype_questionpy\utils;

/**
Expand Down
8 changes: 8 additions & 0 deletions classes/form/context/render_context.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

namespace qtype_questionpy\form\context;

use core\uuid;
use moodleform;
use MoodleQuickForm;
use qtype_questionpy\utils;
Expand Down Expand Up @@ -212,4 +213,11 @@ public function reference_to_absolute(string $reference): string {
* @return string|null input string with format specifiers replaced
*/
abstract public function contextualize(?string $text): ?string;

/**
* Generate a new UUID. Probably uses {@see uuid}, but may be overridden for tests.
*
* @return string
*/
abstract public function generate_uuid(): string;
}
14 changes: 14 additions & 0 deletions classes/form/context/root_render_context.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

namespace qtype_questionpy\form\context;

use core\uuid;

/**
* Uppermost render context.
*
Expand All @@ -28,6 +30,9 @@ class root_render_context extends mform_render_context {
/** @var int the next int which will be returned by {@see next_unique_int} */
private int $nextuniqueint = 1;

/** @var callable can be set from tests to supply mock UUIDs */
public $uuidgen = [uuid::class, 'generate'];

/**
* Get a unique and deterministic integer for use in generated element names and IDs.
*
Expand All @@ -48,4 +53,13 @@ public function next_unique_int(): int {
public function contextualize(?string $text): ?string {
return $text;
}

/**
* Generate a new UUID. Probably uses {@see uuid}, but may be overridden for tests.
*
* @return string
*/
public function generate_uuid(): string {
return ($this->uuidgen)();
}
}
11 changes: 10 additions & 1 deletion classes/form/context/section_render_context.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class section_render_context extends mform_render_context {
* Initializes a new {@see section_render_context}.
*
* @param render_context $parent context containing this section
* @param string $name the name part which will be appended to `$parent`'s prefix
* @param string $name the name part which will be appended to `$parent`'s prefix
*/
public function __construct(render_context $parent, string $name) {
$this->parent = $parent;
Expand Down Expand Up @@ -69,4 +69,13 @@ public function next_unique_int(): int {
public function contextualize(?string $text): ?string {
return $this->parent->contextualize($text);
}

/**
* Generate a new UUID. Probably uses {@see uuid}, but may be overridden for tests.
*
* @return string
*/
public function generate_uuid(): string {
return $this->parent->generate_uuid();
}
}
3 changes: 2 additions & 1 deletion classes/form/elements/form_element.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* @copyright 2022 TU Berlin, innoCampus {@link https://www.questionpy.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[array_polymorphic('kind', variants: [
#[array_polymorphic(discriminator: 'kind', variants: [
'checkbox' => checkbox_element::class,
'checkbox_group' => checkbox_group_element::class,
'group' => group_element::class,
Expand All @@ -38,6 +38,7 @@
'static_text' => static_text_element::class,
'input' => text_input_element::class,
'textarea' => text_area_element::class,
'id' => generated_id_element::class,
], fallbackvariant: fallback_element::class)]
abstract class form_element implements qpy_renderable {
}
60 changes: 60 additions & 0 deletions classes/form/elements/generated_id_element.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
// This file is part of the QuestionPy Moodle plugin - https://questionpy.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 qtype_questionpy\form\elements;

use core\uuid;
use qtype_questionpy\form\context\render_context;


/**
* Generates a unique ID which won't change across form saves.
*
* @package qtype_questionpy
* @author Maximilian Haye
* @copyright 2024 TU Berlin, innoCampus {@link https://www.questionpy.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class generated_id_element extends form_element {
/**
* Trivial constructor.
*
* @param string $name
*/
public function __construct(
/** @var string $name */
public string $name
) {
}

/**
* Render this item to the given context.
*
* @param render_context $context target context
* @package qtype_questionpy
*/
public function render_to(render_context $context): void {
$mangledname = $context->mangle_name($this->name);
$value = $context->moodleform->optional_param(
$mangledname,
null,
PARAM_ALPHANUMEXT
) ?? $context->data[$this->name] ?? $context->generate_uuid();

$context->add_element('hidden', $mangledname, $value);
$context->set_type($this->name, PARAM_ALPHANUMEXT);
}
}
2 changes: 1 addition & 1 deletion classes/form/elements/select_element.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public function render_to(render_context $context): void {
$selected = [];
$optionsassociative = [];
foreach ($this->options as $option) {
$optionsassociative[$option->value] = $context->contextualize($option->label);
$optionsassociative[$option->value] = s($context->contextualize($option->label));
if ($option->selected) {
$selected[] = $option->value;
}
Expand Down
2 changes: 2 additions & 0 deletions tests/data_provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use qtype_questionpy\form\conditions\is_not_checked;
use qtype_questionpy\form\elements\checkbox_element;
use qtype_questionpy\form\elements\checkbox_group_element;
use qtype_questionpy\form\elements\generated_id_element;
use qtype_questionpy\form\elements\group_element;
use qtype_questionpy\form\elements\hidden_element;
use qtype_questionpy\form\elements\option;
Expand Down Expand Up @@ -143,5 +144,6 @@ function element_provider(): array {
['static_text', new static_text_element('my_text', 'Label', 'Lorem ipsum dolor sit amet.')],
['input', new text_input_element('my_field', 'Label', true, 'default', 'placeholder')],
['textarea', new text_area_element('my_field', 'Label', true, 'default', 'placeholder')],
['id', new generated_id_element('my_id')],
];
}
9 changes: 9 additions & 0 deletions tests/form/elements/html/id.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><form id="my_form" autocomplete="off" action="" method="post" accept-charset="utf-8" class="mform">
<div style="display: none;"><input name="qpy_form[my_id]" type="hidden" value="24daab97-7eeb-422d-a2f3-f4e770fb11f6">
<input name="sesskey" type="hidden" value="sesskey">
<input name="_qf__qtype_questionpy_form_elements_test_moodleform" type="hidden" value="1">
</div>


</form></body></html>
4 changes: 4 additions & 0 deletions tests/form/elements/json/id.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"kind": "id",
"name": "my_id"
}
1 change: 1 addition & 0 deletions tests/form/elements/test_moodleform.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public function __construct(qpy_renderable $element) {
*/
protected function definition() {
$context = new root_render_context($this, $this->_form, 'qpy_form', []);
$context->uuidgen = fn() => '24daab97-7eeb-422d-a2f3-f4e770fb11f6';
$this->element->render_to($context);
}
}