MDL-82571 customfield_number: Display format to number custom field

Add extra settings to the new number custom field in order to allow it
format the exported data using placeholders plus supporting multi language.
This commit is contained in:
Carlos Castillo 2024-08-08 16:58:37 +02:00
parent 072fb90384
commit 241fa420d8
7 changed files with 266 additions and 17 deletions

View File

@ -892,6 +892,49 @@ final class externallib_test extends externallib_advanced_testcase {
'value' => userdate(1580389200),
'valueraw' => 1580389200,
], reset($course['customfields']));
// Set the multilang filter to apply to strings + reset filer caches.
filter_set_global_state('multilang', TEXTFILTER_ON);
filter_set_applies_to_strings('multilang', true);
\filter_manager::reset_caches();
// Let's create a custom field (number), and test the placeholders/multilang display.
/** @var core_customfield_generator $cfgenerator */
$cfgenerator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
$numberfieldata = [
'categoryid' => $fieldcategory->get('id'),
'name' => 'Price',
'shortname' => 'price',
'type' => 'number',
'configdata' => [
'display' => '{value}',
'decimalplaces' => 2,
],
];
// Create a number custom field with default display template.
$numberfield = $cfgenerator->create_field($numberfieldata);
$cfgenerator->add_instance_data($numberfield, $newcourse->id, 15);
// Create a number custom field with multilang display template.
$numberfieldata['name'] = 'Price (multilang)';
$numberfieldata['shortname'] = 'pricemultilang';
$numberfieldata['configdata']['display'] = '<span lang="en" class="multilang">$ {value}</span>'
. '<span lang="es" class="multilang">€ {value}</span>';
$numberfield1 = $cfgenerator->create_field($numberfieldata);
$cfgenerator->add_instance_data($numberfield1, $newcourse->id, 20);
$courses = external_api::clean_returnvalue(
core_course_external::get_courses_returns(),
core_course_external::get_courses(['ids' => [$newcourse->id]])
);
$course = reset($courses);
$this->assertCount(3, $course['customfields']);
// Assert the received number custom fields display placeholders correctly with multilang filter when applied.
$this->assertEquals('15.00', $course['customfields'][1]['value']);
$this->assertEquals('$ 20.00', $course['customfields'][2]['value']);
}
/**

View File

@ -120,12 +120,8 @@ class data_controller extends \core_customfield\data_controller {
* @return string|null
*/
public function export_value(): ?string {
$value = $this->get_value();
if ($this->is_empty($value)) {
return null;
}
$decimalplaces = (int) $this->get_field()->get_configdata_property('decimalplaces');
return format_float((float) $value, $decimalplaces);
/** @var field_controller $field */
$field = $this->get_field();
return $field->prepare_field_for_display($this->get_value(), $this->get_context());
}
}

View File

@ -18,6 +18,9 @@ declare(strict_types=1);
namespace customfield_number;
use core\context\system;
use core\context;
use html_writer;
use MoodleQuickForm;
/**
@ -62,6 +65,29 @@ class field_controller extends \core_customfield\field_controller {
$mform->setDefault('configdata[decimalplaces]', 0);
}
$mform->setType('configdata[decimalplaces]', PARAM_INT);
// Display format settings.
// TODO: Change this after MDL-82996 fixed.
$randelname = 'str_' . random_string();
$mform->addGroup([], $randelname, html_writer::tag('h4', get_string('headerdisplaysettings', 'customfield_number')));
// Display template.
$mform->addElement('text', 'configdata[display]', get_string('display', 'customfield_number'),
['size' => 50]);
$mform->setType('configdata[display]', PARAM_TEXT);
$mform->addHelpButton('configdata[display]', 'display', 'customfield_number');
if ($this->get_configdata_property('display') === null) {
$mform->setDefault('configdata[display]', '{value}');
}
// Display when zero.
$mform->addElement('text', 'configdata[displaywhenzero]', get_string('displaywhenzero', 'customfield_number'),
['size' => 50]);
$mform->setType('configdata[displaywhenzero]', PARAM_TEXT);
$mform->addHelpButton('configdata[displaywhenzero]', 'displaywhenzero', 'customfield_number');
if ($this->get_configdata_property('displaywhenzero') === null) {
$mform->setDefault('configdata[displaywhenzero]', 0);
}
}
/**
@ -74,6 +100,11 @@ class field_controller extends \core_customfield\field_controller {
public function config_form_validation(array $data, $files = []): array {
$errors = parent::config_form_validation($data, $files);
$display = $data['configdata']['display'];
if (!preg_match('/\{value}/', $display)) {
$errors['configdata[display]'] = get_string('displayvalueconfigerror', 'customfield_number');
}
// Each of these configuration fields are optional.
$defaultvalue = $data['configdata']['defaultvalue'] ?? '';
$minimumvalue = $data['configdata']['minimumvalue'] ?? '';
@ -103,4 +134,29 @@ class field_controller extends \core_customfield\field_controller {
return $errors;
}
/**
* Prepares a value for export
*
* @param mixed $value
* @param context|null $context
* @return string|null
*/
public function prepare_field_for_display(mixed $value, ?context $context = null): ?string {
if ((float)$value == 0) {
$value = $this->get_configdata_property('displaywhenzero');
if ((string) $value === '') {
return null;
}
} else {
// Let's format the value.
$decimalplaces = (int) $this->get_configdata_property('decimalplaces');
$value = format_float((float) $value, $decimalplaces);
// Apply the display format.
$format = $this->get_configdata_property('display');
$value = str_replace('{value}', $value, $format);
}
return format_string($value, true, ['context' => $context ?? system::instance()]);
}
}

View File

@ -26,6 +26,18 @@ defined('MOODLE_INTERNAL') || die;
$string['decimalplaces'] = 'Decimal places';
$string['defaultvalueconfigerror'] = 'Default value must be between minimum and maximum';
$string['display'] = 'Display template';
$string['display_help'] = 'How to display the value of the field. Use the following placeholders:
* **{value}** - display value in a general format (float with decimals configured in the field)
* **$ {value}** - price in dollars
* **{value} hrs** - duration in hours';
$string['displayvalueconfigerror'] = 'The placeholder is not invalid';
$string['displaywhenzero'] = 'Display when zero';
$string['displaywhenzero_help'] = 'How to display the field value when the value is "0". For example, in case of a price you can display the word "Free" but in case of the duration you may want to leave it empty since it means that the duration was not estimated.
Leave empty if you do not want to display anything at all when the value is set to "0".';
$string['headerdisplaysettings'] = 'Display format';
$string['maximumvalue'] = 'Maximum value';
$string['maximumvalueerror'] = 'Value must be less than or equal to {$a}';
$string['minimumvalue'] = 'Minimum value';

View File

@ -0,0 +1,93 @@
@customfield @customfield_number @javascript
Feature: Managers can manage course custom fields number
In order to have additional data on the course
As a manager
I need to create, edit, remove and display number custom fields
Background:
Given the following "custom field categories" exist:
| name | component | area | itemid |
| Category for test | core_course | course | 0 |
And I log in as "admin"
And I navigate to "Courses > Default settings > Course custom fields" in site administration
Scenario: Create a custom course number field
When I click on "Add a new custom field" "link"
And I click on "Number" "link"
When I set the following fields to these values:
| Name | Number field |
| Short name | numberfield |
| Display template | test |
And I click on "Save changes" "button" in the "Adding a new Number" "dialogue"
Then I should see "The placeholder is not invalid"
And I set the following fields to these values:
| Name | Number field |
| Short name | numberfield |
| Display template | {value} |
And I click on "Save changes" "button" in the "Adding a new Number" "dialogue"
And I should see "Number field"
And I log out
Scenario: Edit a custom course number field
When I click on "Add a new custom field" "link"
And I click on "Number" "link"
And I set the following fields to these values:
| Name | Number field |
| Short name | numberfield |
And I click on "Save changes" "button" in the "Adding a new Number" "dialogue"
Then I should see "Number field"
And I click on "Edit" "link" in the "Number field" "table_row"
And I set the following fields to these values:
| Name | Edited number field |
And I click on "Save changes" "button" in the "Updating Number field" "dialogue"
Then I should see "Edited number field"
And I log out
Scenario: Delete a custom course number field
When I click on "Add a new custom field" "link"
And I click on "Number" "link"
And I set the following fields to these values:
| Name | Number field |
| Short name | numberfield |
And I click on "Save changes" "button" in the "Adding a new Number" "dialogue"
And I click on "Delete" "link" in the "Number field" "table_row"
And I click on "Yes" "button" in the "Confirm" "dialogue"
And I wait until the page is ready
And I wait until "Number field" "text" does not exist
Then I should not see "Number field"
And I log out
Scenario Outline: A number field must shown correctly on course listing
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Example 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And I navigate to "Courses > Default settings > Course custom fields" in site administration
And I click on "Add a new custom field" "link"
And I click on "Number" "link"
When I set the following fields to these values:
| Name | Test number |
| Short name | testnumber |
| Decimal places | 2 |
| Display template | <template> |
| Display when zero | <whenzero> |
And I click on "Save changes" "button" in the "Adding a new Number" "dialogue"
And I log out
Then I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Test number | <fieldvalue> |
And I press "Save and display"
And I am on site homepage
And I should see "Test number" in the ".customfields-container .customfieldname" "css_element"
And I should see "<expectedvalue>" in the ".customfields-container .customfieldvalue" "css_element"
Examples:
| template | whenzero | fieldvalue | expectedvalue |
| $ {value} | 0 | 150 | $ 150.00 |
| {value} | Free | 0 | Free |

View File

@ -121,22 +121,66 @@ final class data_controller_test extends advanced_testcase {
}
/**
* Test exporting instance
* Data provider for {@see test_export_value}
*
* @return array[]
*/
public function test_export_value(): void {
public static function export_value_provider(): array {
$template = '<span class="multilang" lang="en">$ {value}</span><span class="multilang" lang="es">€ {value}</span>';
$whenzero = '<span class="multilang" lang="en">Unknown</span><span class="multilang" lang="es">Desconocido</span>';
return [
'Export float value' => [42, 42.0, [
'decimalplaces' => 2,
'display' => '{value}',
'displaywhenzero' => 0],
],
'Export value with a prefix' => [10, '$ 10.00', [
'decimalplaces' => 2,
'display' => $template,
'displaywhenzero' => 0],
],
'Export value when zero' => [0, 'Unknown', [
'display' => '{value}',
'displaywhenzero' => $whenzero],
],
];
}
/**
* Test exporting instance
*
* @param float|string $datavalue
* @param float|string $expectedvalue
* @param array $configdata
*
* @dataProvider export_value_provider
*/
public function test_export_value(
float|string $datavalue,
float|string $expectedvalue,
array $configdata,
): void {
$this->resetAfterTest();
$this->setAdminUser();
// Enable multilang filter.
filter_set_global_state('multilang', TEXTFILTER_ON);
filter_set_applies_to_strings('multilang', true);
$course = $this->getDataGenerator()->create_course();
/** @var core_customfield_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
$category = $generator->create_category();
$field = $generator->create_field(['categoryid' => $category->get('id'), 'type' => 'number']);
$data = $generator->add_instance_data($field, (int) $course->id, 42);
$field = $generator->create_field([
'categoryid' => $category->get('id'),
'type' => 'number',
'configdata' => $configdata,
]);
$data = $generator->add_instance_data($field, (int) $course->id, $datavalue);
$result = \core_customfield\data_controller::create($data->get('id'))->export_value();
$this->assertEquals(42.0, $result);
$this->assertEquals($expectedvalue, $result);
}
}

View File

@ -55,11 +55,13 @@ final class field_controller_test extends advanced_testcase {
*/
public static function form_definition_provider(): array {
return [
'Defaults' => ['', '', '', true],
'Minimum greater than maximum' => ['', 12, 10, false],
'Default value less than minimum' => [1, 10, 12, false],
'Default value greater than maximum' => [13, 10, 12, false],
'Valid' => [11, 10, 12, true],
'Defaults' => ['', '', '', '{value}', true],
'Minimum greater than maximum' => ['', 12, 10, '{value}', false],
'Default value less than minimum' => [1, 10, 12, '{value}', false],
'Default value greater than maximum' => [13, 10, 12, '{value}', false],
'Valid' => [11, 10, 12, '{value}', true],
'Display valid single placeholder' => ['', '', '', '{value}', true],
'Display invalid single placeholder' => ['', '', '', '111', false],
];
}
@ -69,6 +71,7 @@ final class field_controller_test extends advanced_testcase {
* @param float|string $defaultvalue
* @param float|string $minimumvalue
* @param float|string $maximumvalue
* @param string $display
* @param bool $expected
*
* @dataProvider form_definition_provider
@ -77,6 +80,7 @@ final class field_controller_test extends advanced_testcase {
float|string $defaultvalue,
float|string $minimumvalue,
float|string $maximumvalue,
string $display,
bool $expected,
): void {
$this->resetAfterTest();
@ -93,6 +97,7 @@ final class field_controller_test extends advanced_testcase {
'defaultvalue' => $defaultvalue,
'minimumvalue' => $minimumvalue,
'maximumvalue' => $maximumvalue,
'display' => $display,
]);
$formdata = field_config_form::mock_ajax_submit($submitdata);