MDL-24962 backup - now destroying circular refs manually to help PHP 5.2
[moodle.git] / backup / util / structure / base_nested_element.class.php
1 <?php
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/>.
18 /**
19  * @package    moodlecore
20  * @subpackage backup-structure
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  * TODO: Finish phpdocs
25  */
27 /**
28  * Abstract class representing one nestable element (non final) piece of information
29  */
30 abstract class base_nested_element extends base_final_element {
32     /** @var array final elements of the element (maps to XML final elements of the tag) */
33     private $final_elements;
35     /** @var array children base_elements of this element (describes structure of the XML file) */
36     private $children;
38     /** @var base_optigroup optional group of this element (branches to be processed conditionally) */
39     private $optigroup;
41     /** @var array elements already used by the base_element, to avoid circular references */
42     private $used;
44     /**
45      * Constructor - instantiates one base_nested_element, specifying its basic info.
46      *
47      * @param string $name name of the element
48      * @param array  $attributes attributes this element will handle (optional, defaults to null)
49      * @param array  $final_elements this element will handle (optional, defaults to null)
50      */
51     public function __construct($name, $attributes = null, $final_elements = null) {
52         parent::__construct($name, $attributes);
53         $this->final_elements = array();
54         if (!empty($final_elements)) {
55             $this->add_final_elements($final_elements);
56         }
57         $this->children = array();
58         $this->optigroup = null;
59         $this->used[] = $name;
60     }
62     /**
63      * Destroy all circular references. It helps PHP 5.2 a lot!
64      */
65     public function destroy() {
66         // Before reseting anything, call destroy recursively
67         foreach ($this->children as $child) {
68             $child->destroy();
69         }
70         foreach ($this->final_elements as $element) {
71             $element->destroy();
72         }
73         if ($this->optigroup) {
74             $this->optigroup->destroy();
75         }
76         // Everything has been destroyed recursively, now we can reset safely
77         $this->children = array();
78         $this->final_elements = array();
79         $this->optigroup = null;
80         // Delegate to parent to destroy other bits
81         parent::destroy();
82     }
84     protected function get_used() {
85         return $this->used;
86     }
88     protected function set_used($used) {
89         $this->used = $used;
90     }
92     protected function add_used($element) {
93         $this->used = array_merge($this->used, $element->get_used());
94     }
96     protected function check_and_set_used($element) {
97         $grandparent = $this->get_grandoptigroupelement_or_grandparent();
98         if ($existing = array_intersect($grandparent->get_used(), $element->get_used())) { // Check the element isn't being used already
99             throw new base_element_struct_exception('baseelementexisting', implode($existing));
100         }
101         $grandparent->add_used($element);
102         // If the parent is one optigroup, add the element useds to it too
103         if ($grandparent->get_parent() instanceof base_optigroup) {
104             $grandparent->get_parent()->add_used($element);
105         }
107     }
109 /// Public API starts here
111     public function get_final_elements() {
112         return $this->final_elements;
113     }
115     public function get_final_element($name) {
116         if (array_key_exists($name, $this->final_elements)) {
117             return $this->final_elements[$name];
118         } else {
119             return null;
120         }
121     }
123     public function get_children() {
124         return $this->children;
125     }
127     public function get_child($name) {
128         if (array_key_exists($name, $this->children)) {
129             return $this->children[$name];
130         } else {
131             return null;
132         }
133     }
135     public function get_optigroup() {
136         return $this->optigroup;
137     }
139     public function add_final_elements($final_elements) {
140         if ($final_elements instanceof base_final_element || is_string($final_elements)) { // Accept 1 final_element, object or string
141             $final_elements = array($final_elements);
142         }
143         if (is_array($final_elements)) {
144             foreach ($final_elements as $final_element) {
145                 if (is_string($final_element)) { // Accept string final_elements
146                     $final_element = $this->get_new_final_element($final_element);
147                 }
148                 if (!($final_element instanceof base_final_element)) {
149                     throw new base_element_struct_exception('baseelementnofinalelement', get_class($final_element));
150                 }
151                 if (array_key_exists($final_element->get_name(), $this->final_elements)) {
152                     throw new base_element_struct_exception('baseelementexists', $final_element->get_name());
153                 }
154                 $this->final_elements[$final_element->get_name()] = $final_element;
155                 $final_element->set_parent($this);
156             }
157         } else {
158             throw new base_element_struct_exception('baseelementincorrect');
159         }
160     }
162     public function add_child($element) {
163         if (!($element instanceof base_nested_element)) { // parameter must be a base_nested_element
164             if (!$found = get_class($element)) {
165                 $found = 'non object';
166             }
167             throw new base_element_struct_exception('nestedelementincorrect', $found);
168         }
169         $this->check_and_set_used($element);
170         $this->children[$element->get_name()] = $element;
171         $element->set_parent($this);
172     }
174     public function add_optigroup($optigroup) {
175         if (!($optigroup instanceof base_optigroup)) { // parameter must be a base_optigroup
176             if (!$found = get_class($optigroup)) {
177                 $found = 'non object';
178             }
179             throw new base_element_struct_exception('optigroupincorrect', $found);
180         }
181         if ($this->optigroup !== null) {
182             throw new base_element_struct_exception('optigroupalreadyset', $found);
183         }
184         $this->check_and_set_used($optigroup);
185         $this->optigroup = $optigroup;
186         $optigroup->set_parent($this);
187     }
189     public function get_value() {
190         throw new base_element_struct_exception('nestedelementnotvalue');
191     }
193     public function set_value($value) {
194         throw new base_element_struct_exception('nestedelementnotvalue');
195     }
197     public function clean_value() {
198         throw new base_element_struct_exception('nestedelementnotvalue');
199     }
201     public function clean_values() {
202         parent::clean_values();
203         if (!empty($this->final_elements)) {
204             foreach ($this->final_elements as $final_element) {
205                 $final_element->clean_values();
206             }
207         }
208         if (!empty($this->children)) {
209             foreach ($this->children as $child) {
210                 $child->clean_values();
211             }
212         }
213         if (!empty($this->optigroup)) {
214             $this->optigroup->clean_values();
215         }
216     }
218     public function to_string($showvalue = false) {
219         $output = parent::to_string($showvalue);
220         if (!empty($this->final_elements)) {
221             foreach ($this->final_elements as $final_element) {
222                 $output .= PHP_EOL . $final_element->to_string($showvalue);
223             }
224         }
225         if (!empty($this->children)) {
226             foreach ($this->children as $child) {
227                 $output .= PHP_EOL . $child->to_string($showvalue);
228             }
229         }
230         if (!empty($this->optigroup)) {
231             $output .= PHP_EOL . $this->optigroup->to_string($showvalue);
232         }
233         return $output;
234     }
236 // Implementable API
238     /**
239      * Returns one instace of the @final_element class to work with
240      * when final_elements are added simply by name
241      */
242     abstract protected function get_new_final_element($name);
245 /**
246  * base_element exception to control all the errors while building the nested tree
247  *
248  * This exception will be thrown each time the base_element class detects some
249  * inconsistency related with the building of the nested tree representing one base part
250  * (invalid objects, circular references, double parents...)
251  */
252 class base_element_struct_exception extends base_atom_exception {
254     /**
255      * Constructor - instantiates one base_element_struct_exception
256      *
257      * @param string $errorcode key for the corresponding error string
258      * @param object $a extra words and phrases that might be required in the error string
259      * @param string $debuginfo optional debugging information
260      */
261     public function __construct($errorcode, $a = null, $debuginfo = null) {
262         parent::__construct($errorcode, $a, $debuginfo);
263     }