MDL-34011 Lesson module: fixed incorrect display of student attempt for short answer...
[moodle.git] / lib / xmldb / xmldb_structure.php
blob678a7d6e7c3c7eb480d2ab3112dc05da2cd043e8
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 if (isset($xmlarr['XMLDB']['@']['VERSION'])) {
235 $this->version = trim($xmlarr['XMLDB']['@']['VERSION']);
236 } else {
237 $this->errormsg = 'Missing VERSION attribute';
238 $this->debug($this->errormsg);
239 $result = false;
241 if (isset($xmlarr['XMLDB']['@']['COMMENT'])) {
242 $this->comment = trim($xmlarr['XMLDB']['@']['COMMENT']);
243 } else if (!empty($CFG->xmldbdisablecommentchecking)) {
244 $this->comment = '';
245 } else {
246 $this->errormsg = 'Missing COMMENT attribute';
247 $this->debug($this->errormsg);
248 $result = false;
251 // Iterate over tables
252 if (isset($xmlarr['XMLDB']['#']['TABLES']['0']['#']['TABLE'])) {
253 foreach ($xmlarr['XMLDB']['#']['TABLES']['0']['#']['TABLE'] as $xmltable) {
254 if (!$result) { //Skip on error
255 continue;
257 $name = trim($xmltable['@']['NAME']);
258 $table = new xmldb_table($name);
259 $table->arr2xmldb_table($xmltable);
260 $this->tables[] = $table;
261 if (!$table->isLoaded()) {
262 $this->errormsg = 'Problem loading table ' . $name;
263 $this->debug($this->errormsg);
264 $result = false;
267 } else {
268 $this->errormsg = 'Missing TABLES section';
269 $this->debug($this->errormsg);
270 $result = false;
273 // Perform some general checks over tables
274 if ($result && $this->tables) {
275 // Check tables names are ok (lowercase, a-z _-)
276 if (!$this->checkNameValues($this->tables)) {
277 $this->errormsg = 'Some TABLES name values are incorrect';
278 $this->debug($this->errormsg);
279 $result = false;
281 // Check previous & next are ok (duplicates and existing tables)
282 $this->fixPrevNext($this->tables);
283 if ($result && !$this->checkPreviousNextValues($this->tables)) {
284 $this->errormsg = 'Some TABLES previous/next values are incorrect';
285 $this->debug($this->errormsg);
286 $result = false;
288 // Order tables
289 if ($result && !$this->orderTables($this->tables)) {
290 $this->errormsg = 'Error ordering the tables';
291 $this->debug($this->errormsg);
292 $result = false;
296 // Set some attributes
297 if ($result) {
298 $this->loaded = true;
300 $this->calculateHash();
301 return $result;
305 * This function calculate and set the hash of one xmldb_structure
306 * @param bool $recursive
308 public function calculateHash($recursive = false) {
309 if (!$this->loaded) {
310 $this->hash = null;
311 } else {
312 $key = $this->name . $this->path . $this->comment;
313 if ($this->tables) {
314 foreach ($this->tables as $tbl) {
315 $table = $this->getTable($tbl->getName());
316 if ($recursive) {
317 $table->calculateHash($recursive);
319 $key .= $table->getHash();
322 $this->hash = md5($key);
327 * This function will output the XML text for one structure
328 * @return string
330 public function xmlOutput() {
331 $o = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
332 $o.= '<XMLDB PATH="' . $this->path . '"';
333 $o.= ' VERSION="' . $this->version . '"';
334 if ($this->comment) {
335 $o.= ' COMMENT="' . htmlspecialchars($this->comment) . '"'."\n";
337 $rel = array_fill(0, count(explode('/', $this->path)), '..');
338 $rel = implode('/', $rel);
339 $o.= ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n";
340 $o.= ' xsi:noNamespaceSchemaLocation="'.$rel.'/lib/xmldb/xmldb.xsd"'."\n";
341 $o.= '>' . "\n";
342 // Now the tables
343 if ($this->tables) {
344 $o.= ' <TABLES>' . "\n";
345 foreach ($this->tables as $table) {
346 $o.= $table->xmlOutput();
348 $o.= ' </TABLES>' . "\n";
350 $o.= '</XMLDB>';
352 return $o;
356 * This function returns the number of uses of one table inside
357 * a whole XMLDStructure. Useful to detect if the table must be
358 * locked. Return false if no uses are found.
359 * @param string $tablename
360 * @return mixed
362 public function getTableUses($tablename) {
364 $uses = array();
366 // Check if some foreign key in the whole structure is using it
367 // (by comparing the reftable with the tablename)
368 if ($this->tables) {
369 foreach ($this->tables as $table) {
370 $keys = $table->getKeys();
371 if ($keys) {
372 foreach ($keys as $key) {
373 if ($key->getType() == XMLDB_KEY_FOREIGN) {
374 if ($tablename == $key->getRefTable()) {
375 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
383 // Return result
384 if (!empty($uses)) {
385 return $uses;
386 } else {
387 return false;
392 * This function returns the number of uses of one field inside
393 * a whole xmldb_structure. Useful to detect if the field must be
394 * locked. Return false if no uses are found.
395 * @param string $tablename
396 * @param string $fieldname
397 * @return mixed
399 public function getFieldUses($tablename, $fieldname) {
401 $uses = array();
403 // Check if any key in the table is using it
404 $table = $this->getTable($tablename);
405 if ($keys = $table->getKeys()) {
406 foreach ($keys as $key) {
407 if (in_array($fieldname, $key->getFields()) ||
408 in_array($fieldname, $key->getRefFields())) {
409 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
413 // Check if any index in the table is using it
414 $table = $this->getTable($tablename);
415 if ($indexes = $table->getIndexes()) {
416 foreach ($indexes as $index) {
417 if (in_array($fieldname, $index->getFields())) {
418 $uses[] = 'table ' . $table->getName() . ' index ' . $index->getName();
422 // Check if some foreign key in the whole structure is using it
423 // By comparing the reftable and refields with the field)
424 if ($this->tables) {
425 foreach ($this->tables as $table) {
426 $keys = $table->getKeys();
427 if ($keys) {
428 foreach ($keys as $key) {
429 if ($key->getType() == XMLDB_KEY_FOREIGN) {
430 if ($tablename == $key->getRefTable()) {
431 $reffieds = $key->getRefFields();
432 if (in_array($fieldname, $key->getRefFields())) {
433 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
442 // Return result
443 if (!empty($uses)) {
444 return $uses;
445 } else {
446 return false;
451 * This function returns the number of uses of one key inside
452 * a whole xmldb_structure. Useful to detect if the key must be
453 * locked. Return false if no uses are found.
454 * @param string $tablename
455 * @param string $keyname
456 * @return mixed
458 public function getKeyUses($tablename, $keyname) {
460 $uses = array();
462 // Check if some foreign key in the whole structure is using it
463 // (by comparing the reftable and reffields with the fields in the key)
464 $mytable = $this->getTable($tablename);
465 $mykey = $mytable->getKey($keyname);
466 if ($this->tables && $mykey) {
467 foreach ($this->tables as $table) {
468 $allkeys = $table->getKeys();
469 if ($allkeys) {
470 foreach ($allkeys as $key) {
471 if ($key->getType() != XMLDB_KEY_FOREIGN) {
472 continue;
474 if ($key->getRefTable() == $tablename &&
475 implode(',', $key->getRefFields()) == implode(',', $mykey->getFields())) {
476 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
483 // Return result
484 if (!empty($uses)) {
485 return $uses;
486 } else {
487 return false;
492 * This function returns the number of uses of one index inside
493 * a whole xmldb_structure. Useful to detect if the index must be
494 * locked. Return false if no uses are found.
495 * @param string $tablename
496 * @param string $indexname
497 * @return mixed
499 public function getIndexUses($tablename, $indexname) {
501 $uses = array();
503 // Nothing to check, because indexes haven't uses! Leave it here
504 // for future checks...
506 // Return result
507 if (!empty($uses)) {
508 return $uses;
509 } else {
510 return false;
515 * This function will return all the errors found in one structure
516 * looking recursively inside each table. Returns
517 * an array of errors or false
518 * @return mixed
520 public function getAllErrors() {
522 $errors = array();
523 // First the structure itself
524 if ($this->getError()) {
525 $errors[] = $this->getError();
527 // Delegate to tables
528 if ($this->tables) {
529 foreach ($this->tables as $table) {
530 if ($tableerrors = $table->getAllErrors()) {
534 // Add them to the errors array
535 if ($tableerrors) {
536 $errors = array_merge($errors, $tableerrors);
539 // Return decision
540 if (count($errors)) {
541 return $errors;
542 } else {
543 return false;