Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
16
 
17
/**
18
 * This class represent one XMLDB structure
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
 */
25
 
26
defined('MOODLE_INTERNAL') || die();
27
 
28
 
29
class xmldb_structure extends xmldb_object {
30
 
31
    /** @var string */
32
    protected $path;
33
 
34
    /** @var string */
35
    protected $version;
36
 
37
    /** @var xmldb_table[] tables */
38
    protected $tables;
39
 
40
    /**
41
     * Creates one new xmldb_structure
42
     * @param string $name
43
     */
44
    public function __construct($name) {
45
        parent::__construct($name);
46
        $this->path = null;
47
        $this->version = null;
48
        $this->tables = array();
49
    }
50
 
51
    /**
52
     * Returns the path of the structure
53
     * @return string
54
     */
55
    public function getPath() {
56
        return $this->path;
57
    }
58
 
59
    /**
60
     * Returns the version of the structure
61
     * @return string
62
     */
63
    public function getVersion() {
64
        return $this->version;
65
    }
66
 
67
    /**
68
     * Returns one xmldb_table
69
     * @param string $tablename
70
     * @return xmldb_table
71
     */
72
    public function getTable($tablename) {
73
        $i = $this->findTableInArray($tablename);
74
        if ($i !== null) {
75
            return $this->tables[$i];
76
        }
77
        return null;
78
    }
79
 
80
    /**
81
     * Returns the position of one table in the array.
82
     * @param string $tablename
83
     * @return mixed
84
     */
85
    public function findTableInArray($tablename) {
86
        foreach ($this->tables as $i => $table) {
87
            if ($tablename == $table->getName()) {
88
                return $i;
89
            }
90
        }
91
        return null;
92
    }
93
 
94
    /**
95
     * This function will reorder the array of tables
96
     * @return bool success
97
     */
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;
105
        }
106
    }
107
 
108
    /**
109
     * Returns the tables of the structure
110
     * @return array
111
     */
112
    public function getTables() {
113
        return $this->tables;
114
    }
115
 
116
    /**
117
     * Set the structure version
118
     * @param string version
119
     */
120
    public function setVersion($version) {
121
        $this->version = $version;
122
    }
123
 
124
    /**
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
129
     */
130
    public function addTable($table, $after=null) {
131
 
132
        // Calculate the previous and next tables
133
        $prevtable = null;
134
        $nexttable = null;
135
 
136
        if (!$after) {
137
            if ($this->tables) {
138
                end($this->tables);
139
                $prevtable = $this->tables[key($this->tables)];
140
            }
141
        } else {
142
            $prevtable = $this->getTable($after);
143
        }
144
        if ($prevtable && $prevtable->getNext()) {
145
            $nexttable = $this->getTable($prevtable->getNext());
146
        }
147
 
148
        // Set current table previous and next attributes
149
        if ($prevtable) {
150
            $table->setPrevious($prevtable->getName());
151
            $prevtable->setNext($table->getName());
152
        }
153
        if ($nexttable) {
154
            $table->setNext($nexttable->getName());
155
            $nexttable->setPrevious($table->getName());
156
        }
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);
169
    }
170
 
171
    /**
172
     * Delete one table from the Structure
173
     * @param string $tablename
174
     */
175
    public function deleteTable($tablename) {
176
 
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());
186
            }
187
            if ($nexttable) {
188
                $nexttable->setPrevious($table->getPrevious());
189
            }
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);
199
        }
200
    }
201
 
202
    /**
203
     * Set the tables
204
     * @param array $tables
205
     */
206
    public function setTables($tables) {
207
        $this->tables = $tables;
208
    }
209
 
210
    /**
211
     * Load data from XML to the structure
212
     * @param array $xmlarr
213
     * @return bool
214
     */
215
    public function arr2xmldb_structure($xmlarr) {
216
 
217
        global $CFG;
218
 
219
        $result = true;
220
 
221
        // Debug the structure
222
        // traverse_xmlize($xmlarr);                   //Debug
223
        // print_object ($GLOBALS['traverse_array']);  //Debug
224
        // $GLOBALS['traverse_array']="";              //Debug
225
 
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;
233
        }
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));
242
            }
243
        }
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;
250
        }
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;
257
        }
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;
266
        }
267
 
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;
273
                }
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;
282
                }
283
            }
284
        } else {
285
            $this->errormsg = 'Missing TABLES section';
286
            $this->debug($this->errormsg);
287
            $result = false;
288
        }
289
 
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;
297
            }
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;
305
            }
306
        }
307
 
308
        // Set some attributes
309
        if ($result) {
310
            $this->loaded = true;
311
        }
312
        $this->calculateHash();
313
        return $result;
314
    }
315
 
316
    /**
317
     * This function calculate and set the hash of one xmldb_structure
318
     * @param bool $recursive
319
     */
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);
330
                    }
331
                    $key .= $table->getHash();
332
                }
333
            }
334
            $this->hash = md5($key);
335
        }
336
    }
337
 
338
    /**
339
     * This function will output the XML text for one structure
340
     * @return string
341
     */
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, ENT_COMPAT) . '"'."\n";
348
        }
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();
359
            }
360
            $o.= '  </TABLES>' . "\n";
361
        }
362
        $o.= '</XMLDB>' . "\n";
363
 
364
        return $o;
365
    }
366
 
367
    /**
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
373
     */
374
    public function getTableUses($tablename) {
375
 
376
        $uses = array();
377
 
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();
388
                            }
389
                        }
390
                    }
391
                }
392
            }
393
        }
394
 
395
        // Return result
396
        if (!empty($uses)) {
397
            return $uses;
398
        } else {
399
            return false;
400
        }
401
    }
402
 
403
    /**
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
410
     */
411
    public function getFieldUses($tablename, $fieldname) {
412
 
413
        $uses = array();
414
 
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();
422
                }
423
            }
424
        }
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();
431
                }
432
            }
433
        }
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();
446
                                }
447
                            }
448
                        }
449
                    }
450
                }
451
            }
452
        }
453
 
454
        // Return result
455
        if (!empty($uses)) {
456
            return $uses;
457
        } else {
458
            return false;
459
        }
460
    }
461
 
462
    /**
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
469
     */
470
    public function getKeyUses($tablename, $keyname) {
471
 
472
        $uses = array();
473
 
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;
485
                        }
486
                        if ($key->getRefTable() == $tablename &&
487
                            implode(',', $key->getRefFields()) == implode(',', $mykey->getFields())) {
488
                                $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
489
                        }
490
                    }
491
                }
492
            }
493
        }
494
 
495
        // Return result
496
        if (!empty($uses)) {
497
            return $uses;
498
        } else {
499
            return false;
500
        }
501
    }
502
 
503
    /**
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
510
     */
511
    public function getIndexUses($tablename, $indexname) {
512
 
513
        $uses = array();
514
 
515
        // Nothing to check, because indexes haven't uses! Leave it here
516
        // for future checks...
517
 
518
        // Return result
519
        if (!empty($uses)) {
520
            return $uses;
521
        } else {
522
            return false;
523
        }
524
    }
525
 
526
    /**
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
531
     */
532
    public function getAllErrors() {
533
 
534
        $errors = array();
535
        // First the structure itself
536
        if ($this->getError()) {
537
            $errors[] = $this->getError();
538
        }
539
        // Delegate to tables
540
        if ($this->tables) {
541
            foreach ($this->tables as $table) {
542
                if ($tableerrors = $table->getAllErrors()) {
543
 
544
                }
545
            }
546
            // Add them to the errors array
547
            if ($tableerrors) {
548
                $errors = array_merge($errors, $tableerrors);
549
            }
550
        }
551
        // Return decision
552
        if (count($errors)) {
553
            return $errors;
554
        } else {
555
            return false;
556
        }
557
    }
558
}