MDL-21695 This missing help was annoying me. I've reworded the text a bit, but instr...
[moodle.git] / backup / util / settings / base_setting.class.php
CommitLineData
69dd0c8c
EL
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * @package moodlecore
20 * @subpackage backup-settings
21 * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25/**
26 * This abstract class defines one basic setting
27 *
28 * Each setting will be able to control its name, value (from a list), ui
29 * representation (check box, drop down, text field...), visibility, status
30 * (editable/locked...) and its hierarchy with other settings (using one
31 * like-observer pattern.
32 *
33 * TODO: Finish phpdocs
34 */
35abstract class base_setting {
36
37 // Some constants defining different ui representations for the setting
38 const UI_NONE = 0;
39 const UI_HTML_CHECKBOX = 10;
40 const UI_HTML_RADIOBUTTON = 20;
41 const UI_HTML_DROPDOWN = 30;
42 const UI_HTML_TEXTFIELD = 40;
43
44 // Type of validation to perform against the value (relaying in PARAM_XXX validations)
45 const IS_BOOLEAN = 'bool';
46 const IS_INTEGER = 'int';
47 const IS_FILENAME= 'file';
48 const IS_PATH = 'path';
49
50 // Visible/hidden
51 const VISIBLE = 1;
52 const HIDDEN = 0;
53
54 // Editable/locked (by different causes)
ce937f99
EL
55 const NOT_LOCKED = 3;
56 const LOCKED_BY_CONFIG = 5;
69dd0c8c 57 const LOCKED_BY_HIERARCHY = 7;
ce937f99 58 const LOCKED_BY_PERMISSION = 9;
69dd0c8c
EL
59
60 // Type of change to inform dependencies
61 const CHANGED_VALUE = 1;
62 const CHANGED_VISIBILITY = 2;
63 const CHANGED_STATUS = 3;
64
65 protected $name; // name of the setting
66 protected $value; // value of the setting
67 protected $vtype; // type of value (setting_base::IS_BOOLEAN/setting_base::IS_INTEGER...)
68
69 protected $visibility; // visibility of the setting (setting_base::VISIBLE/setting_base::HIDDEN)
70 protected $status; // setting_base::NOT_LOCKED/setting_base::LOCKED_BY_PERMISSION...
71
72 protected $dependencies; // array of dependent (observer) objects (usually setting_base ones)
73
1904e9b3
SH
74 /**
75 *
76 * @var backup_setting_ui|backup_setting_ui_checkbox|backup_setting_ui_radio|backup_setting_ui_select|backup_setting_ui_text
77 */
78 protected $uisetting;
79
69dd0c8c
EL
80 // Note: all the UI stuff could go to independent classes in the future...
81 protected $ui_type; // setting_base::UI_HTML_CHECKBOX/setting_base::UI_HTML_RADIOBUTTON...
82 protected $ui_label; // UI label of the setting
83 protected $ui_values; // array of value => ui value of the setting
84 protected $ui_options;// array of custom ui options
85
86 public function __construct($name, $vtype, $value = null, $visibility = self::VISIBLE, $status = self::NOT_LOCKED) {
87 // Check vtype
88 if ($vtype !== self::IS_BOOLEAN && $vtype !== self::IS_INTEGER &&
89 $vtype !== self::IS_FILENAME && $vtype !== self::IS_PATH) {
90 throw new base_setting_exception('setting_invalid_type');
91 }
92
93 // Validate value
94 $value = $this->validate_value($vtype, $value);
95
96 // Check visibility
97 $visibility = $this->validate_visibility($visibility);
98
99 // Check status
100 $status = $this->validate_status($status);
101
102 $this->name = $name;
103 $this->vtype = $vtype;
104 $this->value = $value;
105 $this->visibility = $visibility;
106 $this->status = $status;
107 $this->dependencies= array();
108
1904e9b3
SH
109 // Generate a default ui
110 $this->uisetting = new backup_setting_ui_checkbox($this, $name);
69dd0c8c
EL
111 }
112
113 public function get_name() {
114 return $this->name;
115 }
116
117 public function get_value() {
118 return $this->value;
119 }
120
121 public function get_visibility() {
122 return $this->visibility;
123 }
124
125 public function get_status() {
126 return $this->status;
127 }
128
129 public function set_value($value) {
130 // Validate value
131 $value = $this->validate_value($this->vtype, $value);
132 // Only can change value if setting is not locked
133 if ($this->status != self::NOT_LOCKED) {
134 switch ($this->status) {
135 case self::LOCKED_BY_PERMISSION:
136 throw new base_setting_exception('setting_locked_by_permission');
cd0034d8
EL
137 case self::LOCKED_BY_CONFIG:
138 throw new base_setting_exception('setting_locked_by_config');
69dd0c8c
EL
139 }
140 }
141 $oldvalue = $this->value;
142 $this->value = $value;
143 if ($value !== $oldvalue) { // Value has changed, let's inform dependencies
144 $this->inform_dependencies(self::CHANGED_VALUE, $oldvalue);
145 }
146 }
147
148 public function set_visibility($visibility) {
149 $visibility = $this->validate_visibility($visibility);
150 $oldvisibility = $this->visibility;
151 $this->visibility = $visibility;
152 if ($visibility !== $oldvisibility) { // Visibility has changed, let's inform dependencies
153 $this->inform_dependencies(self::CHANGED_VISIBILITY, $oldvisibility);
154 }
155 }
156
157 public function set_status($status) {
158 $status = $this->validate_status($status);
159 $oldstatus = $this->status;
160 $this->status = $status;
161 if ($status !== $oldstatus) { // Status has changed, let's inform dependencies
162 $this->inform_dependencies(self::CHANGED_STATUS, $oldstatus);
163 }
164 }
165
1904e9b3
SH
166 public function set_ui(backup_setting_ui $ui) {
167 $this->uisetting = $ui;
69dd0c8c
EL
168 }
169
1904e9b3
SH
170 public function make_ui($type, $label, array $attributes = null, array $options = null) {
171 $type = $this->validate_ui_type($type);
172 $label = $this->validate_ui_label($label);
173 $this->uisetting = backup_setting_ui::make($this, $type, $label, $attributes, $options);
174 if (is_array($options) || is_object($options)) {
175 $options = (array)$options;
176 switch (get_class($this->uisetting)) {
177 case 'backup_setting_ui_radio' :
178 // text
179 if (array_key_exists('text', $options)) {
180 $this->uisetting->set_text($options['text']);
181 }
182 case 'backup_setting_ui_checkbox' :
183 // value
184 if (array_key_exists('value', $options)) {
185 $this->uisetting->set_value($options['value']);
186 }
187 break;
188 case 'backup_setting_ui_select' :
189 // options
190 if (array_key_exists('options', $options)) {
191 $this->uisetting->set_values($options['options']);
192 }
193 break;
194 }
195 }
69dd0c8c
EL
196 }
197
1904e9b3
SH
198 public function get_ui() {
199 return $this->uisetting;
69dd0c8c
EL
200 }
201
1904e9b3
SH
202 public function add_dependency(base_setting $dependentsetting, $type=null, $options=array()) {
203 if ($this->is_circular_reference($dependentsetting)) {
69dd0c8c
EL
204 $a = new stdclass();
205 $a->alreadydependent = $this->name;
1904e9b3 206 $a->main = $dependentsetting->get_name();
69dd0c8c
EL
207 throw new base_setting_exception('setting_circular_reference', $a);
208 }
209 // Check the settings hasn't been already added
1904e9b3 210 if (array_key_exists($dependentsetting->get_name(), $this->dependencies)) {
69dd0c8c
EL
211 throw new base_setting_exception('setting_already_added');
212 }
1904e9b3
SH
213
214 $options = (array)$options;
215
216 if (!array_key_exists('defaultvalue', $options)) {
217 $options['defaultvalue'] = false;
218 }
219
220 if ($type == null) {
221 switch ($this->vtype) {
222 case self::IS_BOOLEAN :
223 if ($this->value) {
224 $type = setting_dependency::DISABLED_FALSE;
225 } else {
226 $type = setting_dependency::DISABLED_TRUE;
227 }
228 break;
229 case self::IS_FILENAME :
230 case self::IS_PATH :
231 case self::IS_INTEGER :
232 default :
233 $type = setting_dependency::DISABLED_VALUE;
234 break;
235 }
236 }
237
238 switch ($type) {
239 case setting_dependency::DISABLED_VALUE :
240 if (!array_key_exists('value', $options)) {
241 throw new base_setting_exception('dependency_needs_value');
242 }
243 $dependency = new setting_dependency_disabledif_equals($this, $dependentsetting, $options['value'], $options['defaultvalue']);
244 break;
245 case setting_dependency::DISABLED_TRUE :
246 case setting_dependency::DISABLED_CHECKED :
247 $dependency = new setting_dependency_disabledif_equals($this, $dependentsetting, true, $options['defaultvalue']);
248 break;
249 case setting_dependency::DISABLED_FALSE :
250 case setting_dependency::DISABLED_NOT_CHECKED :
251 $dependency = new setting_dependency_disabledif_equals($this, $dependentsetting, false, $options['defaultvalue']);
252 break;
253 }
254 $this->dependencies[$dependentsetting->get_name()] = $dependency;
69dd0c8c
EL
255 }
256
257// Protected API starts here
258
259 protected function validate_value($vtype, $value) {
260 if (is_null($value)) { // Nulls aren't validated
261 return null;
262 }
263 $oldvalue = $value;
264 switch ($vtype) {
265 case self::IS_BOOLEAN:
266 $value = clean_param($oldvalue, PARAM_BOOL); // Just clean
267 break;
268 case self::IS_INTEGER:
269 $value = clean_param($oldvalue, PARAM_INT);
270 if ($value != $oldvalue) {
271 throw new base_setting_exception('setting_invalid_integer', $oldvalue);
272 }
273 break;
274 case self::IS_FILENAME:
275 $value = clean_param($oldvalue, PARAM_FILE);
276 if ($value != $oldvalue) {
277 throw new base_setting_exception('setting_invalid_filename', $oldvalue);
278 }
279 break;
280 case self::IS_PATH:
281 $value = clean_param($oldvalue, PARAM_PATH);
282 if ($value != $oldvalue) {
283 throw new base_setting_exception('setting_invalid_path', $oldvalue);
284 }
285 break;
286 }
287 return $value;
288 }
289
290 protected function validate_visibility($visibility) {
291 if (is_null($visibility)) {
292 $visibility = self::VISIBLE;
293 }
294 if ($visibility !== self::VISIBLE && $visibility !== self::HIDDEN) {
295 throw new base_setting_exception('setting_invalid_visibility');
296 }
297 return $visibility;
298 }
299
300 protected function validate_status($status) {
301 if (is_null($status)) {
302 $status = self::NOT_LOCKED;
303 }
ce937f99
EL
304 if ($status !== self::NOT_LOCKED && $status !== self::LOCKED_BY_CONFIG &&
305 $status !== self::LOCKED_BY_PERMISSION && $status !== self::LOCKED_BY_HIERARCHY) {
306 throw new base_setting_exception('setting_invalid_status', $status);
69dd0c8c
EL
307 }
308 return $status;
309 }
310
311 protected function validate_ui_type($type) {
312 if ($type !== self::UI_HTML_CHECKBOX && $type !== self::UI_HTML_RADIOBUTTON &&
313 $type !== self::UI_HTML_DROPDOWN && $type !== self::UI_HTML_TEXTFIELD) {
314 throw new base_setting_exception('setting_invalid_ui_type');
315 }
316 return $type;
317 }
318
319 protected function validate_ui_label($label) {
320 if (empty($label) || $label !== clean_param($label, PARAM_ALPHAEXT)) {
321 throw new base_setting_exception('setting_invalid_ui_label');
322 }
323 return $label;
324 }
325
326 protected function inform_dependencies($ctype, $oldv) {
327 foreach ($this->dependencies as $dependency) {
1904e9b3 328 $dependency->process_change($ctype, $oldv);
69dd0c8c
EL
329 }
330 }
331
332 protected function is_circular_reference($obj) {
333 // Get object dependencies recursively and check (by name) if $this is already there
334 $dependencies = $obj->get_dependencies();
335 if (array_key_exists($this->name, $dependencies) || $obj == $this) {
336 return true;
337 }
338 return false;
339 }
340
341 protected function get_dependencies() {
342 $dependencies = array();
343 foreach ($this->dependencies as $dependency) {
1904e9b3 344 $dependencies[$dependency->get_dependant_setting()->get_name()] = $dependency->get_dependant_setting();
69dd0c8c
EL
345 $dependencies = array_merge($dependencies, $dependency->get_dependencies());
346 }
347 return $dependencies;
348 }
349
350// Implementable API starts here
351
352 abstract public function process_change($setting, $ctype, $oldv);
353}
354
355/*
356 * Exception class used by all the @setting_base stuff
357 */
358class base_setting_exception extends backup_exception {
359
360 public function __construct($errorcode, $a=NULL, $debuginfo=null) {
361 parent::__construct($errorcode, $a, $debuginfo);
362 }
363}