MDL-57898 core_customfield: Custom fields API
authorDaniel Neis Araujo <danielneis@gmail.com>
Fri, 11 Jan 2019 10:38:26 +0000 (11:38 +0100)
committerMarina Glancy <marina@moodle.com>
Fri, 18 Jan 2019 13:28:17 +0000 (14:28 +0100)
This commit is part of work on Custom fields API,
to minimize commit history in moodle core the work of a team of developers was split
into several commits with different authors but the authorship of individual
lines of code may be different from the commit author.

35 files changed:
admin/customfields.php [new file with mode: 0644]
admin/settings/plugins.php
customfield/classes/event/category_created.php [new file with mode: 0644]
customfield/classes/event/category_updated.php [new file with mode: 0644]
customfield/classes/event/field_created.php [new file with mode: 0644]
customfield/classes/event/field_updated.php [new file with mode: 0644]
customfield/classes/output/field_data.php [new file with mode: 0644]
customfield/field/checkbox/classes/data_controller.php [new file with mode: 0644]
customfield/field/checkbox/classes/privacy/provider.php [new file with mode: 0644]
customfield/field/checkbox/tests/behat/field.feature [new file with mode: 0644]
customfield/field/date/classes/data_controller.php [new file with mode: 0644]
customfield/field/date/classes/privacy/provider.php [new file with mode: 0644]
customfield/field/date/lib.php [new file with mode: 0644]
customfield/field/date/tests/behat/field.feature [new file with mode: 0644]
customfield/field/select/classes/data_controller.php [new file with mode: 0644]
customfield/field/select/classes/privacy/provider.php [new file with mode: 0644]
customfield/field/select/tests/behat/field.feature [new file with mode: 0644]
customfield/field/text/classes/data_controller.php [new file with mode: 0644]
customfield/field/text/classes/privacy/provider.php [new file with mode: 0644]
customfield/field/text/tests/behat/field.feature [new file with mode: 0644]
customfield/field/textarea/classes/data_controller.php [new file with mode: 0644]
customfield/field/textarea/classes/privacy/provider.php [new file with mode: 0644]
customfield/field/textarea/tests/behat/default_value.feature [new file with mode: 0644]
customfield/field/textarea/tests/behat/field.feature [new file with mode: 0644]
customfield/templates/field_data.mustache [new file with mode: 0644]
customfield/tests/behat/edit_categories.feature [new file with mode: 0644]
customfield/tests/behat/edit_fields_settings.feature [new file with mode: 0644]
customfield/tests/behat/required_field.feature [new file with mode: 0644]
customfield/tests/behat/unique_field.feature [new file with mode: 0644]
lang/en/plugin.php
lib/adminlib.php
lib/classes/component.php
lib/classes/plugin_manager.php
lib/testing/generator/data_generator.php
lib/tests/behat/behat_data_generators.php

diff --git a/admin/customfields.php b/admin/customfields.php
new file mode 100644 (file)
index 0000000..2f7681e
--- /dev/null
@@ -0,0 +1,62 @@
+<?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/>.
+
+/**
+ * Allows the admin to enable, disable and uninstall custom fields
+ *
+ * @package    core_admin
+ * @copyright  2018 Daniel Neis Araujo
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+$action  = required_param('action', PARAM_ALPHANUMEXT);
+$customfieldname = required_param('field', PARAM_PLUGIN);
+
+$syscontext = context_system::instance();
+$PAGE->set_url('/admin/customfields.php');
+$PAGE->set_context($syscontext);
+
+require_login();
+require_capability('moodle/site:config', $syscontext);
+require_sesskey();
+
+$return = new moodle_url('/admin/settings.php', array('section' => 'managecustomfields'));
+
+$customfieldplugins = core_plugin_manager::instance()->get_plugins_of_type('customfield');
+$sortorder = array_flip(array_keys($customfieldplugins));
+
+if (!isset($customfieldplugins[$customfieldname])) {
+    print_error('customfieldnotfound', 'error', $return, $customfieldname);
+}
+
+switch ($action) {
+    case 'disable':
+        if ($customfieldplugins[$customfieldname]->is_enabled()) {
+            set_config('disabled', 1, 'customfield_'. $customfieldname);
+            core_plugin_manager::reset_caches();
+        }
+        break;
+    case 'enable':
+        if (!$customfieldplugins[$customfieldname]->is_enabled()) {
+            unset_config('disabled', 'customfield_'. $customfieldname);
+            core_plugin_manager::reset_caches();
+        }
+        break;
+}
+redirect($return);
index 9a2202f..72aa6ba 100644 (file)
@@ -59,6 +59,18 @@ if ($hassiteconfig) {
         $plugin->load_settings($ADMIN, 'formatsettings', $hassiteconfig);
     }
 
+    // Custom fields.
+    $ADMIN->add('modules', new admin_category('customfieldsettings', new lang_string('customfields', 'core_customfield')));
+    $temp = new admin_settingpage('managecustomfields', new lang_string('managecustomfields', 'core_admin'));
+    $temp->add(new admin_setting_managecustomfields());
+    $ADMIN->add('customfieldsettings', $temp);
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('customfield');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
+        /** @var \core\plugininfo\customfield $plugin */
+        $plugin->load_settings($ADMIN, 'customfieldsettings', $hassiteconfig);
+    }
+
     // blocks
     $ADMIN->add('modules', new admin_category('blocksettings', new lang_string('blocks')));
     $ADMIN->add('blocksettings', new admin_page_manageblocks());
diff --git a/customfield/classes/event/category_created.php b/customfield/classes/event/category_created.php
new file mode 100644 (file)
index 0000000..b5234e0
--- /dev/null
@@ -0,0 +1,84 @@
+<?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/>.
+
+/**
+ * Custom field category created event.
+ *
+ * @package    core_customfield
+ * @copyright  2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_customfield\event;
+
+use core_customfield\category_controller;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Custom field category created event class.
+ *
+ * @package    core_customfield
+ * @since      Moodle 3.6
+ * @copyright  2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class category_created extends \core\event\base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'customfield_category';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Creates an instance from a category controller object
+     *
+     * @param category_controller $category
+     * @return category_created
+     */
+    public static function create_from_object(category_controller $category): category_created {
+        $eventparams = [
+            'objectid' => $category->get('id'),
+            'context'  => $category->get_handler()->get_configuration_context(),
+            'other'    => ['name' => $category->get('name')]
+        ];
+        $event = self::create($eventparams);
+        $event->add_record_snapshot($event->objecttable, $category->to_record());
+        return $event;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcategorycreated', 'core_customfield');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' created the category with id '$this->objectid'.";
+    }
+}
diff --git a/customfield/classes/event/category_updated.php b/customfield/classes/event/category_updated.php
new file mode 100644 (file)
index 0000000..2006c12
--- /dev/null
@@ -0,0 +1,84 @@
+<?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/>.
+
+/**
+ * Custom field category updated event.
+ *
+ * @package    core_customfield
+ * @copyright  2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_customfield\event;
+
+use core_customfield\category_controller;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Custom field category updated event class.
+ *
+ * @package    core_customfield
+ * @since      Moodle 3.6
+ * @copyright  2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class category_updated extends \core\event\base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'customfield_category';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Creates an instance from a category controller object
+     *
+     * @param category_controller $category
+     * @return category_updated
+     */
+    public static function create_from_object(category_controller $category): category_updated {
+        $eventparams = [
+            'objectid' => $category->get('id'),
+            'context'  => $category->get_handler()->get_configuration_context(),
+            'other'    => ['name' => $category->get('name')]
+        ];
+        $event = self::create($eventparams);
+        $event->add_record_snapshot($event->objecttable, $category->to_record());
+        return $event;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcategoryupdated', 'core_customfield');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' updated the category with id '$this->objectid'.";
+    }
+}
diff --git a/customfield/classes/event/field_created.php b/customfield/classes/event/field_created.php
new file mode 100644 (file)
index 0000000..cdb0167
--- /dev/null
@@ -0,0 +1,87 @@
+<?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/>.
+
+/**
+ * Custom field created event.
+ *
+ * @package    core_customfield
+ * @copyright  2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_customfield\event;
+
+use core_customfield\field_controller;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Custom field created event class.
+ *
+ * @package    core_customfield
+ * @since      Moodle 3.6
+ * @copyright  2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class field_created extends \core\event\base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'customfield_field';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Creates an instance from a field controller object
+     *
+     * @param field_controller $field
+     * @return field_created
+     */
+    public static function create_from_object(field_controller $field): field_created {
+        $eventparams = [
+            'objectid' => $field->get('id'),
+            'context'  => $field->get_handler()->get_configuration_context(),
+            'other'    => [
+                'shortname' => $field->get('shortname'),
+                'name'      => $field->get('name')
+            ]
+        ];
+        $event = self::create($eventparams);
+        $event->add_record_snapshot($event->objecttable, $field->to_record());
+        return $event;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventfieldcreated', 'core_customfield');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' created the field with id '$this->objectid'.";
+    }
+}
diff --git a/customfield/classes/event/field_updated.php b/customfield/classes/event/field_updated.php
new file mode 100644 (file)
index 0000000..02a17f2
--- /dev/null
@@ -0,0 +1,87 @@
+<?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/>.
+
+/**
+ * Custom field updated event.
+ *
+ * @package    core_customfield
+ * @copyright  2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_customfield\event;
+
+use core_customfield\field_controller;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Custom field updated event class.
+ *
+ * @package    core_customfield
+ * @since      Moodle 3.6
+ * @copyright  2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class field_updated extends \core\event\base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'customfield_field';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Creates an instance from a field controller object
+     *
+     * @param field_controller $field
+     * @return field_updated
+     */
+    public static function create_from_object(field_controller $field): field_updated {
+        $eventparams = [
+            'objectid' => $field->get('id'),
+            'context'  => $field->get_handler()->get_configuration_context(),
+            'other'    => [
+                'shortname' => $field->get('shortname'),
+                'name'      => $field->get('name')
+            ]
+        ];
+        $event = self::create($eventparams);
+        $event->add_record_snapshot($event->objecttable, $field->to_record());
+        return $event;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventfieldupdated', 'core_customfield');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' updated the field with id '$this->objectid'.";
+    }
+}
diff --git a/customfield/classes/output/field_data.php b/customfield/classes/output/field_data.php
new file mode 100644 (file)
index 0000000..8f515a9
--- /dev/null
@@ -0,0 +1,114 @@
+<?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/>.
+
+/**
+ *  core_customfield field value renderable.
+ *
+ * @package   core_customfield
+ * @copyright 2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_customfield\output;
+
+use core_customfield\data_controller;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * core_customfield field value renderable class.
+ *
+ * @package   core_customfield
+ * @copyright 2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class field_data implements \renderable, \templatable {
+
+    /** @var \core_customfield\data_controller */
+    protected $data;
+
+    /**
+     * Renderable constructor.
+     *
+     * @param \core_customfield\data_controller $data
+     */
+    public function __construct(\core_customfield\data_controller $data) {
+        $this->data = $data;
+    }
+
+    /**
+     * Returns the data value formatted for the output
+     *
+     * @return mixed|null
+     */
+    public function get_value() {
+        return $this->data->export_value();
+    }
+
+    /**
+     * Returns the field type (checkbox, date, text, ...)
+     *
+     * @return string
+     */
+    public function get_type(): string {
+        return $this->data->get_field()->get('type');
+    }
+
+    /**
+     * Returns the field short name
+     *
+     * @return string
+     */
+    public function get_shortname(): string {
+        return $this->data->get_field()->get('shortname');
+    }
+
+    /**
+     * Returns the field name formatted for the output
+     *
+     * @return string
+     */
+    public function get_name(): string {
+        return $this->data->get_field()->get_formatted_name();
+    }
+
+    /**
+     * Returns the data controller used to create this object if additional attributes are needed
+     *
+     * @return data_controller
+     */
+    public function get_data_controller(): data_controller {
+        return $this->data;
+    }
+
+    /**
+     * Export data for using as template context.
+     *
+     * @param \renderer_base $output
+     * @return \stdClass
+     */
+    public function export_for_template(\renderer_base $output) {
+        $value = $this->get_value();
+        return (object)[
+            'value' => $value,
+            'type' => $this->get_type(),
+            'shortname' => $this->get_shortname(),
+            'name' => $this->get_name(),
+            'hasvalue' => ($value !== null),
+            'instanceid' => $this->data->get('instanceid')
+        ];
+    }
+}
diff --git a/customfield/field/checkbox/classes/data_controller.php b/customfield/field/checkbox/classes/data_controller.php
new file mode 100644 (file)
index 0000000..be13469
--- /dev/null
@@ -0,0 +1,84 @@
+<?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/>.
+
+/**
+ * Customfield Checkbox plugin
+ *
+ * @package   customfield_checkbox
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace customfield_checkbox;
+
+use core_customfield\api;
+use core_customfield\output\field_data;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Class data
+ *
+ * @package customfield_checkbox
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class data_controller extends \core_customfield\data_controller {
+
+    /**
+     * Return the name of the field where the information is stored
+     * @return string
+     */
+    public function datafield() : string {
+        return 'intvalue';
+    }
+
+    /**
+     * Add fields for editing a checkbox field.
+     *
+     * @param \MoodleQuickForm $mform
+     */
+    public function instance_form_definition(\MoodleQuickForm $mform) {
+        $field = $this->get_field();
+        $config = $field->get('configdata');
+        $elementname = $this->get_form_element_name();
+        $mform->addElement('advcheckbox', $elementname, $this->get_field()->get_formatted_name());
+        $mform->setDefault($elementname, $config['checkbydefault']);
+        $mform->setType($elementname, PARAM_BOOL);
+        if ($field->get_configdata_property('required')) {
+            $mform->addRule($elementname, null, 'required', null, 'client');
+        }
+    }
+
+    /**
+     * Returns the default value as it would be stored in the database (not in human-readable format).
+     *
+     * @return mixed
+     */
+    public function get_default_value() {
+        return $this->get_field()->get_configdata_property('checkbydefault') ? 1 : 0;
+    }
+
+    /**
+     * Returns value in a human-readable format
+     *
+     * @return mixed|null value or null if empty
+     */
+    public function export_value() {
+        $value = $this->get_value();
+        return $value ? get_string('yes') : get_string('no');
+    }
+}
diff --git a/customfield/field/checkbox/classes/privacy/provider.php b/customfield/field/checkbox/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..f892009
--- /dev/null
@@ -0,0 +1,81 @@
+<?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/>.
+
+/**
+ * Privacy Subsystem implementation for customfield_checkbox.
+ *
+ * @package    customfield_checkbox
+ * @copyright  2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace customfield_checkbox\privacy;
+
+use core_customfield\data_controller;
+use core_customfield\privacy\customfield_provider;
+use core_privacy\local\request\writer;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for customfield_checkbox implementing null_provider.
+ *
+ * @copyright  2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider, customfield_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+
+    /**
+     * Preprocesses data object that is going to be exported
+     *
+     * @param data_controller $data
+     * @param \stdClass $exportdata
+     * @param array $subcontext
+     */
+    public static function export_customfield_data(data_controller $data, \stdClass $exportdata, array $subcontext) {
+        writer::with_context($data->get_context())->export_data($subcontext, $exportdata);
+    }
+
+    /**
+     * Allows plugins to delete everything they store related to the data (usually files)
+     *
+     * @param string $dataidstest
+     * @param array $params
+     * @param array $contextids
+     * @return mixed|void
+     */
+    public static function before_delete_data(string $dataidstest, array $params, array $contextids) {
+    }
+
+    /**
+     * Allows plugins to delete everything they store related to the field configuration (usually files)
+     *
+     * @param string $fieldidstest
+     * @param array $params
+     * @param array $contextids
+     */
+    public static function before_delete_fields(string $fieldidstest, array $params, array $contextids) {
+    }
+}
diff --git a/customfield/field/checkbox/tests/behat/field.feature b/customfield/field/checkbox/tests/behat/field.feature
new file mode 100644 (file)
index 0000000..e26a2fe
--- /dev/null
@@ -0,0 +1,81 @@
+@customfield @customfield_checkbox
+Feature: Managers can manage course custom fields checkbox
+  In order to have additional data on the course
+  As a manager
+  I need to create, edit, remove and sort 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 > Course custom fields" in site administration
+
+  Scenario: Create a custom course checkbox field
+    When I click on "Add a new custom field" "link"
+    And I click on "Checkbox" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    Then I should see "Test field"
+    And I log out
+
+  Scenario: Edit a custom course checkbox field
+    When I click on "Add a new custom field" "link"
+    And I click on "Checkbox" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    And I click on "Edit" "link" in the "Test field" "table_row"
+    And I set the following fields to these values:
+      | Name | Edited field |
+    And I press "Save changes"
+    Then I should see "Edited field"
+    And I should not see "Test field"
+    And I log out
+
+  @javascript
+  Scenario: Delete a custom course checkbox field
+    When I click on "Add a new custom field" "link"
+    And I click on "Checkbox" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    And I click on "Delete" "link" in the "Test field" "table_row"
+    And I click on "Yes" "button" in the "Confirm" "dialogue"
+    Then I should not see "Test field"
+    And I log out
+
+  @javascript
+  Scenario: A checkbox checked by default must be shown on listing but allow uncheck that will keep showing
+    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 |
+    When I click on "Add a new custom field" "link"
+    And I click on "Checkbox" "link"
+    And I set the following fields to these values:
+      | Name               | Test field |
+      | Short name         | testfield  |
+      | Checked by default | Yes        |
+    And I press "Save changes"
+    And I log out
+    And I log in as "teacher1"
+    And I am on site homepage
+    Then I should see "Test field: Yes"
+    When I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I expand all fieldsets
+    And I set the field "Test field" to ""
+    And I press "Save and display"
+    And I am on site homepage
+    Then I should see "Test field: No"
+    And I log out
diff --git a/customfield/field/date/classes/data_controller.php b/customfield/field/date/classes/data_controller.php
new file mode 100644 (file)
index 0000000..3419d25
--- /dev/null
@@ -0,0 +1,147 @@
+<?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/>.
+
+/**
+ * Customfield date plugin
+ *
+ * @package   customfield_date
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace customfield_date;
+
+use core_customfield\api;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Class data
+ *
+ * @package customfield_date
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class data_controller extends \core_customfield\data_controller {
+
+    /**
+     * Return the name of the field where the information is stored
+     * @return string
+     */
+    public function datafield() : string {
+        return 'intvalue';
+    }
+
+    /**
+     * Add fields for editing data of a date field on a context.
+     *
+     * @param \MoodleQuickForm $mform
+     */
+    public function instance_form_definition(\MoodleQuickForm $mform) {
+        $field = $this->get_field();
+        // Get the current calendar in use - see MDL-18375.
+        $calendartype = \core_calendar\type_factory::get_calendar_instance();
+
+        $config = $field->get('configdata');
+
+        // Always set the form element to "optional", even when it's required. Otherwise it defaults to the
+        // current date and is easy to miss.
+        $attributes = ['optional' => true];
+
+        if (!empty($config['mindate'])) {
+            $attributes['startyear'] = $calendartype->timestamp_to_date_array($config['mindate'])['year'];
+        }
+
+        if (!empty($config['maxdate'])) {
+            $attributes['stopyear'] = $calendartype->timestamp_to_date_array($config['maxdate'])['year'];
+        }
+
+        if (empty($config['includetime'])) {
+            $element = 'date_selector';
+        } else {
+            $element = 'date_time_selector';
+        }
+        $elementname = $this->get_form_element_name();
+        $mform->addElement($element, $elementname, $this->get_field()->get_formatted_name(), $attributes);
+        $mform->setType($elementname, PARAM_INT);
+        $mform->setDefault($elementname, time());
+        if ($field->get_configdata_property('required')) {
+            $mform->addRule($elementname, null, 'required', null, 'client');
+        }
+    }
+
+    /**
+     * Validates data for this field.
+     *
+     * @param array $data
+     * @param array $files
+     * @return array
+     */
+    public function instance_form_validation(array $data, array $files): array {
+        $errors = parent::instance_form_validation($data, $files);
+
+        $elementname = $this->get_form_element_name();
+        if (!empty($data[$elementname])) {
+            // Compare the date with min/max values, trim the date to the minute or to the day (depending on inludetime setting).
+            $includetime = $this->get_field()->get_configdata_property('includetime');
+            $machineformat = $includetime ? '%Y-%m-%d %H:%M' : '%Y-%m-%d';
+            $humanformat = $includetime ? get_string('strftimedatetimeshort') : get_string('strftimedatefullshort');
+            $value = userdate($data[$elementname], $machineformat, 99, false, false);
+            $mindate = $this->get_field()->get_configdata_property('mindate');
+            $maxdate = $this->get_field()->get_configdata_property('maxdate');
+
+            if ($mindate && userdate($mindate, $machineformat, 99, false, false) > $value) {
+                $errors[$elementname] = get_string('errormindate', 'customfield_date', userdate($mindate, $humanformat));
+            }
+            if ($maxdate && userdate($maxdate, $machineformat, 99, false, false) < $value) {
+                $errors[$elementname] = get_string('errormaxdate', 'customfield_date', userdate($maxdate, $humanformat));
+            }
+        }
+
+        return $errors;
+    }
+
+    /**
+     * Returns the default value as it would be stored in the database (not in human-readable format).
+     *
+     * @return mixed
+     */
+    public function get_default_value() {
+        return 0;
+    }
+
+    /**
+     * Returns value in a human-readable format
+     *
+     * @return mixed|null value or null if empty
+     */
+    public function export_value() {
+        $value = $this->get_value();
+
+        if ($this->is_empty($value)) {
+            return null;
+        }
+
+        // Check if time needs to be included.
+        if ($this->get_field()->get_configdata_property('includetime')) {
+            $format = get_string('strftimedaydatetime', 'langconfig');
+        } else {
+            $format = get_string('strftimedate', 'langconfig');
+        }
+
+        return userdate($value, $format);
+    }
+}
diff --git a/customfield/field/date/classes/privacy/provider.php b/customfield/field/date/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..b9605a5
--- /dev/null
@@ -0,0 +1,85 @@
+<?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/>.
+/**
+ * Privacy Subsystem implementation for customfield_date.
+ *
+ * @package    customfield_date
+ * @copyright  2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace customfield_date\privacy;
+
+use core_customfield\data_controller;
+use core_customfield\privacy\customfield_provider;
+use core_privacy\local\request\writer;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for customfield_date implementing null_provider.
+ *
+ * @copyright  2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider, customfield_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason(): string {
+        return 'privacy:metadata';
+    }
+
+    /**
+     * Preprocesses data object that is going to be exported
+     *
+     * @param data_controller $data
+     * @param \stdClass $exportdata
+     * @param array $subcontext
+     */
+    public static function export_customfield_data(data_controller $data, \stdClass $exportdata, array $subcontext) {
+        $context = $data->get_context();
+        // For date field we want to use PrivacyAPI date format instead of export_value().
+        $exportdata->value = \core_privacy\local\request\transform::datetime($data->get_value());
+        writer::with_context($context)
+            ->export_data($subcontext, $exportdata);
+    }
+
+    /**
+     * Allows plugins to delete everything they store related to the data (usually files)
+     *
+     * @param string $dataidstest
+     * @param array $params
+     * @param array $contextids
+     * @return mixed|void
+     */
+    public static function before_delete_data(string $dataidstest, array $params, array $contextids) {
+    }
+
+    /**
+     * Allows plugins to delete everything they store related to the field configuration (usually files)
+     *
+     * @param string $fieldidstest
+     * @param array $params
+     * @param array $contextids
+     */
+    public static function before_delete_fields(string $fieldidstest, array $params, array $contextids) {
+    }
+}
diff --git a/customfield/field/date/lib.php b/customfield/field/date/lib.php
new file mode 100644 (file)
index 0000000..00a7d3c
--- /dev/null
@@ -0,0 +1,35 @@
+<?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/>.
+
+/**
+ * Customfield date plugin
+ *
+ * @package   customfield_date
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Get icon mapping for font-awesome.
+ */
+function customfield_date_get_fontawesome_icon_map() {
+    return [
+        'customfield_date:checked' => 'fa-check-square-o',
+        'customfield_date:notchecked' => 'fa-square-o',
+    ];
+}
diff --git a/customfield/field/date/tests/behat/field.feature b/customfield/field/date/tests/behat/field.feature
new file mode 100644 (file)
index 0000000..a897294
--- /dev/null
@@ -0,0 +1,105 @@
+@customfield @customfield_date
+Feature: Managers can manage course custom fields date
+  In order to have additional data on the course
+  As a manager
+  I need to create, edit, remove and sort 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 > Course custom fields" in site administration
+
+  Scenario: Create a custom course date field
+    When I click on "Add a new custom field" "link"
+    And I click on "Date and time" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    Then I should see "Test field"
+    And I log out
+
+  Scenario: Edit a custom course date field
+    When I click on "Add a new custom field" "link"
+    And I click on "Date and time" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    And I click on "[data-role='editfield']" "css_element"
+    And I set the following fields to these values:
+      | Name | Edited field |
+    And I press "Save changes"
+    Then I should see "Edited field"
+    And I log out
+
+  @javascript
+  Scenario: Delete a custom course date field
+    When I click on "Add a new custom field" "link"
+    And I click on "Date and time" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    And I click on "[data-role='deletefield']" "css_element"
+    And I click on "Yes" "button" in the "Confirm" "dialogue"
+    Then I should not see "Test field"
+    And I log out
+
+  @javascript
+  Scenario: A date field makerd to include time must show those fields on course form
+    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 |
+    When I click on "Add a new custom field" "link"
+    And I click on "Date and time" "link"
+    And I set the following fields to these values:
+      | Name         | Test field |
+      | Short name   | testfield  |
+      | Include time | 1          |
+    And I press "Save changes"
+    And I log out
+    Then I log in as "teacher1"
+    When I am on site homepage
+    When I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I expand all fieldsets
+    Then "#id_customfield_testfield_hour" "css_element" should be visible
+    Then "#id_customfield_testfield_minute" "css_element" should be visible
+    And I log out
+
+  @javascript
+  Scenario: A date field makerd to not include time must not show those fields on course form
+    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 |
+    When I click on "Add a new custom field" "link"
+    And I click on "Date and time" "link"
+    And I set the following fields to these values:
+      | Name         | Test field |
+      | Short name   | testfield  |
+      | Include time |            |
+    And I press "Save changes"
+    And I log out
+    Then I log in as "teacher1"
+    When I am on site homepage
+    When I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I expand all fieldsets
+    Then "#id_customfield_testfield_hour" "css_element" should not be visible
+    Then "#id_customfield_testfield_minute" "css_element" should not be visible
+    And I log out
\ No newline at end of file
diff --git a/customfield/field/select/classes/data_controller.php b/customfield/field/select/classes/data_controller.php
new file mode 100644 (file)
index 0000000..4280429
--- /dev/null
@@ -0,0 +1,131 @@
+<?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/>.
+
+/**
+ * Select plugin data controller
+ *
+ * @package   customfield_select
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace customfield_select;
+
+use core_customfield\api;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Class data
+ *
+ * @package customfield_select
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class data_controller extends \core_customfield\data_controller {
+
+    /**
+     * Return the name of the field where the information is stored
+     * @return string
+     */
+    public function datafield() : string {
+        return 'intvalue';
+    }
+
+    /**
+     * Returns the default value as it would be stored in the database (not in human-readable format).
+     *
+     * @return mixed
+     */
+    public function get_default_value() {
+        $defaultvalue = $this->get_field()->get_configdata_property('defaultvalue');
+        if ('' . $defaultvalue !== '') {
+            $options = field_controller::get_options_array($this->get_field());
+            $key = array_search($defaultvalue, $options);
+            if ($key !== false) {
+                return $key;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Add fields for editing a textarea field.
+     *
+     * @param \MoodleQuickForm $mform
+     */
+    public function instance_form_definition(\MoodleQuickForm $mform) {
+        $field = $this->get_field();
+        $config = $field->get('configdata');
+        $options = field_controller::get_options_array($field);
+        $formattedoptions = array();
+        $context = $this->get_field()->get_handler()->get_configuration_context();
+        foreach ($options as $key => $option) {
+            // Multilang formatting with filters.
+            $formattedoptions[$key] = format_string($option, true, ['context' => $context]);
+        }
+
+        $elementname = $this->get_form_element_name();
+        $mform->addElement('select', $elementname, $this->get_field()->get_formatted_name(), $formattedoptions);
+
+        if (($defaultkey = array_search($config['defaultvalue'], $options)) !== false) {
+            $mform->setDefault($elementname, $defaultkey);
+        }
+        if ($field->get_configdata_property('required')) {
+            $mform->addRule($elementname, null, 'required', null, 'client');
+        }
+    }
+
+    /**
+     * Validates data for this field.
+     *
+     * @param array $data
+     * @param array $files
+     * @return array
+     */
+    public function instance_form_validation(array $data, array $files): array {
+        $errors = parent::instance_form_validation($data, $files);
+        if ($this->get_field()->get_configdata_property('required')) {
+            // Standard required rule does not work on select element.
+            $elementname = $this->get_form_element_name();
+            if (empty($data[$elementname])) {
+                $errors[$elementname] = get_string('err_required', 'form');
+            }
+        }
+        return $errors;
+    }
+
+    /**
+     * Returns value in a human-readable format
+     *
+     * @return mixed|null value or null if empty
+     */
+    public function export_value() {
+        $value = $this->get_value();
+
+        if ($this->is_empty($value)) {
+            return null;
+        }
+
+        $options = field_controller::get_options_array($this->get_field());
+        if (array_key_exists($value, $options)) {
+            return format_string($options[$value], true,
+                ['context' => $this->get_field()->get_handler()->get_configuration_context()]);
+        }
+
+        return null;
+    }
+}
diff --git a/customfield/field/select/classes/privacy/provider.php b/customfield/field/select/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..e1c5547
--- /dev/null
@@ -0,0 +1,83 @@
+<?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/>.
+/**
+ * Privacy Subsystem implementation for customfield_select.
+ *
+ * @package    customfield_select
+ * @copyright  2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace customfield_select\privacy;
+
+use core_customfield\data_controller;
+use core_customfield\privacy\customfield_provider;
+use core_privacy\local\request\writer;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for customfield_select implementing null_provider.
+ *
+ * @copyright  2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider, customfield_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+
+    /**
+     * Preprocesses data object that is going to be exported
+     *
+     * @param data_controller $data
+     * @param \stdClass $exportdata
+     * @param array $subcontext
+     */
+    public static function export_customfield_data(data_controller $data, \stdClass $exportdata, array $subcontext) {
+        $context = $data->get_context();
+        $exportdata->value = $data->export_value();
+        writer::with_context($context)
+            ->export_data($subcontext, $exportdata);
+    }
+
+    /**
+     * Allows plugins to delete everything they store related to the data (usually files)
+     *
+     * @param string $dataidstest
+     * @param array $params
+     * @param array $contextids
+     * @return mixed|void
+     */
+    public static function before_delete_data(string $dataidstest, array $params, array $contextids) {
+    }
+
+    /**
+     * Allows plugins to delete everything they store related to the field configuration (usually files)
+     *
+     * @param string $fieldidstest
+     * @param array $params
+     * @param array $contextids
+     */
+    public static function before_delete_fields(string $fieldidstest, array $params, array $contextids) {
+    }
+}
diff --git a/customfield/field/select/tests/behat/field.feature b/customfield/field/select/tests/behat/field.feature
new file mode 100644 (file)
index 0000000..6c43de1
--- /dev/null
@@ -0,0 +1,86 @@
+@customfield @customfield_select
+Feature: Managers can manage course custom fields select
+  In order to have additional data on the course
+  As a manager
+  I need to create, edit, remove and sort 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 > Course custom fields" in site administration
+
+  Scenario: Create a custom course select field
+    When I click on "Add a new custom field" "link"
+    And I click on "Dropdown menu" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I set the field "Menu options (one per line)" to multiline:
+    """
+    a
+    b
+    """
+    And I press "Save changes"
+    Then I should see "Test field"
+    And I log out
+
+  Scenario: Edit a custom course select field
+    When I click on "Add a new custom field" "link"
+    And I click on "Dropdown menu" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I set the field "Menu options (one per line)" to multiline:
+    """
+    a
+    b
+    """
+    And I press "Save changes"
+    And I click on "Edit" "link" in the "Test field" "table_row"
+    And I set the following fields to these values:
+      | Name | Edited field |
+    And I press "Save changes"
+    Then I should see "Edited field"
+    And I should not see "Test field"
+    And I log out
+
+  @javascript
+  Scenario: Delete a custom course select field
+    When I click on "Add a new custom field" "link"
+    And I click on "Dropdown menu" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I set the field "Menu options (one per line)" to multiline:
+    """
+    a
+    b
+    """
+    And I press "Save changes"
+    And I click on "Delete" "link" in the "Test field" "table_row"
+    And I click on "Yes" "button" in the "Confirm" "dialogue"
+    Then I should not see "Test field"
+    And I log out
+
+  Scenario: Validation of custom course select field configuration
+    When I click on "Add a new custom field" "link"
+    And I click on "Dropdown menu" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    And I should see "Please provide at least two options separated with a newline" in the "Menu options (one per line)" "form_row"
+    And I set the field "Menu options (one per line)" to multiline:
+    """
+    a
+    b
+    """
+    And I set the field "Default value" to "c"
+    And I press "Save changes"
+    And I should see "Default value must be one of the options from the list above" in the "Default value" "form_row"
+    And I set the field "Default value" to "b"
+    And I press "Save changes"
+    And "testfield" "text" should exist in the "Test field" "table_row"
+    And I log out
diff --git a/customfield/field/text/classes/data_controller.php b/customfield/field/text/classes/data_controller.php
new file mode 100644 (file)
index 0000000..a55f570
--- /dev/null
@@ -0,0 +1,116 @@
+<?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/>.
+
+/**
+ * Customfields text field plugin
+ *
+ * @package   customfield_text
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace customfield_text;
+
+defined('MOODLE_INTERNAL') || die;
+
+use core_customfield\api;
+
+/**
+ * Class data
+ *
+ * @package customfield_text
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class data_controller extends \core_customfield\data_controller {
+
+    /**
+     * Return the name of the field where the information is stored
+     * @return string
+     */
+    public function datafield() : string {
+        return 'charvalue';
+    }
+
+    /**
+     * Add fields for editing a text field.
+     *
+     * @param \MoodleQuickForm $mform
+     */
+    public function instance_form_definition(\MoodleQuickForm $mform) {
+        $field = $this->get_field();
+        $config = $field->get('configdata');
+        $type = $config['ispassword'] ? 'password' : 'text';
+        $elementname = $this->get_form_element_name();
+        $mform->addElement($type, $elementname, $this->get_field()->get_formatted_name(), 'size=' . (int)$config['displaysize']);
+        $mform->setType($elementname, PARAM_TEXT);
+        if (!empty($config['defaultvalue'])) {
+            $mform->setDefault($elementname, $config['defaultvalue']);
+        }
+        if ($field->get_configdata_property('required')) {
+            $mform->addRule($elementname, null, 'required', null, 'client');
+        }
+    }
+
+    /**
+     * Validates data for this field.
+     *
+     * @param array $data
+     * @param array $files
+     * @return array
+     */
+    public function instance_form_validation(array $data, array $files): array {
+
+        $errors = parent::instance_form_validation($data, $files);
+        $maxlength = $this->get_field()->get_configdata_property('maxlength');
+        $elementname = $this->get_form_element_name();
+        if (($maxlength > 0) && ($maxlength < \core_text::strlen($data[$elementname]))) {
+            $errors[$elementname] = get_string('errormaxlength', 'customfield_text', $maxlength);
+        }
+        return $errors;
+    }
+
+    /**
+     * Returns the default value as it would be stored in the database (not in human-readable format).
+     *
+     * @return mixed
+     */
+    public function get_default_value() {
+        return $this->get_field()->get_configdata_property('defaultvalue');
+    }
+
+    /**
+     * Returns value in a human-readable format
+     *
+     * @return mixed|null value or null if empty
+     */
+    public function export_value() {
+        $value = parent::export_value();
+        if ($value === null) {
+            return null;
+        }
+
+        $link = $this->get_field()->get_configdata_property('link');
+        if ($link) {
+            $linktarget = $this->get_field()->get_configdata_property('linktarget');
+            $url = str_replace('$$', urlencode($this->get_value()), $link);
+            $attributes = $linktarget ? ['target' => $linktarget] : [];
+            $value = \html_writer::link($url, $value, $attributes);
+        }
+
+        return $value;
+    }
+}
diff --git a/customfield/field/text/classes/privacy/provider.php b/customfield/field/text/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..5937145
--- /dev/null
@@ -0,0 +1,84 @@
+<?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/>.
+/**
+ * Privacy Subsystem implementation for customfield_text.
+ *
+ * @package    customfield_text
+ * @copyright  2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace customfield_text\privacy;
+
+use core_customfield\data_controller;
+use core_customfield\privacy\customfield_provider;
+use core_privacy\local\request\writer;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for customfield_text implementing null_provider.
+ *
+ * @copyright  2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider, customfield_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+
+    /**
+     * Preprocesses data object that is going to be exported
+     *
+     * @param data_controller $data
+     * @param \stdClass $exportdata
+     * @param array $subcontext
+     */
+    public static function export_customfield_data(data_controller $data, \stdClass $exportdata, array $subcontext) {
+        $context = $data->get_context();
+        // For text fields we want to apply format_string even to raw value to avoid CSS.
+        $exportdata->{$data->datafield()} = $data->export_value();
+        writer::with_context($context)
+            ->export_data($subcontext, $exportdata);
+    }
+
+    /**
+     * Allows plugins to delete everything they store related to the data (usually files)
+     *
+     * @param string $dataidstest
+     * @param array $params
+     * @param array $contextids
+     * @return mixed|void
+     */
+    public static function before_delete_data(string $dataidstest, array $params, array $contextids) {
+    }
+
+    /**
+     * Allows plugins to delete everything they store related to the field configuration (usually files)
+     *
+     * @param string $fieldidstest
+     * @param array $params
+     * @param array $contextids
+     */
+    public static function before_delete_fields(string $fieldidstest, array $params, array $contextids) {
+    }
+}
diff --git a/customfield/field/text/tests/behat/field.feature b/customfield/field/text/tests/behat/field.feature
new file mode 100644 (file)
index 0000000..e1bed82
--- /dev/null
@@ -0,0 +1,141 @@
+@customfield @customfield_text
+Feature: Managers can manage course custom fields text
+  In order to have additional data on the course
+  As a manager
+  I need to create, edit, remove and sort 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 > Course custom fields" in site administration
+
+  Scenario: Create a custom course text field
+    When I click on "Add a new custom field" "link"
+    And I click on "Text field" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    Then I should see "Test field"
+    And I log out
+
+  Scenario: Edit a custom course text field
+    When I click on "Add a new custom field" "link"
+    And I click on "Text field" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    And I click on "Edit" "link" in the "Test field" "table_row"
+    And I set the following fields to these values:
+      | Name | Edited field |
+    And I press "Save changes"
+    Then I should see "Edited field"
+    And I navigate to "Reports > Logs" in site administration
+    And I press "Get these logs"
+    And I log out
+
+  @javascript
+  Scenario: Delete a custom course text field
+    When I click on "Add a new custom field" "link"
+    And I click on "Text field" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    And I click on "Delete" "link" in the "Test 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 "Test field" "text" does not exist
+    Then I should not see "Test field"
+    And I log out
+
+  Scenario: A text field with a link setting must show link 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 > Course custom fields" in site administration
+    And I click on "Add a new custom field" "link"
+    And I click on "Text field" "link"
+    And I set the following fields to these values:
+      | Name       | See more on website       |
+      | Short name | testfield                 |
+      | Visible to | Everyone                  |
+      | Link       | https://www.moodle.org/$$ |
+    And I press "Save changes"
+    And I log out
+    Then I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | See more on website | course/view.php?id=35 |
+    And I press "Save and display"
+    And I am on site homepage
+    Then I should see "course/view.php?id=35" in the ".customfields-container .customfieldvalue a" "css_element"
+    Then I should see "See more on website" in the ".customfields-container .customfieldname" "css_element"
+
+  Scenario: A text field with a max length must validate it on course edit form
+    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 > Course custom fields" in site administration
+    And I click on "Add a new custom field" "link"
+    And I click on "Text field" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+      | Maximum length | 3          |
+    And I press "Save changes"
+    And I log out
+    Then I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | Test field | 1234 |
+    And I press "Save and display"
+    Then I should see "This field maximum length is 3"
+
+  Scenario: A text field with a default value must be shown on listing but allow empty values that will not be shown
+    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 > Course custom fields" in site administration
+    And I click on "Add a new custom field" "link"
+    And I click on "Text field" "link"
+    And I set the following fields to these values:
+      | Name          | Test field  |
+      | Short name    | testfield   |
+      | Default value | testdefault |
+    And I press "Save changes"
+    And I log out
+    Then I log in as "teacher1"
+    When I am on site homepage
+    Then I should see "Test field: testdefault"
+    When I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    Then the "value" attribute of "#id_customfield_testfield" "css_element" should contain "testdefault"
+    When I set the following fields to these values:
+      | Test field |  |
+    And I press "Save and display"
+    And I am on site homepage
+    And I should not see "Test field"
diff --git a/customfield/field/textarea/classes/data_controller.php b/customfield/field/textarea/classes/data_controller.php
new file mode 100644 (file)
index 0000000..943dcba
--- /dev/null
@@ -0,0 +1,186 @@
+<?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/>.
+
+/**
+ * Customfields textarea plugin
+ *
+ * @package   customfield_textarea
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace customfield_textarea;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Class data
+ *
+ * @package customfield_textarea
+ * @copyright 2018 Daniel Neis Araujo <daniel@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class data_controller extends \core_customfield\data_controller {
+
+    /**
+     * Return the name of the field where the information is stored
+     * @return string
+     */
+    public function datafield() : string {
+        return 'value';
+    }
+
+    /**
+     * Options for the editor
+     *
+     * @return array
+     */
+    protected function value_editor_options() {
+        /** @var field_controller $field */
+        $field = $this->get_field();
+        return $field->value_editor_options($this->get('id') ? $this->get_context() : null);
+    }
+
+    /**
+     * Returns the name of the field to be used on HTML forms.
+     *
+     * @return string
+     */
+    protected function get_form_element_name(): string {
+        return parent::get_form_element_name() . '_editor';
+    }
+
+    /**
+     * Add fields for editing a textarea field.
+     *
+     * @param \MoodleQuickForm $mform
+     */
+    public function instance_form_definition(\MoodleQuickForm $mform) {
+        $field = $this->get_field();
+        $desceditoroptions = $this->value_editor_options();
+        $elementname = $this->get_form_element_name();
+        $mform->addElement('editor', $elementname, $this->get_field()->get_formatted_name(), null, $desceditoroptions);
+        if ($field->get_configdata_property('required')) {
+            $mform->addRule($elementname, null, 'required', null, 'client');
+        }
+    }
+
+    /**
+     * Saves the data coming from form
+     *
+     * @param \stdClass $datanew data coming from the form
+     */
+    public function instance_form_save(\stdClass $datanew) {
+        $fieldname = $this->get_form_element_name();
+        if (!property_exists($datanew, $fieldname)) {
+            return;
+        }
+        $fromform = $datanew->$fieldname;
+
+        if (!$this->get('id')) {
+            $this->data->set('value', '');
+            $this->data->set('valueformat', FORMAT_MOODLE);
+            $this->save();
+        }
+
+        if ($fromform['text']) {
+            $textoptions = $this->value_editor_options();
+            $data = (object) ['field_editor' => $fromform];
+            $data = file_postupdate_standard_editor($data, 'field', $textoptions, $textoptions['context'],
+                'customfield_textarea', 'value', $this->get('id'));
+            $this->data->set('value', $data->field);
+            $this->data->set('valueformat', $data->fieldformat);
+
+            $this->save();
+        }
+    }
+
+    /**
+     * Prepares the custom field data related to the object to pass to mform->set_data() and adds them to it
+     *
+     * This function must be called before calling $form->set_data($object);
+     *
+     * @param \stdClass $instance the entity that has custom fields, if 'id' attribute is present the custom
+     *    fields for this entity will be added, otherwise the default values will be added.
+     */
+    public function instance_form_before_set_data(\stdClass $instance) {
+        $textoptions = $this->value_editor_options();
+        if ($this->get('id')) {
+            $text = $this->get('value');
+            $format = $this->get('valueformat');
+            $temp = (object)['field' => $text, 'fieldformat' => $format];
+            file_prepare_standard_editor($temp, 'field', $textoptions, $textoptions['context'], 'customfield_textarea',
+                'value', $this->get('id'));
+            $value = $temp->field_editor;
+        } else {
+            $text = $this->get_field()->get_configdata_property('defaultvalue');
+            $format = $this->get_field()->get_configdata_property('defaultvalueformat');
+            $temp = (object)['field' => $text, 'fieldformat' => $format];
+            file_prepare_standard_editor($temp, 'field', $textoptions, $textoptions['context'], 'customfield_textarea',
+                'defaultvalue', $this->get_field()->get('id'));
+            $value = $temp->field_editor;
+        }
+        $instance->{$this->get_form_element_name()} = $value;
+    }
+
+    /**
+     * Delete data
+     *
+     * @return bool
+     */
+    public function delete() {
+        get_file_storage()->delete_area_files($this->get('contextid'), 'customfield_textarea',
+            'value', $this->get('id'));
+        return parent::delete();
+    }
+
+    /**
+     * Returns the default value as it would be stored in the database (not in human-readable format).
+     *
+     * @return mixed
+     */
+    public function get_default_value() {
+        return $this->get_field()->get_configdata_property('defaultvalue');
+    }
+
+    /**
+     * Returns value in a human-readable format
+     *
+     * @return mixed|null value or null if empty
+     */
+    public function export_value() {
+        $value = $this->get_value();
+        if ($this->is_empty($value)) {
+            return null;
+        }
+
+        if ($dataid = $this->get('id')) {
+            $context = $this->get_context();
+            $processed = file_rewrite_pluginfile_urls($value, 'pluginfile.php',
+                $context->id, 'customfield_textarea', 'value', $dataid);
+            $value = format_text($processed, $this->get('valueformat'), ['context' => $context]);
+        } else {
+            $fieldid = $this->get_field()->get('id');
+            $configcontext = $this->get_field()->get_handler()->get_configuration_context();
+            $processed = file_rewrite_pluginfile_urls($value, 'pluginfile.php',
+                $configcontext->id, 'customfield_textarea', 'defaultvalue', $fieldid);
+            $valueformat = $this->get_field()->get_configdata_property('defaultvalueformat');
+            $value = format_text($processed, $valueformat, ['context' => $configcontext]);
+        }
+
+        return $value;
+    }
+}
diff --git a/customfield/field/textarea/classes/privacy/provider.php b/customfield/field/textarea/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..f8924c9
--- /dev/null
@@ -0,0 +1,97 @@
+<?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/>.
+/**
+ * Privacy Subsystem implementation for customfield_textarea.
+ *
+ * @package    customfield_textarea
+ * @copyright  2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace customfield_textarea\privacy;
+
+use core_customfield\data_controller;
+use core_customfield\privacy\customfield_provider;
+use core_privacy\local\request\writer;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for customfield_textarea implementing null_provider.
+ *
+ * @copyright  2018 Daniel Neis Araujo <danielneis@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider, customfield_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+
+    /**
+     * Preprocesses data object that is going to be exported
+     *
+     * @param data_controller $data
+     * @param \stdClass $exportdata
+     * @param array $subcontext
+     */
+    public static function export_customfield_data(data_controller $data, \stdClass $exportdata, array $subcontext) {
+        $context = $data->get_context();
+        $exportdata->value = writer::with_context($context)
+            ->rewrite_pluginfile_urls($subcontext, 'customfield_textarea', 'value',
+                $exportdata->id, $exportdata->value);
+        writer::with_context($context)
+            ->export_data($subcontext, $exportdata)
+            ->export_area_files($subcontext, 'customfield_textarea', 'value', $exportdata->id);
+    }
+
+    /**
+     * Allows plugins to delete everything they store related to the data (usually files)
+     *
+     * @param string $dataidstest
+     * @param array $params
+     * @param array $contextids
+     * @return mixed|void
+     */
+    public static function before_delete_data(string $dataidstest, array $params, array $contextids) {
+        $fs = get_file_storage();
+        foreach ($contextids as $contextid) {
+            $fs->delete_area_files_select($contextid, 'customfield_textarea', 'value', $dataidstest, $params);
+        }
+    }
+
+    /**
+     * Allows plugins to delete everything they store related to the field configuration (usually files)
+     *
+     * The implementation should not delete data or anything related to the data, since "before_delete_data" is
+     * invoked separately.
+     *
+     * @param string $fieldidstest
+     * @param array $params
+     * @param array $contextids
+     */
+    public static function before_delete_fields(string $fieldidstest, array $params, array $contextids) {
+        $fs = get_file_storage();
+        foreach ($contextids as $contextid) {
+            $fs->delete_area_files_select($contextid, 'customfield_textarea', 'defaultvalue', $fieldidstest, $params);
+        }
+    }
+}
diff --git a/customfield/field/textarea/tests/behat/default_value.feature b/customfield/field/textarea/tests/behat/default_value.feature
new file mode 100644 (file)
index 0000000..2f55ed3
--- /dev/null
@@ -0,0 +1,80 @@
+@customfield @customfield_textarea @javascript
+Feature: Default value for the textarea custom field can contain images
+  In order to see images on custom fields
+  As a manager
+  I need to be able to add images to the default value
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email                |
+      | teacher  | Teacher   | 1        | teacher1@example.com |
+      | manager  | Manager   | 1        | manager1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1        | topics |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher  | C1     | editingteacher |
+    And the following "system role assigns" exist:
+      | user    | course               | role    |
+      | manager | Acceptance test site | manager |
+    And the following "custom field categories" exist:
+      | name              | component   | area   | itemid |
+      | Category for test | core_course | course | 0      |
+    # Upload an image into the private files.
+    And I log in as "admin"
+    And I follow "Manage private files"
+    And I upload "lib/tests/fixtures/gd-logo.png" file to "Files" filemanager
+    And I click on "Save changes" "button"
+    And I navigate to "Courses > Course custom fields" in site administration
+    And I click on "Add a new custom field" "link"
+    And I click on "Text area" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+      | Default value | v       |
+    # Embed the image into Default value.
+    And I select the text in the "Default value" Atto editor
+    And I click on "Insert or edit image" "button" in the "//*[@data-fieldtype='editor']/*[descendant::*[@id='id_configdata_defaultvalue_editoreditable']]" "xpath_element"
+    And I click on "Browse repositories..." "button"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "gd-logo.png" "link"
+    And I click on "Select this file" "button"
+    And I set the field "Describe this image for someone who cannot see it" to "Example"
+    And I click on "Save image" "button"
+    And I press "Save changes"
+    And I log out
+
+  Scenario: For the courses that existed before the custom field was created the default value is displayed
+    When I am on site homepage
+    Then the image at "//*[contains(@class, 'frontpage-course-list-all')]//*[contains(@class, 'customfield_textarea')]//img[contains(@src, 'pluginfile.php') and contains(@src, '/customfield_textarea/defaultvalue/') and @alt='Example']" "xpath_element" should be identical to "lib/tests/fixtures/gd-logo.png"
+
+  Scenario: Teacher will see textarea default value when editing a course created before custom field was created
+     # Teacher will see the image when editing existing course.
+    And I log in as "teacher"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I expand all fieldsets
+    Then "//*[@id='id_customfield_testfield_editoreditable']//img[contains(@src, 'draftfile.php') and contains(@src, '/gd-logo.png') and @alt='Example']" "xpath_element" should exist
+    # Save the course without changing the default value.
+    And I press "Save and display"
+    And I log out
+    # Now the same image is displayed as "value" and not as "defaultvalue".
+    And I am on site homepage
+    And "//img[contains(@src, '/customfield_textarea/defaultvalue/')]" "xpath_element" should not exist
+    And the image at "//*[contains(@class, 'frontpage-course-list-all')]//*[contains(@class, 'customfield_textarea')]//img[contains(@src, 'pluginfile.php') and contains(@src, '/customfield_textarea/value/') and @alt='Example']" "xpath_element" should be identical to "lib/tests/fixtures/gd-logo.png"
+
+  Scenario: Manager can create a course and the default value for textarea custom field will apply.
+    When I log in as "manager"
+    And I go to the courses management page
+    And I click on "Create new course" "link" in the "#course-listing" "css_element"
+    And I set the following fields to these values:
+      | Course full name      | Course 2     |
+      | Course short name     | C2           |
+    And I expand all fieldsets
+    Then "//*[@id='id_customfield_testfield_editoreditable']//img[contains(@src, 'draftfile.php') and contains(@src, '/gd-logo.png') and @alt='Example']" "xpath_element" should exist
+    And I press "Save and display"
+    And I log out
+    # Now the same image is displayed as "value" and not as "defaultvalue".
+    And I am on site homepage
+    And the image at "//*[contains(@class, 'frontpage-course-list-all')]//*[contains(@class, 'customfield_textarea')]//img[contains(@src, 'pluginfile.php') and contains(@src, '/customfield_textarea/value/') and @alt='Example']" "xpath_element" should be identical to "lib/tests/fixtures/gd-logo.png"
diff --git a/customfield/field/textarea/tests/behat/field.feature b/customfield/field/textarea/tests/behat/field.feature
new file mode 100644 (file)
index 0000000..7093601
--- /dev/null
@@ -0,0 +1,50 @@
+@customfield @customfield_textarea
+Feature: Managers can manage course custom fields textarea
+  In order to have additional data on the course
+  As a manager
+  I need to create, edit, remove and sort 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 > Course custom fields" in site administration
+
+  Scenario: Create a custom course textarea field
+    When I click on "Add a new custom field" "link"
+    And I click on "Text area" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    Then I should see "Test field"
+    And I log out
+
+  Scenario: Edit a custom course textarea field
+    When I click on "Add a new custom field" "link"
+    And I click on "Text area" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    And I click on "Edit" "link" in the "Test field" "table_row"
+    And I set the following fields to these values:
+      | Name | Edited field |
+    And I press "Save changes"
+    Then I should see "Edited field"
+    And I should not see "Test field"
+    And I log out
+
+  @javascript
+  Scenario: Delete a custom course textarea field
+    When I click on "Add a new custom field" "link"
+    And I click on "Text area" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+    And I press "Save changes"
+    And I click on "Delete" "link" in the "Test field" "table_row"
+    And I click on "Yes" "button" in the "Confirm" "dialogue"
+    Then I should not see "Test field"
+    And I log out
diff --git a/customfield/templates/field_data.mustache b/customfield/templates/field_data.mustache
new file mode 100644 (file)
index 0000000..94d323a
--- /dev/null
@@ -0,0 +1,33 @@
+{{!
+    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 core_customfield/display_field
+
+  Example context (json):
+  {
+      "hasvalue": 1,
+      "fieldtype" : "text",
+      "fieldname" : "Nick name",
+      "fieldshortname" : "nickname",
+      "fieldvalue" : "Star Lord"
+  }
+}}
+{{#hasvalue}}
+    <div class="customfield customfield_{{type}} customfield_{{shortname}}">
+        <span class="customfieldname">{{{name}}}</span><span class="customfieldseparator">: </span><span class="customfieldvalue">{{{value}}}</span>
+    </div>
+{{/hasvalue}}
diff --git a/customfield/tests/behat/edit_categories.feature b/customfield/tests/behat/edit_categories.feature
new file mode 100644 (file)
index 0000000..5fb4802
--- /dev/null
@@ -0,0 +1,111 @@
+@core @core_course @core_customfield @javascript
+Feature: Managers can manage categories for course custom fields
+  In order to have additional data on the course
+  As a manager
+  I need to create, edit, remove and sort custom field's categories
+
+  Scenario: Create a category for custom course fields
+    Given I log in as "admin"
+    When I navigate to "Courses > Course custom fields" in site administration
+    And I press "Add a new category"
+    And I wait until the page is ready
+    Then I should see "Other fields" in the "#customfield_catlist" "css_element"
+    And I navigate to "Reports > Logs" in site administration
+    And I press "Get these logs"
+    And I log out
+
+  Scenario: Edit a category name for custom course fields
+    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 > Course custom fields" in site administration
+    And I click on "Edit category name" "link" in the "//div[contains(@class,'categoryinstance') and contains(.,'Category for test')]" "xpath_element"
+    And I set the field "New value for Category for test" to "Good fields"
+    And I press key "13" in the field "New value for Category for test"
+    Then I should not see "Category for test" in the "#customfield_catlist" "css_element"
+    And "New value for Category for test" "field" should not exist
+    And I should see "Good fields" in the "#customfield_catlist" "css_element"
+    And I navigate to "Reports > Logs" in site administration
+    And I press "Get these logs"
+    And I log out
+
+  Scenario: Delete a category for custom course fields
+    Given the following "custom field categories" exist:
+      | name              | component   | area   | itemid |
+      | Category for test | core_course | course | 0      |
+    And the following "custom fields" exist:
+      | name    | category          | type | shortname |
+      | Field 1 | Category for test | text | f1        |
+    And I log in as "admin"
+    And I navigate to "Courses > Course custom fields" in site administration
+    And I click on "[data-role='deletecategory']" "css_element"
+    And I click on "Yes" "button" in the "Confirm" "dialogue"
+    And I wait until the page is ready
+    And I wait until "Test category" "text" does not exist
+    Then I should not see "Test category" in the "#customfield_catlist" "css_element"
+    And I navigate to "Reports > Logs" in site administration
+    And I press "Get these logs"
+    And I log out
+
+  Scenario: Move field in the course custom fields to another category
+    Given the following "custom field categories" exist:
+      | name      | component   | area   | itemid |
+      | Category1 | core_course | course | 0      |
+      | Category2 | core_course | course | 0      |
+      | Category3 | core_course | course | 0      |
+    And the following "custom fields" exist:
+      | name   | category  | type | shortname |
+      | Field1 | Category1 | text | f1        |
+      | Field2 | Category2 | text | f2        |
+    When I log in as "admin"
+    And I navigate to "Courses > Course custom fields" in site administration
+    Then "Field1" "text" should appear after "Category1" "text"
+    And "Category2" "text" should appear after "Field1" "text"
+    And "Field2" "text" should appear after "Category2" "text"
+    And "Category3" "text" should appear after "Field2" "text"
+    And I press "Move \"Field1\""
+    And I follow "To the top of category Category2"
+    And "Category2" "text" should appear after "Category1" "text"
+    And "Field1" "text" should appear after "Category2" "text"
+    And "Field2" "text" should appear after "Field1" "text"
+    And "Category3" "text" should appear after "Field2" "text"
+    And I navigate to "Courses > Course custom fields" in site administration
+    And "Category2" "text" should appear after "Category1" "text"
+    And "Field1" "text" should appear after "Category2" "text"
+    And "Field2" "text" should appear after "Field1" "text"
+    And "Category3" "text" should appear after "Field2" "text"
+    And I press "Move \"Field1\""
+    And I follow "After field Field2"
+    And "Field1" "text" should appear after "Field2" "text"
+    And I log out
+
+  Scenario: Reorder course custom field categories
+    Given the following "custom field categories" exist:
+      | name      | component   | area   | itemid |
+      | Category1 | core_course | course | 0      |
+      | Category2 | core_course | course | 0      |
+      | Category3 | core_course | course | 0      |
+    And the following "custom fields" exist:
+      | name   | category  | type | shortname |
+      | Field1 | Category1 | text | f1        |
+    When I log in as "admin"
+    And I navigate to "Courses > Course custom fields" in site administration
+    Then "Field1" "text" should appear after "Category1" "text"
+    And "Category2" "text" should appear after "Field1" "text"
+    And "Category3" "text" should appear after "Category2" "text"
+    And I press "Move \"Category2\""
+    And I follow "After \"Category3\""
+    And "Field1" "text" should appear after "Category1" "text"
+    And "Category3" "text" should appear after "Field1" "text"
+    And "Category2" "text" should appear after "Category3" "text"
+    And I navigate to "Courses > Course custom fields" in site administration
+    And "Field1" "text" should appear after "Category1" "text"
+    And "Category3" "text" should appear after "Field1" "text"
+    And "Category2" "text" should appear after "Category3" "text"
+    And I press "Move \"Category2\""
+    And I follow "After \"Category1\""
+    And "Field1" "text" should appear after "Category1" "text"
+    And "Category2" "text" should appear after "Field1" "text"
+    And "Category3" "text" should appear after "Category2" "text"
+    And I log out
diff --git a/customfield/tests/behat/edit_fields_settings.feature b/customfield/tests/behat/edit_fields_settings.feature
new file mode 100644 (file)
index 0000000..6d9f96c
--- /dev/null
@@ -0,0 +1,115 @@
+@core @core_course @core_customfield
+Feature: Teachers can edit course custom fields
+  In order to have additional data on the course
+  As a teacher
+  I need to edit data for custom fields
+
+  Background:
+    Given the following "custom field categories" exist:
+      | name              | component   | area   | itemid |
+      | Category for test | core_course | course | 0      |
+    And the following "custom fields" exist:
+      | name    | category          | type     | shortname | description | configdata            |
+      | Field 1 | Category for test | text     | f1        | d1          |                       |
+      | Field 2 | Category for test | textarea | f2        | d2          |                       |
+      | Field 3 | Category for test | checkbox | f3        | d3          |                       |
+      | Field 4 | Category for test | date     | f4        | d4          |                       |
+      | Field 5 | Category for test | select   | f5        | d5          | {"options":"a\nb\nc"} |
+    And the following "users" exist:
+      | username | firstname | lastname | email                |
+      | teacher1 | Teacher   | 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 |
+
+  Scenario: Display custom fields on course edit form
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I expand all fieldsets
+    Then I should see "Category for test"
+    And I should see "Field 1"
+    And I should see "Field 2"
+    And I should see "Field 3"
+    And I should see "Field 4"
+    And I should see "Field 5"
+    And I log out
+
+  Scenario: Create a course with custom fields from the management interface
+    When I log in as "admin"
+    And I go to the courses management page
+    And I should see the "Categories" management page
+    And I click on category "Miscellaneous" in the management interface
+    And I should see the "Course categories and courses" management page
+    And I click on "Create new course" "link" in the "#course-listing" "css_element"
+    And I set the following fields to these values:
+      | Course full name      | Course 2     |
+      | Course short name     | C2           |
+      | Field 1               | testcontent1 |
+      | Field 2               | testcontent2 |
+      | Field 3               | 1            |
+      | customfield_f4[enabled] | 1          |
+      | customfield_f4[day]   | 1            |
+      | customfield_f4[month] | January      |
+      | customfield_f4[year]  | 2019         |
+      | Field 5               | b            |
+    And I press "Save and display"
+    And I press "Proceed to course content"
+    And I navigate to "Edit settings" in current page administration
+    And the following fields match these values:
+      | Course full name      | Course 2     |
+      | Course short name     | C2           |
+      | Field 1               | testcontent1 |
+      | Field 2               | testcontent2 |
+      | Field 3               | 1            |
+      | customfield_f4[day]   | 1            |
+      | customfield_f4[month] | January      |
+      | customfield_f4[year]  | 2019         |
+      | Field 5               | b            |
+    And I log out
+
+  @javascript
+  Scenario: Use images in the custom field description
+    When I log in as "admin"
+    And I follow "Manage private files"
+    And I upload "lib/tests/fixtures/gd-logo.png" file to "Files" filemanager
+    And I click on "Save changes" "button"
+    And I navigate to "Courses > Course custom fields" in site administration
+    And I click on "Edit" "link" in the "Field 1" "table_row"
+    And I select the text in the "Description" Atto editor
+    And I click on "Insert or edit image" "button" in the "//*[@data-fieldtype='editor']/*[descendant::*[@id='id_description_editoreditable']]" "xpath_element"
+    And I click on "Browse repositories..." "button"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "gd-logo.png" "link"
+    And I click on "Select this file" "button"
+    And I set the field "Describe this image for someone who cannot see it" to "Example"
+    And I click on "Save image" "button"
+    And I press "Save changes"
+    And I log out
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I expand all fieldsets
+    Then the image at "//div[contains(@class, 'fitem')][contains(., 'Field 1')]/following-sibling::div[1]//img[contains(@src, 'pluginfile.php') and contains(@src, '/core_customfield/description/') and @alt='Example']" "xpath_element" should be identical to "lib/tests/fixtures/gd-logo.png"
+    And I log out
+
+  @javascript
+  Scenario: Custom field short name must be present and unique
+    When I log in as "admin"
+    And I navigate to "Courses > Course custom fields" in site administration
+    And I click on "Add a new custom field" "link"
+    And I click on "Text field" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+    And I press "Save changes"
+    Then I should see "You must supply a value here" in the "Short name" "form_row"
+    And I set the field "Short name" to "short name"
+    And I press "Save changes"
+    And I should see "Short name can only contain lowercase latin letters, digits and an underscore sign" in the "Short name" "form_row"
+    And I set the field "Short name" to "f1"
+    And I press "Save changes"
+    And I should see "Short name already exists" in the "Short name" "form_row"
+    And I log out
diff --git a/customfield/tests/behat/required_field.feature b/customfield/tests/behat/required_field.feature
new file mode 100644 (file)
index 0000000..d0aea96
--- /dev/null
@@ -0,0 +1,58 @@
+@core @core_course @core_customfield
+Feature: Requiredness The course custom fields can be mandatory or not
+  In order to make users required to fill a custom field
+  As a manager
+  I can change the requiredness of the fields
+
+  Background:
+    Given the following "custom field categories" exist:
+      | name              | component   | area   | itemid |
+      | Category for test | core_course | course | 0      |
+    Given the following "users" exist:
+      | username | firstname | lastname | email                |
+      | teacher1 | Teacher   | 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 |
+
+  Scenario: A required course custom field must be filled when editing course settings
+    When I log in as "admin"
+    And I navigate to "Courses > Course custom fields" in site administration
+    And I click on "Add a new custom field" "link"
+    And I click on "Text field" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+      | Required   | Yes        |
+    And I press "Save changes"
+    And I log out
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I press "Save and display"
+    Then I should see "You must supply a value here"
+    And I set the field "Test field" to "some value"
+    And I press "Save and display"
+    And I should not see "This field is required"
+    And I log out
+
+  Scenario: A course custom field that is not required may not be filled
+    When I log in as "admin"
+    And I navigate to "Courses > Course custom fields" in site administration
+    And I click on "Add a new custom field" "link"
+    And I click on "Text field" "link"
+    And I set the following fields to these values:
+      | Name       | Test field |
+      | Short name | testfield  |
+      | Required   | No         |
+    And I press "Save changes"
+    And I log out
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I press "Save and display"
+    Then I should see "Course 1"
+    And I should see "Topic 1"
diff --git a/customfield/tests/behat/unique_field.feature b/customfield/tests/behat/unique_field.feature
new file mode 100644 (file)
index 0000000..8fe7063
--- /dev/null
@@ -0,0 +1,74 @@
+@core @core_course @core_customfield
+Feature: Uniqueness The course custom fields can be mandatory or not
+  In order to make users required to fill a custom field
+  As a manager
+  I can change the uniqueness of the fields
+
+  Background:
+    Given the following "custom field categories" exist:
+      | name              | component   | area   | itemid |
+      | Category for test | core_course | course | 0      |
+    Given the following "users" exist:
+      | username | firstname | lastname | email                |
+      | teacher1 | Teacher   | 1        | teacher1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1        | topics |
+      | Course 2 | C2        | topics |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+      | teacher1 | C2     | editingteacher |
+    When I log in as "admin"
+    And I navigate to "Courses > Course custom fields" in site administration
+    And I click on "Add a new custom field" "link"
+    And I click on "Text field" "link"
+    And I set the following fields to these values:
+      | Name        | Test field |
+      | Short name  | testfield  |
+      | Unique data | Yes        |
+    And I press "Save changes"
+    And I log out
+
+  Scenario: A course custom field with unique data must not allow same data in same field in different courses
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | Test field | testcontent |
+    And I press "Save and display"
+    And I am on "Course 2" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | Test field | testcontent |
+    And I press "Save and display"
+    Then I should see "This value is already used"
+
+  Scenario: A course custom field with unique data must not compare with itself
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | Test field | testcontent |
+    And I press "Save and display"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | Test field | testcontent |
+    And I press "Save and display"
+    Then I should not see "This value is already used"
+    And I should see "Topic 1"
+
+  Scenario: A course custom field with unique data must allow empty data
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | Test field |  |
+    And I press "Save and display"
+    And I am on "Course 2" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | Test field |  |
+    And I press "Save and display"
+    Then I should not see "This value is already used"
\ No newline at end of file
index 78c661b..2f3322b 100644 (file)
@@ -121,6 +121,8 @@ $string['type_cachestore'] = 'Cache store';
 $string['type_cachestore_plural'] = 'Cache stores';
 $string['type_calendartype'] = 'Calendar type';
 $string['type_calendartype_plural'] = 'Calendar types';
+$string['type_customfield'] = 'Custom field';
+$string['type_customfield_plural'] = 'Custom fields';
 $string['type_coursereport'] = 'Course report';
 $string['type_coursereport_plural'] = 'Course reports';
 $string['type_dataformat'] = 'Data format';
index d15b8d2..1693dde 100644 (file)
@@ -7260,6 +7260,125 @@ class admin_setting_manageformats extends admin_setting {
     }
 }
 
+/**
+ * Custom fields manager. Allows to enable/disable custom fields and jump to settings.
+ */
+class admin_setting_managecustomfields extends admin_setting {
+
+    /**
+     * Calls parent::__construct with specific arguments
+     */
+    public function __construct() {
+        $this->nosave = true;
+        parent::__construct('customfieldsui', new lang_string('managecustomfields', 'core_admin'), '', '');
+    }
+
+    /**
+     * Always returns true
+     *
+     * @return true
+     */
+    public function get_setting() {
+        return true;
+    }
+
+    /**
+     * Always returns true
+     *
+     * @return true
+     */
+    public function get_defaultsetting() {
+        return true;
+    }
+
+    /**
+     * Always returns '' and doesn't write anything
+     *
+     * @param mixed $data string or array, must not be NULL
+     * @return string Always returns ''
+     */
+    public function write_setting($data) {
+        // Do not write any setting.
+        return '';
+    }
+
+    /**
+     * Search to find if Query is related to format plugin
+     *
+     * @param string $query The string to search for
+     * @return bool true for related false for not
+     */
+    public function is_related($query) {
+        if (parent::is_related($query)) {
+            return true;
+        }
+        $formats = core_plugin_manager::instance()->get_plugins_of_type('customfield');
+        foreach ($formats as $format) {
+            if (strpos($format->component, $query) !== false ||
+                    strpos(core_text::strtolower($format->displayname), $query) !== false) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return XHTML to display control
+     *
+     * @param mixed $data Unused
+     * @param string $query
+     * @return string highlight
+     */
+    public function output_html($data, $query='') {
+        global $CFG, $OUTPUT;
+        $return = '';
+        $return = $OUTPUT->heading(new lang_string('customfields'), 3, 'main');
+        $return .= $OUTPUT->box_start('generalbox customfieldsui');
+
+        $fields = core_plugin_manager::instance()->get_plugins_of_type('customfield');
+
+        $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down'));
+        $txt->uninstall = get_string('uninstallplugin', 'core_admin');
+        $txt->updown = "$txt->up/$txt->down";
+
+        $table = new html_table();
+        $table->head  = array($txt->name, $txt->enable, $txt->uninstall, $txt->settings);
+        $table->align = array('left', 'center', 'center', 'center');
+        $table->attributes['class'] = 'managecustomfieldtable generaltable admintable';
+        $table->data  = array();
+
+        $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'iconsmall'));
+        foreach ($fields as $field) {
+            $url = new moodle_url('/admin/customfields.php',
+                    array('sesskey' => sesskey(), 'field' => $field->name));
+
+            if ($field->is_enabled()) {
+                $strfieldname = $field->displayname;
+                $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
+                        $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall')));
+            } else {
+                $strfieldname = $field->displayname;
+                $class = 'dimmed_text';
+                $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
+                    $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall')));
+            }
+            $settings = '';
+            if ($field->get_settings_url()) {
+                $settings = html_writer::link($field->get_settings_url(), $txt->settings);
+            }
+            $uninstall = '';
+            if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('customfield_'.$field->name, 'manage')) {
+                $uninstall = html_writer::link($uninstallurl, $txt->uninstall);
+            }
+            $row = new html_table_row(array($strfieldname, $hideshow, $uninstall, $settings));
+            $table->data[] = $row;
+        }
+        $return .= html_writer::table($table);
+        $return .= $OUTPUT->box_end();
+        return highlight($query, $return);
+    }
+}
+
 /**
  * Data formats manager. Allow reorder and to enable/disable data formats and jump to settings
  *
index 11ce5a6..de199dc 100644 (file)
@@ -436,6 +436,7 @@ $cache = '.var_export($cache, true).';
             'countries'   => null,
             'course'      => $CFG->dirroot.'/course',
             'currencies'  => null,
+            'customfield' => $CFG->dirroot.'/customfield',
             'dbtransfer'  => null,
             'debug'       => null,
             'editor'      => $CFG->dirroot.'/lib/editor',
@@ -504,6 +505,7 @@ $cache = '.var_export($cache, true).';
             'mod'           => $CFG->dirroot.'/mod',
             'auth'          => $CFG->dirroot.'/auth',
             'calendartype'  => $CFG->dirroot.'/calendar/type',
+            'customfield'   => $CFG->dirroot.'/customfield/field',
             'enrol'         => $CFG->dirroot.'/enrol',
             'message'       => $CFG->dirroot.'/message/output',
             'block'         => $CFG->dirroot.'/blocks',
index a934008..82423d9 100644 (file)
@@ -1740,6 +1740,10 @@ class core_plugin_manager {
                 'gregorian'
             ),
 
+            'customfield' => array(
+                'checkbox', 'date', 'select', 'text', 'textarea'
+            ),
+
             'coursereport' => array(
                 // Deprecated!
             ),
index 961d669..1c2b54f 100644 (file)
@@ -426,6 +426,12 @@ EOD;
             $record['numsections'] = get_config('moodlecourse', 'numsections');
         }
 
+        if (!empty($record['customfields'])) {
+            foreach($record['customfields'] as $field) {
+                $record['customfield_'.$field['shortname']] = $field['value'];
+            }
+        }
+
         $course = create_course((object)$record);
         context_course::instance($course->id);
 
@@ -1173,6 +1179,31 @@ EOD;
         return $event->properties();
     }
 
+    /**
+     * Create a new course custom field category with the given name.
+     *
+     * @param   array $data Array with data['name'] of category
+     * @return  \core_customfield\category_controller   The created category
+     */
+    public function create_custom_field_category($data): \core_customfield\category_controller {
+        return $this->get_plugin_generator('core_customfield')->create_category($data);
+    }
+
+    /**
+     * Create a new custom field
+     *
+     * @param   array $data Array with 'name', 'shortname' and 'type' of the field
+     * @return  \core_customfield\field_controller   The created field
+     */
+    public function create_custom_field($data): \core_customfield\field_controller {
+        global $DB;
+        if (empty($data['categoryid']) && !empty($data['category'])) {
+            $data['categoryid'] = $DB->get_field('customfield_category', 'id', ['name' => $data['category']]);
+            unset($data['category']);
+        }
+        return $this->get_plugin_generator('core_customfield')->create_field($data);
+    }
+
     /**
      * Create a new user, and enrol them in the specified course as the supplied role.
      *
index d0bdb8e..22d8759 100644 (file)
@@ -92,6 +92,16 @@ class behat_data_generators extends behat_base {
             'required' => array('user', 'course', 'role'),
             'switchids' => array('user' => 'userid', 'course' => 'courseid', 'role' => 'roleid')
         ),
+        'custom field categories' => array(
+            'datagenerator' => 'custom_field_category',
+            'required' => array('name', 'component', 'area', 'itemid'),
+            'switchids' => array()
+        ),
+        'custom fields' => array(
+            'datagenerator' => 'custom_field',
+            'required' => array('name', 'category', 'type', 'shortname'),
+            'switchids' => array()
+        ),
         'permission overrides' => array(
             'datagenerator' => 'permission_override',
             'required' => array('capability', 'permission', 'role', 'contextlevel', 'reference'),