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 */ 35 abstract 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 const IS_TEXT = 'text'; 50 51 // Visible/hidden 52 const VISIBLE = 1; 53 const HIDDEN = 0; 54 55 // Editable/locked (by different causes) 56 const NOT_LOCKED = 3; 57 const LOCKED_BY_CONFIG = 5; 58 const LOCKED_BY_HIERARCHY = 7; 59 const LOCKED_BY_PERMISSION = 9; 60 61 // Type of change to inform dependencies 62 const CHANGED_VALUE = 1; 63 const CHANGED_VISIBILITY = 2; 64 const CHANGED_STATUS = 3; 65 66 protected $name; // name of the setting 67 protected $value; // value of the setting 68 protected $unlockedvalue; // Value to set after the setting is unlocked. 69 protected $vtype; // type of value (setting_base::IS_BOOLEAN/setting_base::IS_INTEGER...) 70 71 protected $visibility; // visibility of the setting (setting_base::VISIBLE/setting_base::HIDDEN) 72 protected $status; // setting_base::NOT_LOCKED/setting_base::LOCKED_BY_PERMISSION... 73 74 /** @var setting_dependency[] */ 75 protected $dependencies = array(); // array of dependent (observer) objects (usually setting_base ones) 76 protected $dependenton = array(); 77 78 /** 79 * The user interface for this setting 80 * @var backup_setting_ui|backup_setting_ui_checkbox|backup_setting_ui_radio|backup_setting_ui_select|backup_setting_ui_text 81 */ 82 protected $uisetting; 83 84 /** 85 * An array that contains the identifier and component of a help string if one 86 * has been set 87 * @var array 88 */ 89 protected $help = array(); 90 91 /** 92 * Instantiates a setting object 93 * 94 * @param string $name Name of the setting 95 * @param string $vtype Type of the setting, eg {@link self::IS_TEXT} 96 * @param mixed $value Value of the setting 97 * @param bool $visibility Is the setting visible in the UI, eg {@link self::VISIBLE} 98 * @param int $status Status of the setting with regards to the locking, eg {@link self::NOT_LOCKED} 99 */ 100 public function __construct($name, $vtype, $value = null, $visibility = self::VISIBLE, $status = self::NOT_LOCKED) { 101 // Check vtype 102 if ($vtype !== self::IS_BOOLEAN && $vtype !== self::IS_INTEGER && 103 $vtype !== self::IS_FILENAME && $vtype !== self::IS_PATH && 104 $vtype !== self::IS_TEXT) { 105 throw new base_setting_exception('setting_invalid_type'); 106 } 107 108 // Validate value 109 $value = $this->validate_value($vtype, $value); 110 111 // Check visibility 112 $visibility = $this->validate_visibility($visibility); 113 114 // Check status 115 $status = $this->validate_status($status); 116 117 $this->name = $name; 118 $this->vtype = $vtype; 119 $this->value = $value; 120 $this->visibility = $visibility; 121 $this->status = $status; 122 $this->unlockedvalue = $this->value; 123 124 // Generate a default ui 125 $this->uisetting = new base_setting_ui($this); 126 } 127 128 /** 129 * Destroy all circular references. It helps PHP 5.2 a lot! 130 */ 131 public function destroy() { 132 // Before reseting anything, call destroy recursively 133 foreach ($this->dependencies as $dependency) { 134 $dependency->destroy(); 135 } 136 foreach ($this->dependenton as $dependenton) { 137 $dependenton->destroy(); 138 } 139 if ($this->uisetting) { 140 $this->uisetting->destroy(); 141 } 142 // Everything has been destroyed recursively, now we can reset safely 143 $this->dependencies = array(); 144 $this->dependenton = array(); 145 $this->uisetting = null; 146 } 147 148 public function get_name() { 149 return $this->name; 150 } 151 152 public function get_value() { 153 return $this->value; 154 } 155 156 public function get_visibility() { 157 return $this->visibility; 158 } 159 160 public function get_status() { 161 return $this->status; 162 } 163 164 public function set_value($value) { 165 // Validate value 166 $value = $this->validate_value($this->vtype, $value); 167 // Only can change value if setting is not locked 168 if ($this->status != self::NOT_LOCKED) { 169 switch ($this->status) { 170 case self::LOCKED_BY_PERMISSION: 171 throw new base_setting_exception('setting_locked_by_permission'); 172 case self::LOCKED_BY_CONFIG: 173 throw new base_setting_exception('setting_locked_by_config'); 174 } 175 } 176 $oldvalue = $this->value; 177 $this->value = $value; 178 if ($value !== $oldvalue) { // Value has changed, let's inform dependencies 179 $this->inform_dependencies(self::CHANGED_VALUE, $oldvalue); 180 } 181 } 182 183 public function set_visibility($visibility) { 184 $visibility = $this->validate_visibility($visibility); 185 186 // If this setting is dependent on other settings first check that all 187 // of those settings are visible 188 if (count($this->dependenton) > 0 && $visibility == base_setting::VISIBLE) { 189 foreach ($this->dependenton as $dependency) { 190 if ($dependency->get_setting()->get_visibility() != base_setting::VISIBLE) { 191 $visibility = base_setting::HIDDEN; 192 break; 193 } 194 } 195 } 196 197 $oldvisibility = $this->visibility; 198 $this->visibility = $visibility; 199 if ($visibility !== $oldvisibility) { // Visibility has changed, let's inform dependencies 200 $this->inform_dependencies(self::CHANGED_VISIBILITY, $oldvisibility); 201 } 202 } 203 204 public function set_status($status) { 205 $status = $this->validate_status($status); 206 207 if (($this->status == base_setting::LOCKED_BY_PERMISSION || $this->status == base_setting::LOCKED_BY_CONFIG) 208 && $status == base_setting::LOCKED_BY_HIERARCHY) { 209 // Lock by permission or config can not be overriden by lock by hierarchy. 210 return; 211 } 212 213 // If the setting is being unlocked first check whether an other settings 214 // this setting is dependent on are locked. If they are then we still don't 215 // want to lock this setting. 216 if (count($this->dependenton) > 0 && $status == base_setting::NOT_LOCKED) { 217 foreach ($this->dependenton as $dependency) { 218 if ($dependency->is_locked()) { 219 // It still needs to be locked 220 $status = base_setting::LOCKED_BY_HIERARCHY; 221 break; 222 } 223 } 224 } 225 226 $oldstatus = $this->status; 227 $this->status = $status; 228 if ($status !== $oldstatus) { // Status has changed, let's inform dependencies 229 $this->inform_dependencies(self::CHANGED_STATUS, $oldstatus); 230 231 if ($status == base_setting::NOT_LOCKED) { 232 // When setting gets unlocked set it to the original value. 233 $this->set_value($this->unlockedvalue); 234 } 235 } 236 } 237 238 /** 239 * Gets an array of properties for all of the dependencies that will affect 240 * this setting. 241 * 242 * This method returns an array rather than the dependencies in order to 243 * minimise the memory footprint of for the potentially huge recursive 244 * dependency structure that we may be dealing with. 245 * 246 * This method also ensures that all dependencies are transmuted to affect 247 * the setting in question and that we don't provide any duplicates. 248 * 249 * @param string|null $settingname 250 * @return array 251 */ 252 public function get_my_dependency_properties($settingname=null) { 253 if ($settingname == null) { 254 $settingname = $this->get_ui_name(); 255 } 256 $dependencies = array(); 257 foreach ($this->dependenton as $dependenton) { 258 $properties = $dependenton->get_moodleform_properties(); 259 $properties['setting'] = $settingname; 260 $dependencies[$properties['setting'].'-'.$properties['dependenton']] = $properties; 261 $dependencies = array_merge($dependencies, $dependenton->get_setting()->get_my_dependency_properties($settingname)); 262 } 263 return $dependencies; 264 } 265 266 /** 267 * Returns all of the dependencies that affect this setting. 268 * e.g. settings this setting depends on. 269 * 270 * @return array Array of setting_dependency's 271 */ 272 public function get_settings_depended_on() { 273 return $this->dependenton; 274 } 275 276 /** 277 * Checks if there are other settings that are dependent on this setting 278 * 279 * @return bool True if there are other settings that are dependent on this setting 280 */ 281 public function has_dependent_settings() { 282 return (count($this->dependencies)>0); 283 } 284 285 /** 286 * Checks if this setting is dependent on any other settings 287 * 288 * @return bool True if this setting is dependent on any other settings 289 */ 290 public function has_dependencies_on_settings() { 291 return (count($this->dependenton)>0); 292 } 293 294 /** 295 * Sets the user interface for this setting 296 * 297 * @param base_setting_ui $ui 298 */ 299 public function set_ui(backup_setting_ui $ui) { 300 $this->uisetting = $ui; 301 } 302 303 /** 304 * Gets the user interface for this setting 305 * 306 * @return base_setting_ui 307 */ 308 public function get_ui() { 309 return $this->uisetting; 310 } 311 312 /** 313 * Adds a dependency where another setting depends on this setting. 314 * @param setting_dependency $dependency 315 */ 316 public function register_dependency(setting_dependency $dependency) { 317 if ($this->is_circular_reference($dependency->get_dependent_setting())) { 318 $a = new stdclass(); 319 $a->alreadydependent = $this->name; 320 $a->main = $dependency->get_dependent_setting()->get_name(); 321 throw new base_setting_exception('setting_circular_reference', $a); 322 } 323 $this->dependencies[$dependency->get_dependent_setting()->get_name()] = $dependency; 324 $dependency->get_dependent_setting()->register_dependent_dependency($dependency); 325 } 326 /** 327 * Adds a dependency where this setting is dependent on another. 328 * 329 * This should only be called internally once we are sure it is not cicrular. 330 * 331 * @param setting_dependency $dependency 332 */ 333 protected function register_dependent_dependency(setting_dependency $dependency) { 334 $this->dependenton[$dependency->get_setting()->get_name()] = $dependency; 335 } 336 337 /** 338 * Quick method to add a dependency to this setting. 339 * 340 * The dependency created is done so by inspecting this setting and the 341 * setting that is passed in as the dependent setting. 342 * 343 * @param base_setting $dependentsetting 344 * @param int $type One of setting_dependency::* 345 * @param array $options 346 */ 347 public function add_dependency(base_setting $dependentsetting, $type=null, $options=array()) { 348 if ($this->is_circular_reference($dependentsetting)) { 349 $a = new stdclass(); 350 $a->alreadydependent = $this->name; 351 $a->main = $dependentsetting->get_name(); 352 throw new base_setting_exception('setting_circular_reference', $a); 353 } 354 // Check the settings hasn't been already added 355 if (array_key_exists($dependentsetting->get_name(), $this->dependencies)) { 356 throw new base_setting_exception('setting_already_added'); 357 } 358 359 $options = (array)$options; 360 361 if (!array_key_exists('defaultvalue', $options)) { 362 $options['defaultvalue'] = false; 363 } 364 365 if ($type == null) { 366 switch ($this->vtype) { 367 case self::IS_BOOLEAN : 368 if ($this->get_ui_type() == self::UI_HTML_CHECKBOX) { 369 if ($this->value) { 370 $type = setting_dependency::DISABLED_NOT_CHECKED; 371 } else { 372 $type = setting_dependency::DISABLED_CHECKED; 373 } 374 } else { 375 if ($this->value) { 376 $type = setting_dependency::DISABLED_FALSE; 377 } else { 378 $type = setting_dependency::DISABLED_TRUE; 379 } 380 } 381 break; 382 case self::IS_FILENAME : 383 case self::IS_PATH : 384 case self::IS_INTEGER : 385 default : 386 $type = setting_dependency::DISABLED_VALUE; 387 break; 388 } 389 } 390 391 switch ($type) { 392 case setting_dependency::DISABLED_VALUE : 393 if (!array_key_exists('value', $options)) { 394 throw new base_setting_exception('dependency_needs_value'); 395 } 396 $dependency = new setting_dependency_disabledif_equals($this, $dependentsetting, $options['value'], $options['defaultvalue']); 397 break; 398 case setting_dependency::DISABLED_TRUE : 399 $dependency = new setting_dependency_disabledif_equals($this, $dependentsetting, true, $options['defaultvalue']); 400 break; 401 case setting_dependency::DISABLED_FALSE : 402 $dependency = new setting_dependency_disabledif_equals($this, $dependentsetting, false, $options['defaultvalue']); 403 break; 404 case setting_dependency::DISABLED_CHECKED : 405 $dependency = new setting_dependency_disabledif_checked($this, $dependentsetting, $options['defaultvalue']); 406 break; 407 case setting_dependency::DISABLED_NOT_CHECKED : 408 $dependency = new setting_dependency_disabledif_not_checked($this, $dependentsetting, $options['defaultvalue']); 409 break; 410 case setting_dependency::DISABLED_EMPTY : 411 $dependency = new setting_dependency_disabledif_empty($this, $dependentsetting, $options['defaultvalue']); 412 break; 413 case setting_dependency::DISABLED_NOT_EMPTY : 414 $dependency = new setting_dependency_disabledif_not_empty($this, $dependentsetting, $options['defaultvalue']); 415 break; 416 } 417 $this->dependencies[$dependentsetting->get_name()] = $dependency; 418 $dependency->get_dependent_setting()->register_dependent_dependency($dependency); 419 } 420 421 /** 422 * Get the PARAM_XXXX validation to be applied to the setting 423 * 424 * @return string The PARAM_XXXX constant of null if the setting type is not defined 425 */ 426 public function get_param_validation() { 427 switch ($this->vtype) { 428 case self::IS_BOOLEAN: 429 return PARAM_BOOL; 430 case self::IS_INTEGER: 431 return PARAM_INT; 432 case self::IS_FILENAME: 433 return PARAM_FILE; 434 case self::IS_PATH: 435 return PARAM_PATH; 436 case self::IS_TEXT: 437 return PARAM_TEXT; 438 } 439 return null; 440 } 441 442 // Protected API starts here 443 444 protected function validate_value($vtype, $value) { 445 if (is_null($value)) { // Nulls aren't validated 446 return null; 447 } 448 $oldvalue = $value; 449 switch ($vtype) { 450 case self::IS_BOOLEAN: 451 $value = clean_param($oldvalue, PARAM_BOOL); // Just clean 452 break; 453 case self::IS_INTEGER: 454 $value = clean_param($oldvalue, PARAM_INT); 455 if ($value != $oldvalue) { 456 throw new base_setting_exception('setting_invalid_integer', $oldvalue); 457 } 458 break; 459 case self::IS_FILENAME: 460 $value = clean_param($oldvalue, PARAM_FILE); 461 if ($value != $oldvalue) { 462 throw new base_setting_exception('setting_invalid_filename', $oldvalue); 463 } 464 break; 465 case self::IS_PATH: 466 $value = clean_param($oldvalue, PARAM_PATH); 467 if ($value != $oldvalue) { 468 throw new base_setting_exception('setting_invalid_path', $oldvalue); 469 } 470 break; 471 case self::IS_TEXT: 472 $value = clean_param($oldvalue, PARAM_TEXT); 473 if ($value != $oldvalue) { 474 throw new base_setting_exception('setting_invalid_text', $oldvalue); 475 } 476 break; 477 } 478 return $value; 479 } 480 481 protected function validate_visibility($visibility) { 482 if (is_null($visibility)) { 483 $visibility = self::VISIBLE; 484 } 485 if ($visibility !== self::VISIBLE && $visibility !== self::HIDDEN) { 486 throw new base_setting_exception('setting_invalid_visibility'); 487 } 488 return $visibility; 489 } 490 491 protected function validate_status($status) { 492 if (is_null($status)) { 493 $status = self::NOT_LOCKED; 494 } 495 if ($status !== self::NOT_LOCKED && $status !== self::LOCKED_BY_CONFIG && 496 $status !== self::LOCKED_BY_PERMISSION && $status !== self::LOCKED_BY_HIERARCHY) { 497 throw new base_setting_exception('setting_invalid_status', $status); 498 } 499 return $status; 500 } 501 502 protected function inform_dependencies($ctype, $oldv) { 503 foreach ($this->dependencies as $dependency) { 504 $dependency->process_change($ctype, $oldv); 505 } 506 } 507 508 protected function is_circular_reference($obj) { 509 // Get object dependencies recursively and check (by name) if $this is already there 510 $dependencies = $obj->get_dependencies(); 511 if (array_key_exists($this->name, $dependencies) || $obj == $this) { 512 return true; 513 } 514 // Recurse the dependent settings one by one 515 foreach ($dependencies as $dependency) { 516 if ($dependency->get_dependent_setting()->is_circular_reference($obj)) { 517 return true; 518 } 519 } 520 return false; 521 } 522 523 public function get_dependencies() { 524 return $this->dependencies; 525 } 526 527 public function get_ui_name() { 528 return $this->uisetting->get_name(); 529 } 530 531 public function get_ui_type() { 532 return $this->uisetting->get_type(); 533 } 534 535 /** 536 * Sets a help string for this setting 537 * 538 * @param string $identifier 539 * @param string $component 540 */ 541 public function set_help($identifier, $component='moodle') { 542 $this->help = array($identifier, $component); 543 } 544 545 /** 546 * Gets the help string params for this setting if it has been set 547 * @return array|false An array (identifier, component) or false if not set 548 */ 549 public function get_help() { 550 if ($this->has_help()) { 551 return $this->help; 552 } 553 return false; 554 } 555 556 /** 557 * Returns true if help has been set for this setting 558 * @return cool 559 */ 560 public function has_help() { 561 return (!empty($this->help)); 562 } 563 } 564 565 /* 566 * Exception class used by all the @setting_base stuff 567 */ 568 class base_setting_exception extends backup_exception { 569 570 public function __construct($errorcode, $a=NULL, $debuginfo=null) { 571 parent::__construct($errorcode, $a, $debuginfo); 572 } 573 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body