MDL-24962 backup - now destroying circular refs manually to help PHP 5.2
[moodle.git] / backup / util / structure / base_final_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 final element atom (name/value/parent) piece of information
29 */
30abstract class base_final_element extends base_atom {
31
32 /** @var array base_attributes of the element (maps to XML attributes of the tag) */
33 private $attributes;
34
35 /** @var base_nested_element parent of this element (describes structure of the XML file) */
36 private $parent;
37
38 /**
39 * Constructor - instantiates one base_final_element, specifying its basic info.
40 *
41 * @param string $name name of the element
42 * @param array $attributes attributes this element will handle (optional, defaults to null)
43 */
44 public function __construct($name, $attributes = null) {
45 parent::__construct($name);
46 $this->attributes = array();
47 if (!empty($attributes)) {
48 $this->add_attributes($attributes);
49 }
50 $this->parent = null;
51 }
52
d0ad9860
EL
53 /**
54 * Destroy all circular references. It helps PHP 5.2 a lot!
55 */
56 public function destroy() {
57 // No need to destroy anything recursively here, direct reset
58 $this->attributes = array();
59 $this->parent = null;
60 }
61
69dd0c8c
EL
62 protected function set_parent($element) {
63 if ($this->parent) {
64 $info = new stdClass();
65 $info->currparent= $this->parent->get_name();
66 $info->newparent = $element->get_name();
67 $info->element = $this->get_name();
68 throw new base_element_parent_exception('baseelementhasparent', $info);
69 }
70 $this->parent = $element;
71 }
72
73 protected function get_grandparent() {
74 $parent = $this->parent;
75 if ($parent instanceof base_nested_element) {
76 return $parent->get_grandparent();
77 } else {
78 return $this;
79 }
80 }
81
82 protected function get_grandoptigroupelement_or_grandparent() {
83 $parent = $this->parent;
84 if ($parent instanceof base_optigroup) {
85 return $this; // Have found one parent optigroup, so I (first child of optigroup) am
86 } else if ($parent instanceof base_nested_element) {
87 return $parent->get_grandoptigroupelement_or_grandparent(); // Continue searching
88 } else {
89 return $this;
90 }
91 }
92
93 protected function find_element_by_path($path) {
94 $patharr = explode('/', trim($path, '/')); // Split the path trimming slashes
95 if (substr($path, 0, 1) == '/') { // Absolute path, go to grandparent and process
96 if (!$this->get_grandparent() instanceof base_nested_element) {
97 throw new base_element_struct_exception('baseelementincorrectgrandparent', $patharr[0]);
98 } else if ($this->get_grandparent()->get_name() !== $patharr[0]) {
99 throw new base_element_struct_exception('baseelementincorrectgrandparent', $patharr[0]);
100 } else {
101 $newpath = implode('/', array_slice($patharr, 1)); // Take out 1st element
102 return $this->get_grandparent()->find_element_by_path($newpath); // Process as relative in grandparent
103 }
104 } else {
105 if ($patharr[0] == '..') { // Go to parent
106 if (!$this->get_parent() instanceof base_nested_element) {
107 throw new base_element_struct_exception('baseelementincorrectparent', $patharr[0]);
108 } else {
109 $newpath = implode('/', array_slice($patharr, 1)); // Take out 1st element
110 return $this->get_parent()->find_element_by_path($newpath); // Process as relative in parent
111 }
112 } else if (count($patharr) > 1) { // Go to next child
113 if (!$this->get_child($patharr[0]) instanceof base_nested_element) {
114 throw new base_element_struct_exception('baseelementincorrectchild', $patharr[0]);
115 } else {
116 $newpath = implode('/', array_slice($patharr, 1)); // Take out 1st element
117 return $this->get_child($patharr[0])->find_element_by_path($newpath); // Process as relative in parent
118 }
119 } else { // Return final element or attribute
120 if ($this->get_final_element($patharr[0]) instanceof base_final_element) {
121 return $this->get_final_element($patharr[0]);
122 } else if ($this->get_attribute($patharr[0]) instanceof base_attribute) {
123 return $this->get_attribute($patharr[0]);
124 } else {
125 throw new base_element_struct_exception('baseelementincorrectfinalorattribute', $patharr[0]);
126 }
127 }
128 }
129 }
130
131 protected function find_first_parent_by_name($name) {
132 if ($parent = $this->get_parent()) { // If element has parent
133 $element = $parent->get_final_element($name); // Look for name into parent finals
134 $attribute = $parent->get_attribute($name); // Look for name into parent attrs
135 if ($element instanceof base_final_element) {
136 return $element;
137
138 } else if ($attribute instanceof base_attribute) {
139 return $attribute;
140
141 } else { // Not found, go up 1 level and continue searching
142 return $parent->find_first_parent_by_name($name);
143 }
144 } else { // No more parents available, return the original backup::VAR_PARENTID, exception
145 throw new base_element_struct_exception('cannotfindparentidforelement', $name);
146 }
147 }
148
149
150/// Public API starts here
151
152 public function get_attributes() {
153 return $this->attributes;
154 }
155
156 public function get_attribute($name) {
157 if (array_key_exists($name, $this->attributes)) {
158 return $this->attributes[$name];
159 } else {
160 return null;
161 }
162 }
163
164 public function get_parent() {
165 return $this->parent;
166 }
167
168 public function get_level() {
169 return $this->parent == null ? 1 : $this->parent->get_level() + 1;
170 }
171
172 public function add_attributes($attributes) {
173 if ($attributes instanceof base_attribute || is_string($attributes)) { // Accept 1 attribute, object or string
174 $attributes = array($attributes);
175 }
176 if (is_array($attributes)) {
177 foreach ($attributes as $attribute) {
178 if (is_string($attribute)) { // Accept string attributes
179 $attribute = $this->get_new_attribute($attribute);
180 }
181 if (!($attribute instanceof base_attribute)) {
182 throw new base_element_attribute_exception('baseelementnoattribute', get_class($attribute));
183 }
184 if (array_key_exists($attribute->get_name(), $this->attributes)) {
185 throw new base_element_attribute_exception('baseelementattributeexists', $attribute->get_name());
186 }
187 $this->attributes[$attribute->get_name()] = $attribute;
188 }
189 } else {
190 throw new base_element_attribute_exception('baseelementattributeincorrect');
191 }
192 }
193
194 public function clean_values() {
195 parent::clean_value();
196 if (!empty($this->attributes)) {
197 foreach ($this->attributes as $attribute) {
198 $attribute->clean_value();
199 }
200 }
201 }
202
203 public function to_string($showvalue = false) {
204 // Decide the correct prefix
205 $prefix = '#'; // default
206 if ($this->parent instanceof base_optigroup) {
207 $prefix = '?';
208 } else if ($this instanceof base_nested_element) {
209 $prefix = '';
210 }
211 $indent = str_repeat(' ', $this->get_level()); // Indent output based in level (4cc)
212 $output = $indent . $prefix . $this->get_name() . ' (level: ' . $this->get_level() . ')';
213 if ($showvalue) {
214 $value = $this->is_set() ? $this->get_value() : 'not set';
215 $output .= ' => ' . $value;
216 }
217 if (!empty($this->attributes)) {
218 foreach ($this->attributes as $attribute) {
219 $output .= PHP_EOL . $indent . ' ' . $attribute->to_string($showvalue);
220 }
221 }
222 return $output;
223 }
224
225// Implementable API
226
227 /**
228 * Returns one instace of the @base_attribute class to work with
229 * when attributes are added simply by name
230 */
231 abstract protected function get_new_attribute($name);
232}
233
234/**
235 * base_element exception to control all the errors related with parents handling
236 */
237class base_element_parent_exception extends base_atom_exception {
238
239 /**
240 * Constructor - instantiates one base_element_parent_exception
241 *
242 * @param string $errorcode key for the corresponding error string
243 * @param object $a extra words and phrases that might be required in the error string
244 * @param string $debuginfo optional debugging information
245 */
246 public function __construct($errorcode, $a = null, $debuginfo = null) {
247 parent::__construct($errorcode, $a, $debuginfo);
248 }
249}
250
251/**
252 * base_element exception to control all the errors related with attributes handling
253 */
254class base_element_attribute_exception extends base_atom_exception {
255
256 /**
257 * Constructor - instantiates one base_element_attribute_exception
258 *
259 * @param string $errorcode key for the corresponding error string
260 * @param object $a extra words and phrases that might be required in the error string
261 * @param string $debuginfo optional debugging information
262 */
263 public function __construct($errorcode, $a = null, $debuginfo = null) {
264 parent::__construct($errorcode, $a, $debuginfo);
265 }
266}