See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Customfield component data controller abstract class 19 * 20 * @package core_customfield 21 * @copyright 2018 Toni Barbera <toni@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace core_customfield; 26 27 use core_customfield\output\field_data; 28 29 defined('MOODLE_INTERNAL') || die; 30 31 /** 32 * Base class for custom fields data controllers 33 * 34 * This class is a wrapper around the persistent data class that allows to define 35 * how the element behaves in the instance edit forms. 36 * 37 * Custom field plugins must define a class 38 * \{pluginname}\data_controller extends \core_customfield\data_controller 39 * 40 * @package core_customfield 41 * @copyright 2018 Toni Barbera <toni@moodle.com> 42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 */ 44 abstract class data_controller { 45 /** 46 * Data persistent 47 * 48 * @var data 49 */ 50 protected $data; 51 52 /** 53 * Field that this data belongs to. 54 * 55 * @var field_controller 56 */ 57 protected $field; 58 59 /** 60 * data_controller constructor. 61 * 62 * @param int $id 63 * @param \stdClass|null $record 64 */ 65 public function __construct(int $id, \stdClass $record) { 66 $this->data = new data($id, $record); 67 } 68 69 /** 70 * Creates an instance of data_controller 71 * 72 * Parameters $id, $record and $field can complement each other but not conflict. 73 * If $id is not specified, fieldid must be present either in $record or in $field. 74 * If $id is not specified, instanceid must be present in $record 75 * 76 * No DB queries are performed if both $record and $field are specified. 77 78 * @param int $id 79 * @param \stdClass|null $record 80 * @param field_controller|null $field 81 * @return data_controller 82 * @throws \coding_exception 83 * @throws \moodle_exception 84 */ 85 public static function create(int $id, \stdClass $record = null, field_controller $field = null) : data_controller { 86 global $DB; 87 if ($id && $record) { 88 // This warning really should be in persistent as well. 89 debugging('Too many parameters, either id need to be specified or a record, but not both.', 90 DEBUG_DEVELOPER); 91 } 92 if ($id) { 93 $record = $DB->get_record(data::TABLE, array('id' => $id), '*', MUST_EXIST); 94 } else if (!$record) { 95 $record = new \stdClass(); 96 } 97 98 if (!$field && empty($record->fieldid)) { 99 throw new \coding_exception('Not enough parameters to initialise data_controller - unknown field'); 100 } 101 if (!$field) { 102 $field = field_controller::create($record->fieldid); 103 } 104 if (empty($record->fieldid)) { 105 $record->fieldid = $field->get('id'); 106 } 107 if ($field->get('id') != $record->fieldid) { 108 throw new \coding_exception('Field id from the record does not match field from the parameter'); 109 } 110 $type = $field->get('type'); 111 $customfieldtype = "\\customfield_{$type}\\data_controller"; 112 if (!class_exists($customfieldtype) || !is_subclass_of($customfieldtype, self::class)) { 113 throw new \moodle_exception('errorfieldtypenotfound', 'core_customfield', '', s($type)); 114 } 115 $datacontroller = new $customfieldtype(0, $record); 116 $datacontroller->field = $field; 117 return $datacontroller; 118 } 119 120 /** 121 * Returns the name of the field to be used on HTML forms. 122 * 123 * @return string 124 */ 125 public function get_form_element_name() : string { 126 return 'customfield_' . $this->get_field()->get('shortname'); 127 } 128 129 /** 130 * Persistent getter parser. 131 * 132 * @param string $property 133 * @return mixed 134 */ 135 final public function get($property) { 136 return $this->data->get($property); 137 } 138 139 /** 140 * Persistent setter parser. 141 * 142 * @param string $property 143 * @param mixed $value 144 * @return data 145 */ 146 final public function set($property, $value) { 147 return $this->data->set($property, $value); 148 } 149 150 /** 151 * Return the name of the field in the db table {customfield_data} where the data is stored 152 * 153 * Must be one of the following: 154 * intvalue - can store integer values, this field is indexed 155 * decvalue - can store decimal values 156 * shortcharvalue - can store character values up to 255 characters long, this field is indexed 157 * charvalue - can store character values up to 1333 characters long, this field is not indexed but 158 * full text search is faster than on field 'value' 159 * value - can store character values of unlimited length ("text" field in the db) 160 * 161 * @return string 162 */ 163 abstract public function datafield() : string; 164 165 /** 166 * Delete data. Element can override it if related information needs to be deleted as well (such as files) 167 * 168 * @return bool 169 */ 170 public function delete() { 171 return $this->data->delete(); 172 } 173 174 /** 175 * Persistent save parser. 176 * 177 * @return void 178 */ 179 public function save() { 180 $this->data->save(); 181 } 182 183 /** 184 * Field associated with this data 185 * 186 * @return field_controller 187 */ 188 public function get_field() : field_controller { 189 return $this->field; 190 } 191 192 /** 193 * Saves the data coming from form 194 * 195 * @param \stdClass $datanew data coming from the form 196 */ 197 public function instance_form_save(\stdClass $datanew) { 198 $elementname = $this->get_form_element_name(); 199 if (!property_exists($datanew, $elementname)) { 200 return; 201 } 202 $value = $datanew->$elementname; 203 $this->data->set($this->datafield(), $value); 204 $this->data->set('value', $value); 205 $this->save(); 206 } 207 208 /** 209 * Prepares the custom field data related to the object to pass to mform->set_data() and adds them to it 210 * 211 * This function must be called before calling $form->set_data($object); 212 * 213 * @param \stdClass $instance the instance that has custom fields, if 'id' attribute is present the custom 214 * fields for this instance will be added, otherwise the default values will be added. 215 */ 216 public function instance_form_before_set_data(\stdClass $instance) { 217 $instance->{$this->get_form_element_name()} = $this->get_value(); 218 } 219 220 /** 221 * Checks if the value is empty 222 * 223 * @param mixed $value 224 * @return bool 225 */ 226 protected function is_empty($value) : bool { 227 if ($this->datafield() === 'value' || $this->datafield() === 'charvalue' || $this->datafield() === 'shortcharvalue') { 228 return '' . $value === ''; 229 } 230 return empty($value); 231 } 232 233 /** 234 * Checks if the value is unique 235 * 236 * @param mixed $value 237 * @return bool 238 */ 239 protected function is_unique($value) : bool { 240 global $DB; 241 242 // Ensure the "value" datafield can be safely compared across all databases. 243 $datafield = $this->datafield(); 244 if ($datafield === 'value') { 245 $datafield = $DB->sql_cast_to_char($datafield); 246 } 247 248 $where = "fieldid = ? AND {$datafield} = ?"; 249 $params = [$this->get_field()->get('id'), $value]; 250 if ($this->get('id')) { 251 $where .= ' AND id <> ?'; 252 $params[] = $this->get('id'); 253 } 254 return !$DB->record_exists_select('customfield_data', $where, $params); 255 } 256 257 /** 258 * Called from instance edit form in validation() 259 * 260 * @param array $data 261 * @param array $files 262 * @return array array of errors 263 */ 264 public function instance_form_validation(array $data, array $files) : array { 265 $errors = []; 266 $elementname = $this->get_form_element_name(); 267 if ($this->get_field()->get_configdata_property('uniquevalues') == 1) { 268 $value = $data[$elementname]; 269 if (!$this->is_empty($value) && !$this->is_unique($value)) { 270 $errors[$elementname] = get_string('erroruniquevalues', 'core_customfield'); 271 } 272 } 273 return $errors; 274 } 275 276 /** 277 * Called from instance edit form in definition_after_data() 278 * 279 * @param \MoodleQuickForm $mform 280 */ 281 public function instance_form_definition_after_data(\MoodleQuickForm $mform) { 282 283 } 284 285 /** 286 * Used by handlers to display data on various places. 287 * 288 * @return string 289 */ 290 public function display() : string { 291 global $PAGE; 292 $output = $PAGE->get_renderer('core_customfield'); 293 return $output->render(new field_data($this)); 294 } 295 296 /** 297 * Returns the default value as it would be stored in the database (not in human-readable format). 298 * 299 * @return mixed 300 */ 301 public abstract function get_default_value(); 302 303 /** 304 * Returns the value as it is stored in the database or default value if data record is not present 305 * 306 * @return mixed 307 */ 308 public function get_value() { 309 if (!$this->get('id')) { 310 return $this->get_default_value(); 311 } 312 return $this->get($this->datafield()); 313 } 314 315 /** 316 * Return the context of the field 317 * 318 * @return \context 319 */ 320 public function get_context() : \context { 321 if ($this->get('contextid')) { 322 return \context::instance_by_id($this->get('contextid')); 323 } else if ($this->get('instanceid')) { 324 return $this->get_field()->get_handler()->get_instance_context($this->get('instanceid')); 325 } else { 326 // Context is not yet known (for example, entity is not yet created). 327 return \context_system::instance(); 328 } 329 } 330 331 /** 332 * Add a field to the instance edit form. 333 * 334 * @param \MoodleQuickForm $mform 335 */ 336 public abstract function instance_form_definition(\MoodleQuickForm $mform); 337 338 /** 339 * Returns value in a human-readable format or default value if data record is not present 340 * 341 * This is the default implementation that most likely needs to be overridden 342 * 343 * @return mixed|null value or null if empty 344 */ 345 public function export_value() { 346 $value = $this->get_value(); 347 348 if ($this->is_empty($value)) { 349 return null; 350 } 351 352 if ($this->datafield() === 'intvalue') { 353 return (int)$value; 354 } else if ($this->datafield() === 'decvalue') { 355 return (float)$value; 356 } else if ($this->datafield() === 'value') { 357 return format_text($value, $this->get('valueformat'), ['context' => $this->get_context()]); 358 } else { 359 return format_string($value, true, ['context' => $this->get_context()]); 360 } 361 } 362 363 /** 364 * Persistent to_record parser. 365 * 366 * @return \stdClass 367 */ 368 final public function to_record() { 369 return $this->data->to_record(); 370 } 371 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body