MDL-37726 stop using PREVIOUS/NEXT in install.xml files
[moodle.git] / lib / xmldb / xmldb_object.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * This class represent the XMLDB base class where all the common pieces are defined
19  *
20  * @package    core_xmldb
21  * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
22  *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
29 class xmldb_object {
31     /** @var string name of obejct */
32     protected $name;
34     /** @var string comment on object */
35     protected $comment;
37     /** @var xmldb_object */
38     protected $previous;
40     /** @var xmldb_object */
41     protected $next;
43     /** @var string hash of object */
44     protected $hash;
46     /** @var bool is it loaded yet */
47     protected $loaded;
49     /** @var bool was object changed */
50     protected $changed;
52     /** @var string error message */
53     protected $errormsg;
55     /**
56      * Creates one new xmldb_object
57      * @param string $name
58      */
59     public function __construct($name) {
60         $this->name = $name;
61         $this->comment = null;
62         $this->previous = null;
63         $this->next = null;
64         $this->hash = null;
65         $this->loaded = false;
66         $this->changed = false;
67         $this->errormsg = null;
68     }
70     /**
71      * This function returns true/false, if the xmldb_object has been loaded
72      * @return bool
73      */
74     public function isLoaded() {
75         return $this->loaded;
76     }
78     /**
79      * This function returns true/false, if the xmldb_object has changed
80      * @return bool
81      */
82     public function hasChanged() {
83         return $this->changed;
84     }
86     /**
87      * This function returns the comment of one xmldb_object
88      * @return string
89      */
90     public function getComment() {
91         return $this->comment;
92     }
94     /**
95      * This function returns the hash of one xmldb_object
96      * @return string
97      */
98     public function getHash() {
99         return $this->hash;
100     }
102     /**
103      * This function will return the name of the previous xmldb_object
104      * @return xmldb_object
105      */
106     public function getPrevious() {
107         return $this->previous;
108     }
110     /**
111      * This function will return the name of the next xmldb_object
112      * @return xmldb_object
113      */
114     public function getNext() {
115         return $this->next;
116     }
118     /**
119      * This function will return the name of the xmldb_object
120      * @return string
121      */
122     public function getName() {
123         return $this->name;
124     }
126     /**
127      * This function will return the error detected in the object
128      * @return string
129      */
130     public function getError() {
131         return $this->errormsg;
132     }
134     /**
135      * This function will set the comment of the xmldb_object
136      * @param string $comment
137      */
138     public function setComment($comment) {
139         $this->comment = $comment;
140     }
142     /**
143      * This function will set the previous of the xmldb_object
144      * @param xmldb_object $previous
145      */
146     public function setPrevious($previous) {
147         $this->previous = $previous;
148     }
150     /**
151      * This function will set the next of the xmldb_object
152      * @param xmldb_object $next
153      */
154     public function setNext($next) {
155         $this->next = $next;
156     }
158     /**
159      * This function will set the hash of the xmldb_object
160      * @param string $hash
161      */
162     public function setHash($hash) {
163         $this->hash = $hash;
164     }
166     /**
167      * This function will set the loaded field of the xmldb_object
168      * @param bool $loaded
169      */
170     public function setLoaded($loaded = true) {
171         $this->loaded = $loaded;
172     }
174     /**
175      * This function will set the changed field of the xmldb_object
176      * @param bool $changed
177      */
178     public function setChanged($changed = true) {
179         $this->changed = $changed;
180     }
181     /**
182      * This function will set the name field of the xmldb_object
183      * @param string $name
184      */
185     public function setName($name) {
186         $this->name = $name;
187     }
190     /**
191      * This function will check if one key name is ok or no (true/false)
192      * only lowercase a-z, 0-9 and _ are allowed
193      * @return bool
194      */
195     public function checkName () {
196         $result = true;
198         if ($this->name != preg_replace('/[^a-z0-9_ -]/i', '', $this->name)) {
199             $result = false;
200         }
201         return $result;
202     }
204     /**
205      * This function will check that all the elements in one array
206      * have a correct name [a-z0-9_]
207      * @param array $arr
208      * @return bool
209      */
210     public function checkNameValues($arr) {
211         $result = true;
212         // TODO: Perhaps, add support for reserved words
214         // Check the name only contains valid chars
215         if ($arr) {
216             foreach($arr as $element) {
217                 if (!$element->checkName()) {
218                     $result = false;
219                 }
220             }
221         }
222         // Check there aren't duplicate names
223         if ($arr) {
224             $existing_fields = array();
225             foreach($arr as $element) {
226                 if (in_array($element->getName(), $existing_fields)) {
227                     debugging('Object ' . $element->getName() . ' is duplicated!', DEBUG_DEVELOPER);
228                     $result = false;
229                 }
230                 $existing_fields[] = $element->getName();
231             }
232         }
233         return $result;
234     }
236     /**
237      * Reconstruct previous/next attributes.
238      * @param array $arr
239      * @return bool true if $arr modified
240      */
241     public function fixPrevNext(&$arr) {
242         $tweaked = false;
244         $prev = null;
245         foreach ($arr as $key=>$el) {
246             $prev_value = $arr[$key]->previous;
247             $next_value = $arr[$key]->next;
249             $arr[$key]->next     = null;
250             $arr[$key]->previous = null;
251             if ($prev !== null) {
252                 $arr[$prev]->next    = $arr[$key]->name;
253                 $arr[$key]->previous = $arr[$prev]->name;
254             }
255             $prev = $key;
257             if ($prev_value != $arr[$key]->previous or $next_value != $arr[$key]->next) {
258                 $tweaked = true;
259             }
260         }
262         return $tweaked;
263     }
265     /**
266      * This function will order all the elements in one array, following
267      * the previous/next rules
268      * @param array $arr
269      * @return array|bool
270      */
271     public function orderElements($arr) {
272         $result = true;
274         // Create a new array
275         $newarr = array();
276         if (!empty($arr)) {
277             $currentelement = null;
278             // Get the element without previous
279             foreach($arr as $key => $element) {
280                 if (!$element->getPrevious()) {
281                     $currentelement = $arr[$key];
282                     $newarr[0] = $arr[$key];
283                 }
284             }
285             if (!$currentelement) {
286                 $result = false;
287             }
288             // Follow the next rules
289             $counter = 1;
290             while ($result && $currentelement->getNext()) {
291                 $i = $this->findObjectInArray($currentelement->getNext(), $arr);
292                 $currentelement = $arr[$i];
293                 $newarr[$counter] = $arr[$i];
294                 $counter++;
295             }
296             // Compare number of elements between original and new array
297             if ($result && count($arr) != count($newarr)) {
298                 $result = false;
299             } else if ($newarr) {
300                 $result = $newarr;
301             } else {
302                 $result = false;
303             }
304         } else {
305             $result = array();
306         }
307         return $result;
308     }
310     /**
311      * Returns the position of one object in the array.
312      * @param string $objectname
313      * @param array $arr
314      * @return mixed
315      */
316     public function findObjectInArray($objectname, $arr) {
317         foreach ($arr as $i => $object) {
318             if ($objectname == $object->getName()) {
319                 return $i;
320             }
321         }
322         return null;
323     }
325     /**
326      * This function will display a readable info about the xmldb_object
327      * (should be implemented inside each XMLDBxxx object)
328      * @return string
329      */
330     public function readableInfo() {
331         return get_class($this);
332     }
334     /**
335      * This function will perform the central debug of all the XMLDB classes
336      * being called automatically every time one error is found. Apart from
337      * the main actions performed in it (XMLDB agnostic) it looks for one
338      * function called xmldb_debug() and invokes it, passing both the
339      * message code and the whole object.
340      * So, to perform custom debugging just add such function to your libs.
341      *
342      * Call to the external hook function can be disabled by request by
343      * defining XMLDB_SKIP_DEBUG_HOOK
344      * @param string $message
345      */
346     public function debug($message) {
348         // Check for xmldb_debug($message, $xmldb_object)
349         $funcname = 'xmldb_debug';
350         // If exists and XMLDB_SKIP_DEBUG_HOOK is undefined
351         if (function_exists($funcname) && !defined('XMLDB_SKIP_DEBUG_HOOK')) {
352             $funcname($message, $this);
353         }
354     }
356     /**
357      * Returns one array of elements from one comma separated string,
358      * supporting quoted strings containing commas and concat function calls
359      * @param string $string
360      * @return array
361      */
362     public function comma2array($string) {
364         $foundquotes  = array();
365         $foundconcats = array();
367         // Extract all the concat elements from the string
368         preg_match_all("/(CONCAT\(.*?\))/is", $string, $matches);
369         foreach (array_unique($matches[0]) as $key=>$value) {
370             $foundconcats['<#'.$key.'#>'] = $value;
371         }
372         if (!empty($foundconcats)) {
373             $string = str_replace($foundconcats,array_keys($foundconcats),$string);
374         }
376         // Extract all the quoted elements from the string (skipping
377         // backslashed quotes that are part of the content.
378         preg_match_all("/(''|'.*?[^\\\\]')/is", $string, $matches);
379         foreach (array_unique($matches[0]) as $key=>$value) {
380             $foundquotes['<%'.$key.'%>'] = $value;
381         }
382         if (!empty($foundquotes)) {
383             $string = str_replace($foundquotes,array_keys($foundquotes),$string);
384         }
386         // Explode safely the string
387         $arr = explode (',', $string);
389         // Put the concat and quoted elements back again, trimming every element
390         if ($arr) {
391             foreach ($arr as $key => $element) {
392                 // Clear some spaces
393                 $element = trim($element);
394                 // Replace the quoted elements if exists
395                 if (!empty($foundquotes)) {
396                     $element = str_replace(array_keys($foundquotes), $foundquotes, $element);
397                 }
398                 // Replace the concat elements if exists
399                 if (!empty($foundconcats)) {
400                     $element = str_replace(array_keys($foundconcats), $foundconcats, $element);
401                 }
402                 // Delete any backslash used for quotes. XMLDB stuff will add them before insert
403                 $arr[$key] = str_replace("\\'", "'", $element);
404             }
405         }
407         return $arr;
408     }
410     /**
411      * Validates the definition of objects and returns error message.
412      *
413      * The error message should not be localised because it is intended for developers,
414      * end users and admins should never see these problems!
415      *
416      * @param xmldb_table $xmldb_table optional when object is table
417      * @return string null if ok, error message if problem found
418      */
419     public function validateDefinition(xmldb_table $xmldb_table=null) {
420         return null;
421     }