Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}