diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php
index a73238ff7be..d18579e95f2 100644
--- a/course/tests/externallib_test.php
+++ b/course/tests/externallib_test.php
@@ -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'] = '$ {value}'
+ . '€ {value}';
+ $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']);
}
/**
diff --git a/customfield/field/number/classes/data_controller.php b/customfield/field/number/classes/data_controller.php
index 52be04a3007..16d2bb0bd95 100644
--- a/customfield/field/number/classes/data_controller.php
+++ b/customfield/field/number/classes/data_controller.php
@@ -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());
}
}
diff --git a/customfield/field/number/classes/field_controller.php b/customfield/field/number/classes/field_controller.php
index 0bd0413b593..c282f725eaf 100644
--- a/customfield/field/number/classes/field_controller.php
+++ b/customfield/field/number/classes/field_controller.php
@@ -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()]);
+ }
}
diff --git a/customfield/field/number/lang/en/customfield_number.php b/customfield/field/number/lang/en/customfield_number.php
index aff194f18c8..dec2ae0cd0c 100644
--- a/customfield/field/number/lang/en/customfield_number.php
+++ b/customfield/field/number/lang/en/customfield_number.php
@@ -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';
diff --git a/customfield/field/number/tests/behat/field.feature b/customfield/field/number/tests/behat/field.feature
new file mode 100644
index 00000000000..37576b41d65
--- /dev/null
+++ b/customfield/field/number/tests/behat/field.feature
@@ -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 | |
+ | Display when zero | |
+ 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 | |
+ 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 "" in the ".customfields-container .customfieldvalue" "css_element"
+ Examples:
+ | template | whenzero | fieldvalue | expectedvalue |
+ | $ {value} | 0 | 150 | $ 150.00 |
+ | {value} | Free | 0 | Free |
diff --git a/customfield/field/number/tests/data_controller_test.php b/customfield/field/number/tests/data_controller_test.php
index 5026bc0b81b..45648ec2b5e 100644
--- a/customfield/field/number/tests/data_controller_test.php
+++ b/customfield/field/number/tests/data_controller_test.php
@@ -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 = '$ {value}€ {value}';
+ $whenzero = 'UnknownDesconocido';
+ 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);
}
}
diff --git a/customfield/field/number/tests/field_controller_test.php b/customfield/field/number/tests/field_controller_test.php
index 1787d8f8383..632dd147df5 100644
--- a/customfield/field/number/tests/field_controller_test.php
+++ b/customfield/field/number/tests/field_controller_test.php
@@ -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);