Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** This class represent one XMLDB table** @package core_xmldb* @copyright 1999 onwards Martin Dougiamas http://dougiamas.com* 2001-3001 Eloy Lafuente (stronk7) http://contiento.com* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/defined('MOODLE_INTERNAL') || die();class xmldb_table extends xmldb_object {/** @var xmldb_field[] table columns */protected $fields;/** @var xmldb_key[] keys */protected $keys;/** @var xmldb_index[] indexes */protected $indexes;/** @var int max length of table name prefixes */const PREFIX_MAX_LENGTH = 10;/*** Note:* - PostgreSQL has a limit of 63 ascii chars (bytes) for table names. Others have greater limits.* Up to PREFIX_MAX_LENGTH ascii chars (bytes) are reserved for table prefixes.** @var int max length of table names (without prefix).*/const NAME_MAX_LENGTH = 63 - self::PREFIX_MAX_LENGTH;/*** Creates one new xmldb_table* @param string $name*/public function __construct($name) {parent::__construct($name);$this->fields = array();$this->keys = array();$this->indexes = array();}/*** Add one field to the table, allowing to specify the desired order* If it's not specified, then the field is added at the end* @param xmldb_field $field* @param xmldb_object $after* @return xmldb_field*/public function addField($field, $after=null) {// Detect duplicates firstif ($this->getField($field->getName())) {throw new coding_exception('Duplicate field '.$field->getName().' specified in table '.$this->getName());}// Calculate the previous and next fields$prevfield = null;$nextfield = null;if (!$after) {$allfields = $this->getFields();if (!empty($allfields)) {end($allfields);$prevfield = $allfields[key($allfields)];}} else {$prevfield = $this->getField($after);}if ($prevfield && $prevfield->getNext()) {$nextfield = $this->getField($prevfield->getNext());}// Set current field previous and next attributesif ($prevfield) {$field->setPrevious($prevfield->getName());$prevfield->setNext($field->getName());}if ($nextfield) {$field->setNext($nextfield->getName());$nextfield->setPrevious($field->getName());}// Some more attributes$field->setLoaded(true);$field->setChanged(true);// Add the new field$this->fields[] = $field;// Reorder the field$this->orderFields($this->fields);// Recalculate the hash$this->calculateHash(true);// We have one new field, so the table has changed$this->setChanged(true);return $field;}/*** Add one key to the table, allowing to specify the desired order* If it's not specified, then the key is added at the end* @param xmldb_key $key* @param xmldb_object $after*/public function addKey($key, $after=null) {// Detect duplicates firstif ($this->getKey($key->getName())) {throw new coding_exception('Duplicate key '.$key->getName().' specified in table '.$this->getName());}// Make sure there are no indexes with the key column specs because they would collide.$newfields = $key->getFields();$allindexes = $this->getIndexes();foreach ($allindexes as $index) {$fields = $index->getFields();if ($fields === $newfields) {throw new coding_exception('Index '.$index->getName().' collides with key'.$key->getName().' specified in table '.$this->getName());}}// Calculate the previous and next keys$prevkey = null;$nextkey = null;if (!$after) {$allkeys = $this->getKeys();if (!empty($allkeys)) {end($allkeys);$prevkey = $allkeys[key($allkeys)];}} else {$prevkey = $this->getKey($after);}if ($prevkey && $prevkey->getNext()) {$nextkey = $this->getKey($prevkey->getNext());}// Set current key previous and next attributesif ($prevkey) {$key->setPrevious($prevkey->getName());$prevkey->setNext($key->getName());}if ($nextkey) {$key->setNext($nextkey->getName());$nextkey->setPrevious($key->getName());}// Some more attributes$key->setLoaded(true);$key->setChanged(true);// Add the new key$this->keys[] = $key;// Reorder the keys$this->orderKeys($this->keys);// Recalculate the hash$this->calculateHash(true);// We have one new field, so the table has changed$this->setChanged(true);}/*** Add one index to the table, allowing to specify the desired order* If it's not specified, then the index is added at the end* @param xmldb_index $index* @param xmldb_object $after*/public function addIndex($index, $after=null) {// Detect duplicates firstif ($this->getIndex($index->getName())) {throw new coding_exception('Duplicate index '.$index->getName().' specified in table '.$this->getName());}// Make sure there are no keys with the index column specs because they would collide.$newfields = $index->getFields();$allkeys = $this->getKeys();foreach ($allkeys as $key) {$fields = $key->getFields();if ($fields === $newfields) {throw new coding_exception('Key '.$key->getName().' collides with index'.$index->getName().' specified in table '.$this->getName());}}// Calculate the previous and next indexes$previndex = null;$nextindex = null;if (!$after) {$allindexes = $this->getIndexes();if (!empty($allindexes)) {end($allindexes);$previndex = $allindexes[key($allindexes)];}} else {$previndex = $this->getIndex($after);}if ($previndex && $previndex->getNext()) {$nextindex = $this->getIndex($previndex->getNext());}// Set current index previous and next attributesif ($previndex) {$index->setPrevious($previndex->getName());$previndex->setNext($index->getName());}if ($nextindex) {$index->setNext($nextindex->getName());$nextindex->setPrevious($index->getName());}// Some more attributes$index->setLoaded(true);$index->setChanged(true);// Add the new index$this->indexes[] = $index;// Reorder the indexes$this->orderIndexes($this->indexes);// Recalculate the hash$this->calculateHash(true);// We have one new index, so the table has changed$this->setChanged(true);}/*** This function will return the array of fields in the table* @return xmldb_field[]*/public function getFields() {return $this->fields;}/*** This function will return the array of keys in the table* @return xmldb_key[]*/public function getKeys() {return $this->keys;}/*** This function will return the array of indexes in the table* @return xmldb_index[]*/public function getIndexes() {return $this->indexes;}/*** Returns one xmldb_field* @param string $fieldname* @return xmldb_field|null*/public function getField($fieldname) {$i = $this->findFieldInArray($fieldname);if ($i !== null) {return $this->fields[$i];}return null;}/*** Returns the position of one field in the array.* @param string $fieldname* @return int|null index of the field, or null if not found.*/public function findFieldInArray($fieldname) {foreach ($this->fields as $i => $field) {if ($fieldname == $field->getName()) {return $i;}}return null;}/*** This function will reorder the array of fields* @return bool whether the reordering succeeded.*/public function orderFields() {$result = $this->orderElements($this->fields);if ($result) {$this->setFields($result);return true;} else {return false;}}/*** Returns one xmldb_key* @param string $keyname* @return xmldb_key|null*/public function getKey($keyname) {$i = $this->findKeyInArray($keyname);if ($i !== null) {return $this->keys[$i];}return null;}/*** Returns the position of one key in the array.* @param string $keyname* @return int|null index of the key, or null if not found.*/public function findKeyInArray($keyname) {foreach ($this->keys as $i => $key) {if ($keyname == $key->getName()) {return $i;}}return null;}/*** This function will reorder the array of keys* @return bool whether the reordering succeeded.*/public function orderKeys() {$result = $this->orderElements($this->keys);if ($result) {$this->setKeys($result);return true;} else {return false;}}/*** Returns one xmldb_index* @param string $indexname* @return xmldb_index|null*/public function getIndex($indexname) {$i = $this->findIndexInArray($indexname);if ($i !== null) {return $this->indexes[$i];}return null;}/*** Returns the position of one index in the array.* @param string $indexname* @return int|null index of the index, or null if not found.*/public function findIndexInArray($indexname) {foreach ($this->indexes as $i => $index) {if ($indexname == $index->getName()) {return $i;}}return null;}/*** This function will reorder the array of indexes* @return bool whether the reordering succeeded.*/public function orderIndexes() {$result = $this->orderElements($this->indexes);if ($result) {$this->setIndexes($result);return true;} else {return false;}}/*** This function will set the array of fields in the table* @param xmldb_field[] $fields*/public function setFields($fields) {$this->fields = $fields;}/*** This function will set the array of keys in the table* @param xmldb_key[] $keys*/public function setKeys($keys) {$this->keys = $keys;}/*** This function will set the array of indexes in the table* @param xmldb_index[] $indexes*/public function setIndexes($indexes) {$this->indexes = $indexes;}/*** Delete one field from the table* @param string $fieldname*/public function deleteField($fieldname) {$field = $this->getField($fieldname);if ($field) {$i = $this->findFieldInArray($fieldname);// Look for prev and next field$prevfield = $this->getField($field->getPrevious());$nextfield = $this->getField($field->getNext());// Change their previous and next attributesif ($prevfield) {$prevfield->setNext($field->getNext());}if ($nextfield) {$nextfield->setPrevious($field->getPrevious());}// Delete the fieldunset($this->fields[$i]);// Reorder the whole structure$this->orderFields($this->fields);// Recalculate the hash$this->calculateHash(true);// We have one deleted field, so the table has changed$this->setChanged(true);}}/*** Delete one key from the table* @param string $keyname*/public function deleteKey($keyname) {$key = $this->getKey($keyname);if ($key) {$i = $this->findKeyInArray($keyname);// Look for prev and next key$prevkey = $this->getKey($key->getPrevious());$nextkey = $this->getKey($key->getNext());// Change their previous and next attributesif ($prevkey) {$prevkey->setNext($key->getNext());}if ($nextkey) {$nextkey->setPrevious($key->getPrevious());}// Delete the keyunset($this->keys[$i]);// Reorder the Keys$this->orderKeys($this->keys);// Recalculate the hash$this->calculateHash(true);// We have one deleted key, so the table has changed$this->setChanged(true);}}/*** Delete one index from the table* @param string $indexname*/public function deleteIndex($indexname) {$index = $this->getIndex($indexname);if ($index) {$i = $this->findIndexInArray($indexname);// Look for prev and next index$previndex = $this->getIndex($index->getPrevious());$nextindex = $this->getIndex($index->getNext());// Change their previous and next attributesif ($previndex) {$previndex->setNext($index->getNext());}if ($nextindex) {$nextindex->setPrevious($index->getPrevious());}// Delete the indexunset($this->indexes[$i]);// Reorder the indexes$this->orderIndexes($this->indexes);// Recalculate the hash$this->calculateHash(true);// We have one deleted index, so the table has changed$this->setChanged(true);}}/*** Load data from XML to the table* @param array $xmlarr* @return bool success*/public function arr2xmldb_table($xmlarr) {global $CFG;$result = true;// Debug the table// traverse_xmlize($xmlarr); //Debug// print_object ($GLOBALS['traverse_array']); //Debug// $GLOBALS['traverse_array']=""; //Debug// Process table attributes (name, comment, previoustable and nexttable)if (isset($xmlarr['@']['NAME'])) {$this->name = trim($xmlarr['@']['NAME']);} else {$this->errormsg = 'Missing NAME attribute';$this->debug($this->errormsg);$result = false;}if (isset($xmlarr['@']['COMMENT'])) {$this->comment = trim($xmlarr['@']['COMMENT']);} else if (!empty($CFG->xmldbdisablecommentchecking)) {$this->comment = '';} else {$this->errormsg = 'Missing COMMENT attribute';$this->debug($this->errormsg);$result = false;}// Iterate over fieldsif (isset($xmlarr['#']['FIELDS']['0']['#']['FIELD'])) {foreach ($xmlarr['#']['FIELDS']['0']['#']['FIELD'] as $xmlfield) {if (!$result) { //Skip on errorcontinue;}$name = trim($xmlfield['@']['NAME']);$field = new xmldb_field($name);$field->arr2xmldb_field($xmlfield);$this->fields[] = $field;if (!$field->isLoaded()) {$this->errormsg = 'Problem loading field ' . $name;$this->debug($this->errormsg);$result = false;}}} else {$this->errormsg = 'Missing FIELDS section';$this->debug($this->errormsg);$result = false;}// Perform some general checks over fieldsif ($result && $this->fields) {// Check field names are ok (lowercase, a-z _-)if (!$this->checkNameValues($this->fields)) {$this->errormsg = 'Some FIELDS name values are incorrect';$this->debug($this->errormsg);$result = false;}// Compute prev/next.$this->fixPrevNext($this->fields);// Order fieldsif ($result && !$this->orderFields($this->fields)) {$this->errormsg = 'Error ordering the fields';$this->debug($this->errormsg);$result = false;}}// Iterate over keysif (isset($xmlarr['#']['KEYS']['0']['#']['KEY'])) {foreach ($xmlarr['#']['KEYS']['0']['#']['KEY'] as $xmlkey) {if (!$result) { //Skip on errorcontinue;}$name = trim($xmlkey['@']['NAME']);$key = new xmldb_key($name);$key->arr2xmldb_key($xmlkey);$this->keys[] = $key;if (!$key->isLoaded()) {$this->errormsg = 'Problem loading key ' . $name;$this->debug($this->errormsg);$result = false;}}} else {$this->errormsg = 'Missing KEYS section (at least one PK must exist)';$this->debug($this->errormsg);$result = false;}// Perform some general checks over keysif ($result && $this->keys) {// Check keys names are ok (lowercase, a-z _-)if (!$this->checkNameValues($this->keys)) {$this->errormsg = 'Some KEYS name values are incorrect';$this->debug($this->errormsg);$result = false;}// Compute prev/next.$this->fixPrevNext($this->keys);// Order keysif ($result && !$this->orderKeys($this->keys)) {$this->errormsg = 'Error ordering the keys';$this->debug($this->errormsg);$result = false;}// TODO: Only one PK// TODO: Not keys with repeated fields// TODO: Check fields and reffieds exist in table}// Iterate over indexesif (isset($xmlarr['#']['INDEXES']['0']['#']['INDEX'])) {foreach ($xmlarr['#']['INDEXES']['0']['#']['INDEX'] as $xmlindex) {if (!$result) { //Skip on errorcontinue;}$name = trim($xmlindex['@']['NAME']);$index = new xmldb_index($name);$index->arr2xmldb_index($xmlindex);$this->indexes[] = $index;if (!$index->isLoaded()) {$this->errormsg = 'Problem loading index ' . $name;$this->debug($this->errormsg);$result = false;}}}// Perform some general checks over indexesif ($result && $this->indexes) {// Check field names are ok (lowercase, a-z _-)if (!$this->checkNameValues($this->indexes)) {$this->errormsg = 'Some INDEXES name values are incorrect';$this->debug($this->errormsg);$result = false;}// Compute prev/next.$this->fixPrevNext($this->indexes);// Order indexesif ($result && !$this->orderIndexes($this->indexes)) {$this->errormsg = 'Error ordering the indexes';$this->debug($this->errormsg);$result = false;}// TODO: Not indexes with repeated fields// TODO: Check fields exist in table}// Set some attributesif ($result) {$this->loaded = true;}$this->calculateHash();return $result;}/*** This function calculate and set the hash of one xmldb_table* @param bool $recursive*/public function calculateHash($recursive = false) {if (!$this->loaded) {$this->hash = null;} else {$key = $this->name . $this->comment;if ($this->fields) {foreach ($this->fields as $fie) {$field = $this->getField($fie->getName());if ($recursive) {$field->calculateHash($recursive);}$key .= $field->getHash();}}if ($this->keys) {foreach ($this->keys as $ke) {$k = $this->getKey($ke->getName());if ($recursive) {$k->calculateHash($recursive);}$key .= $k->getHash();}}if ($this->indexes) {foreach ($this->indexes as $in) {$index = $this->getIndex($in->getName());if ($recursive) {$index->calculateHash($recursive);}$key .= $index->getHash();}}$this->hash = md5($key);}}/*** Validates the table restrictions (does not validate child elements).** The error message should not be localised because it is intended for developers,* end users and admins should never see these problems!** @param xmldb_table $xmldb_table optional when object is table* @return string null if ok, error message if problem found*/public function validateDefinition(xmldb_table $xmldb_table=null) {// table parameter is ignored$name = $this->getName();if (strlen($name) > self::NAME_MAX_LENGTH) {return 'Invalid table name {'.$name.'}: name is too long. Limit is '.self::NAME_MAX_LENGTH.' chars.';}if (!preg_match('/^[a-z][a-z0-9_]*$/', $name)) {return 'Invalid table name {'.$name.'}: name includes invalid characters.';}return null;}/*** This function will output the XML text for one table* @return string*/public function xmlOutput() {$o = '';$o.= ' <TABLE NAME="' . $this->name . '"';if ($this->comment) {$o.= ' COMMENT="' . htmlspecialchars($this->comment, ENT_COMPAT) . '"';}$o.= '>' . "\n";// Now the fieldsif ($this->fields) {$o.= ' <FIELDS>' . "\n";foreach ($this->fields as $field) {$o.= $field->xmlOutput();}$o.= ' </FIELDS>' . "\n";}// Now the keysif ($this->keys) {$o.= ' <KEYS>' . "\n";foreach ($this->keys as $key) {$o.= $key->xmlOutput();}$o.= ' </KEYS>' . "\n";}// Now the indexesif ($this->indexes) {$o.= ' <INDEXES>' . "\n";foreach ($this->indexes as $index) {$o.= $index->xmlOutput();}$o.= ' </INDEXES>' . "\n";}$o.= ' </TABLE>' . "\n";return $o;}/*** This function will add one new field to the table with all* its attributes defined** @param string $name name of the field* @param int $type XMLDB_TYPE_INTEGER, XMLDB_TYPE_NUMBER, XMLDB_TYPE_CHAR, XMLDB_TYPE_TEXT, XMLDB_TYPE_BINARY* @param string $precision length for integers and chars, two-comma separated numbers for numbers* @param bool $unsigned XMLDB_UNSIGNED or null (or false)* @param bool $notnull XMLDB_NOTNULL or null (or false)* @param bool $sequence XMLDB_SEQUENCE or null (or false)* @param mixed $default meaningful default o null (or false)* @param xmldb_object $previous name of the previous field in the table or null (or false)* @return xmlddb_field*/public function add_field($name, $type, $precision=null, $unsigned=null, $notnull=null, $sequence=null, $default=null, $previous=null) {$field = new xmldb_field($name, $type, $precision, $unsigned, $notnull, $sequence, $default);$this->addField($field, $previous);return $field;}/*** This function will add one new key to the table with all* its attributes defined** @param string $name name of the key* @param int $type XMLDB_KEY_PRIMARY, XMLDB_KEY_UNIQUE, XMLDB_KEY_FOREIGN* @param array $fields an array of fieldnames to build the key over* @param string $reftable name of the table the FK points to or null* @param array $reffields an array of fieldnames in the FK table or null*/public function add_key($name, $type, $fields, $reftable=null, $reffields=null) {$key = new xmldb_key($name, $type, $fields, $reftable, $reffields);$this->addKey($key);}/*** This function will add one new index to the table with all* its attributes defined** @param string $name name of the index* @param int $type XMLDB_INDEX_UNIQUE, XMLDB_INDEX_NOTUNIQUE* @param array $fields an array of fieldnames to build the index over* @param array $hints optional index type hints*/public function add_index($name, $type, $fields, $hints = array()) {$index = new xmldb_index($name, $type, $fields, $hints);$this->addIndex($index);}/*** This function will return all the errors found in one table* looking recursively inside each field/key/index. Returns* an array of errors or false*/public function getAllErrors() {$errors = array();// First the table itselfif ($this->getError()) {$errors[] = $this->getError();}// Delegate to fieldsif ($fields = $this->getFields()) {foreach ($fields as $field) {if ($field->getError()) {$errors[] = $field->getError();}}}// Delegate to keysif ($keys = $this->getKeys()) {foreach ($keys as $key) {if ($key->getError()) {$errors[] = $key->getError();}}}// Delegate to indexesif ($indexes = $this->getIndexes()) {foreach ($indexes as $index) {if ($index->getError()) {$errors[] = $index->getError();}}}// Return decisionif (count($errors)) {return $errors;} else {return false;}}}