NOBUG: Fixed file access permissions
[moodle.git] / lib / xmldb / xmldb_structure.php
blobad681efcd79d6a8dd059fdd073bf2fc4d7797116
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.
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 one XMLDB structure
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
26 defined('MOODLE_INTERNAL') || die();
29 class xmldb_structure extends xmldb_object {
31 /** @var string */
32 protected $path;
34 /** @var string */
35 protected $version;
37 /** @var array tables */
38 protected $tables;
40 /**
41 * Creates one new xmldb_structure
42 * @param string $name
44 public function __construct($name) {
45 parent::__construct($name);
46 $this->path = null;
47 $this->version = null;
48 $this->tables = array();
51 /**
52 * Returns the path of the structure
53 * @return string
55 public function getPath() {
56 return $this->path;
59 /**
60 * Returns the version of the structure
61 * @return string
63 public function getVersion() {
64 return $this->version;
67 /**
68 * Returns one xmldb_table
69 * @param string $tablename
70 * @return xmldb_table
72 public function getTable($tablename) {
73 $i = $this->findTableInArray($tablename);
74 if ($i !== null) {
75 return $this->tables[$i];
77 return null;
80 /**
81 * Returns the position of one table in the array.
82 * @param string $tablename
83 * @return mixed
85 public function findTableInArray($tablename) {
86 foreach ($this->tables as $i => $table) {
87 if ($tablename == $table->getName()) {
88 return $i;
91 return null;
94 /**
95 * This function will reorder the array of tables
96 * @return bool success
98 public function orderTables() {
99 $result = $this->orderElements($this->tables);
100 if ($result) {
101 $this->setTables($result);
102 return true;
103 } else {
104 return false;
109 * Returns the tables of the structure
110 * @return array
112 public function getTables() {
113 return $this->tables;
117 * Set the structure version
118 * @param string version
120 public function setVersion($version) {
121 $this->version = $version;
125 * Add one table to the structure, allowing to specify the desired order
126 * If it's not specified, then the table is added at the end.
127 * @param xmldb_table $table
128 * @param mixed $after
130 public function addTable($table, $after=null) {
132 // Calculate the previous and next tables
133 $prevtable = null;
134 $nexttable = null;
136 if (!$after) {
137 if ($this->tables) {
138 end($this->tables);
139 $prevtable = $this->tables[key($this->tables)];
141 } else {
142 $prevtable = $this->getTable($after);
144 if ($prevtable && $prevtable->getNext()) {
145 $nexttable = $this->getTable($prevtable->getNext());
148 // Set current table previous and next attributes
149 if ($prevtable) {
150 $table->setPrevious($prevtable->getName());
151 $prevtable->setNext($table->getName());
153 if ($nexttable) {
154 $table->setNext($nexttable->getName());
155 $nexttable->setPrevious($table->getName());
157 // Some more attributes
158 $table->setLoaded(true);
159 $table->setChanged(true);
160 // Add the new table
161 $this->tables[] = $table;
162 // Reorder the whole structure
163 $this->orderTables($this->tables);
164 // Recalculate the hash
165 $this->calculateHash(true);
166 // We have one new table, so the structure has changed
167 $this->setVersion(userdate(time(), '%Y%m%d', 99, false));
168 $this->setChanged(true);
172 * Delete one table from the Structure
173 * @param string $tablename
175 public function deleteTable($tablename) {
177 $table = $this->getTable($tablename);
178 if ($table) {
179 $i = $this->findTableInArray($tablename);
180 // Look for prev and next table
181 $prevtable = $this->getTable($table->getPrevious());
182 $nexttable = $this->getTable($table->getNext());
183 // Change their previous and next attributes
184 if ($prevtable) {
185 $prevtable->setNext($table->getNext());
187 if ($nexttable) {
188 $nexttable->setPrevious($table->getPrevious());
190 // Delete the table
191 unset($this->tables[$i]);
192 // Reorder the tables
193 $this->orderTables($this->tables);
194 // Recalculate the hash
195 $this->calculateHash(true);
196 // We have one deleted table, so the structure has changed
197 $this->setVersion(userdate(time(), '%Y%m%d', 99, false));
198 $this->setChanged(true);
203 * Set the tables
204 * @param array $tables
206 public function setTables($tables) {
207 $this->tables = $tables;
211 * Load data from XML to the structure
212 * @param array $xmlarr
213 * @return bool
215 public function arr2xmldb_structure($xmlarr) {
217 global $CFG;
219 $result = true;
221 // Debug the structure
222 // traverse_xmlize($xmlarr); //Debug
223 // print_object ($GLOBALS['traverse_array']); //Debug
224 // $GLOBALS['traverse_array']=""; //Debug
226 // Process structure attributes (path, comment and version)
227 if (isset($xmlarr['XMLDB']['@']['PATH'])) {
228 $this->path = trim($xmlarr['XMLDB']['@']['PATH']);
229 } else {
230 $this->errormsg = 'Missing PATH attribute';
231 $this->debug($this->errormsg);
232 $result = false;
234 // Normalize paths to compare them.
235 $filepath = realpath($this->name); // File path comes in name.
236 $filename = basename($filepath);
237 $normalisedpath = $this->path;
238 if ($CFG->admin !== 'admin') {
239 $needle = 'admin/';
240 if (strpos($this->path, $needle) === 0) {
241 $normalisedpath = substr_replace($this->path, "$CFG->admin/", 0, strlen($needle));
244 $structurepath = realpath($CFG->dirroot . DIRECTORY_SEPARATOR . $normalisedpath . DIRECTORY_SEPARATOR . $filename);
245 if ($filepath !== $structurepath) {
246 $relativepath = dirname(str_replace(realpath($CFG->dirroot) . DIRECTORY_SEPARATOR, '', $filepath));
247 $this->errormsg = 'PATH attribute does not match file directory: ' . $relativepath;
248 $this->debug($this->errormsg);
249 $result = false;
251 if (isset($xmlarr['XMLDB']['@']['VERSION'])) {
252 $this->version = trim($xmlarr['XMLDB']['@']['VERSION']);
253 } else {
254 $this->errormsg = 'Missing VERSION attribute';
255 $this->debug($this->errormsg);
256 $result = false;
258 if (isset($xmlarr['XMLDB']['@']['COMMENT'])) {
259 $this->comment = trim($xmlarr['XMLDB']['@']['COMMENT']);
260 } else if (!empty($CFG->xmldbdisablecommentchecking)) {
261 $this->comment = '';
262 } else {
263 $this->errormsg = 'Missing COMMENT attribute';
264 $this->debug($this->errormsg);
265 $result = false;
268 // Iterate over tables
269 if (isset($xmlarr['XMLDB']['#']['TABLES']['0']['#']['TABLE'])) {
270 foreach ($xmlarr['XMLDB']['#']['TABLES']['0']['#']['TABLE'] as $xmltable) {
271 if (!$result) { //Skip on error
272 continue;
274 $name = trim($xmltable['@']['NAME']);
275 $table = new xmldb_table($name);
276 $table->arr2xmldb_table($xmltable);
277 $this->tables[] = $table;
278 if (!$table->isLoaded()) {
279 $this->errormsg = 'Problem loading table ' . $name;
280 $this->debug($this->errormsg);
281 $result = false;
284 } else {
285 $this->errormsg = 'Missing TABLES section';
286 $this->debug($this->errormsg);
287 $result = false;
290 // Perform some general checks over tables
291 if ($result && $this->tables) {
292 // Check tables names are ok (lowercase, a-z _-)
293 if (!$this->checkNameValues($this->tables)) {
294 $this->errormsg = 'Some TABLES name values are incorrect';
295 $this->debug($this->errormsg);
296 $result = false;
298 // Compute prev/next.
299 $this->fixPrevNext($this->tables);
300 // Order tables
301 if ($result && !$this->orderTables($this->tables)) {
302 $this->errormsg = 'Error ordering the tables';
303 $this->debug($this->errormsg);
304 $result = false;
308 // Set some attributes
309 if ($result) {
310 $this->loaded = true;
312 $this->calculateHash();
313 return $result;
317 * This function calculate and set the hash of one xmldb_structure
318 * @param bool $recursive
320 public function calculateHash($recursive = false) {
321 if (!$this->loaded) {
322 $this->hash = null;
323 } else {
324 $key = $this->name . $this->path . $this->comment;
325 if ($this->tables) {
326 foreach ($this->tables as $tbl) {
327 $table = $this->getTable($tbl->getName());
328 if ($recursive) {
329 $table->calculateHash($recursive);
331 $key .= $table->getHash();
334 $this->hash = md5($key);
339 * This function will output the XML text for one structure
340 * @return string
342 public function xmlOutput() {
343 $o = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
344 $o.= '<XMLDB PATH="' . $this->path . '"';
345 $o.= ' VERSION="' . $this->version . '"';
346 if ($this->comment) {
347 $o.= ' COMMENT="' . htmlspecialchars($this->comment) . '"'."\n";
349 $rel = array_fill(0, count(explode('/', $this->path)), '..');
350 $rel = implode('/', $rel);
351 $o.= ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n";
352 $o.= ' xsi:noNamespaceSchemaLocation="'.$rel.'/lib/xmldb/xmldb.xsd"'."\n";
353 $o.= '>' . "\n";
354 // Now the tables
355 if ($this->tables) {
356 $o.= ' <TABLES>' . "\n";
357 foreach ($this->tables as $table) {
358 $o.= $table->xmlOutput();
360 $o.= ' </TABLES>' . "\n";
362 $o.= '</XMLDB>';
364 return $o;
368 * This function returns the number of uses of one table inside
369 * a whole XMLDStructure. Useful to detect if the table must be
370 * locked. Return false if no uses are found.
371 * @param string $tablename
372 * @return mixed
374 public function getTableUses($tablename) {
376 $uses = array();
378 // Check if some foreign key in the whole structure is using it
379 // (by comparing the reftable with the tablename)
380 if ($this->tables) {
381 foreach ($this->tables as $table) {
382 $keys = $table->getKeys();
383 if ($keys) {
384 foreach ($keys as $key) {
385 if ($key->getType() == XMLDB_KEY_FOREIGN) {
386 if ($tablename == $key->getRefTable()) {
387 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
395 // Return result
396 if (!empty($uses)) {
397 return $uses;
398 } else {
399 return false;
404 * This function returns the number of uses of one field inside
405 * a whole xmldb_structure. Useful to detect if the field must be
406 * locked. Return false if no uses are found.
407 * @param string $tablename
408 * @param string $fieldname
409 * @return mixed
411 public function getFieldUses($tablename, $fieldname) {
413 $uses = array();
415 // Check if any key in the table is using it
416 $table = $this->getTable($tablename);
417 if ($keys = $table->getKeys()) {
418 foreach ($keys as $key) {
419 if (in_array($fieldname, $key->getFields()) ||
420 in_array($fieldname, $key->getRefFields())) {
421 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
425 // Check if any index in the table is using it
426 $table = $this->getTable($tablename);
427 if ($indexes = $table->getIndexes()) {
428 foreach ($indexes as $index) {
429 if (in_array($fieldname, $index->getFields())) {
430 $uses[] = 'table ' . $table->getName() . ' index ' . $index->getName();
434 // Check if some foreign key in the whole structure is using it
435 // By comparing the reftable and refields with the field)
436 if ($this->tables) {
437 foreach ($this->tables as $table) {
438 $keys = $table->getKeys();
439 if ($keys) {
440 foreach ($keys as $key) {
441 if ($key->getType() == XMLDB_KEY_FOREIGN) {
442 if ($tablename == $key->getRefTable()) {
443 $reffieds = $key->getRefFields();
444 if (in_array($fieldname, $key->getRefFields())) {
445 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
454 // Return result
455 if (!empty($uses)) {
456 return $uses;
457 } else {
458 return false;
463 * This function returns the number of uses of one key inside
464 * a whole xmldb_structure. Useful to detect if the key must be
465 * locked. Return false if no uses are found.
466 * @param string $tablename
467 * @param string $keyname
468 * @return mixed
470 public function getKeyUses($tablename, $keyname) {
472 $uses = array();
474 // Check if some foreign key in the whole structure is using it
475 // (by comparing the reftable and reffields with the fields in the key)
476 $mytable = $this->getTable($tablename);
477 $mykey = $mytable->getKey($keyname);
478 if ($this->tables && $mykey) {
479 foreach ($this->tables as $table) {
480 $allkeys = $table->getKeys();
481 if ($allkeys) {
482 foreach ($allkeys as $key) {
483 if ($key->getType() != XMLDB_KEY_FOREIGN) {
484 continue;
486 if ($key->getRefTable() == $tablename &&
487 implode(',', $key->getRefFields()) == implode(',', $mykey->getFields())) {
488 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
495 // Return result
496 if (!empty($uses)) {
497 return $uses;
498 } else {
499 return false;
504 * This function returns the number of uses of one index inside
505 * a whole xmldb_structure. Useful to detect if the index must be
506 * locked. Return false if no uses are found.
507 * @param string $tablename
508 * @param string $indexname
509 * @return mixed
511 public function getIndexUses($tablename, $indexname) {
513 $uses = array();
515 // Nothing to check, because indexes haven't uses! Leave it here
516 // for future checks...
518 // Return result
519 if (!empty($uses)) {
520 return $uses;
521 } else {
522 return false;
527 * This function will return all the errors found in one structure
528 * looking recursively inside each table. Returns
529 * an array of errors or false
530 * @return mixed
532 public function getAllErrors() {
534 $errors = array();
535 // First the structure itself
536 if ($this->getError()) {
537 $errors[] = $this->getError();
539 // Delegate to tables
540 if ($this->tables) {
541 foreach ($this->tables as $table) {
542 if ($tableerrors = $table->getAllErrors()) {
546 // Add them to the errors array
547 if ($tableerrors) {
548 $errors = array_merge($errors, $tableerrors);
551 // Return decision
552 if (count($errors)) {
553 return $errors;
554 } else {
555 return false;