MDL-24962 backup - now destroying circular refs manually to help PHP 5.2
[moodle.git] / backup / util / structure / base_nested_element.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-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 */
26
27/**
28 * Abstract class representing one nestable element (non final) piece of information
29 */
30abstract class base_nested_element extends base_final_element {
31
32 /** @var array final elements of the element (maps to XML final elements of the tag) */
33 private $final_elements;
34
35 /** @var array children base_elements of this element (describes structure of the XML file) */
36 private $children;
37
38 /** @var base_optigroup optional group of this element (branches to be processed conditionally) */
39 private $optigroup;
40
41 /** @var array elements already used by the base_element, to avoid circular references */
42 private $used;
43
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 }
61
d0ad9860
EL
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 }
83
69dd0c8c
EL
84 protected function get_used() {
85 return $this->used;
86 }
87
88 protected function set_used($used) {
89 $this->used = $used;
90 }
91
92 protected function add_used($element) {
93 $this->used = array_merge($this->used, $element->get_used());
94 }
95
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 }
106
107 }
108
109/// Public API starts here
110
111 public function get_final_elements() {
112 return $this->final_elements;
113 }
114
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 }
122
123 public function get_children() {
124 return $this->children;
125 }
126
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 }
134
135 public function get_optigroup() {
136 return $this->optigroup;
137 }
138
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 }
161
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 }
173
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 }
188
189 public function get_value() {
190 throw new base_element_struct_exception('nestedelementnotvalue');
191 }
192
193 public function set_value($value) {
194 throw new base_element_struct_exception('nestedelementnotvalue');
195 }
196
197 public function clean_value() {
198 throw new base_element_struct_exception('nestedelementnotvalue');
199 }
200
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 }
217
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 }
235
236// Implementable API
237
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);
243}
244
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 */
252class base_element_struct_exception extends base_atom_exception {
253
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 }
264}