2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
18 * This class represent one XMLDB Field
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_field
extends xmldb_object
{
31 /** @var int XMLDB_TYPE_ constants */
34 /** @var int size of field */
37 /** @var bool is null forbidden? XMLDB_NOTNULL */
40 /** @var mixed default value */
43 /** @var bool use automatic counter */
46 /** @var int number of decimals */
51 * - Oracle: VARCHAR2 has a limit of 4000 bytes
52 * - SQL Server: NVARCHAR has a limit of 40000 chars
53 * - MySQL: VARCHAR 65,535 chars
54 * - PostgreSQL: no limit
56 * @const maximum length of text field
58 const CHAR_MAX_LENGTH
= 1333;
62 * @const maximum number of digits of integers
64 const INTEGER_MAX_LENGTH
= 20;
67 * @const max length (precision, the total number of digits) of decimals
69 const NUMBER_MAX_LENGTH
= 38;
72 * @const max length of floats
74 const FLOAT_MAX_LENGTH
= 20;
78 * - Oracle has 30 chars limit for all names
80 * @const maximumn length of field names
82 const NAME_MAX_LENGTH
= 30;
85 * Creates one new xmldb_field
86 * @param string $name of field
87 * @param int $type XMLDB_TYPE_INTEGER, XMLDB_TYPE_NUMBER, XMLDB_TYPE_CHAR, XMLDB_TYPE_TEXT, XMLDB_TYPE_BINARY
88 * @param string $precision length for integers and chars, two-comma separated numbers for numbers
89 * @param bool $unsigned XMLDB_UNSIGNED or null (or false)
90 * @param bool $notnull XMLDB_NOTNULL or null (or false)
91 * @param bool $sequence XMLDB_SEQUENCE or null (or false)
92 * @param mixed $default meaningful default o null (or false)
93 * @param string $previous
95 public function __construct($name, $type=null, $precision=null, $unsigned=null, $notnull=null, $sequence=null, $default=null, $previous=null) {
98 $this->notnull
= false;
99 $this->default = null;
100 $this->sequence
= false;
101 $this->decimals
= null;
102 parent
::__construct($name);
103 $this->set_attributes($type, $precision, $unsigned, $notnull, $sequence, $default, $previous);
107 * Set all the attributes of one xmldb_field
109 * @param int $type XMLDB_TYPE_INTEGER, XMLDB_TYPE_NUMBER, XMLDB_TYPE_CHAR, XMLDB_TYPE_TEXT, XMLDB_TYPE_BINARY
110 * @param string $precision length for integers and chars, two-comma separated numbers for numbers
111 * @param bool $unsigned XMLDB_UNSIGNED or null (or false)
112 * @param bool $notnull XMLDB_NOTNULL or null (or false)
113 * @param bool $sequence XMLDB_SEQUENCE or null (or false)
114 * @param mixed $default meaningful default o null (or false)
115 * @param string $previous
117 public function set_attributes($type, $precision=null, $unsigned=null, $notnull=null, $sequence=null, $default=null, $previous=null) {
120 // LOBs (BINARY OR TEXT) don't support any precision (neither length or decimals).
121 if ($type == XMLDB_TYPE_BINARY ||
$this->type
== XMLDB_TYPE_TEXT
) {
122 $this->length
= null;
123 $this->decimals
= null;
125 } else if (!is_null($precision)) {
126 // Try to split the not null precision into length and decimals and apply each one as needed.
127 $precisionarr = explode(',', $precision);
128 if (isset($precisionarr[0])) {
129 $this->length
= trim($precisionarr[0]);
131 if (isset($precisionarr[1])) {
132 $this->decimals
= trim($precisionarr[1]);
136 $this->notnull
= !empty($notnull) ?
true : false;
137 $this->sequence
= !empty($sequence) ?
true : false;
138 $this->setDefault($default);
140 $this->previous
= $previous;
147 public function getType() {
155 public function getLength() {
156 return $this->length
;
163 public function getDecimals() {
164 return $this->decimals
;
171 public function getNotNull() {
172 return $this->notnull
;
177 * @deprecated since moodle 2.3
180 public function getUnsigned() {
188 public function getSequence() {
189 return $this->sequence
;
196 public function getDefault() {
197 return $this->default;
204 public function setType($type) {
209 * Set the field length
212 public function setLength($length) {
213 $this->length
= $length;
217 * Set the field decimals
220 public function setDecimals($decimals) {
221 $this->decimals
= $decimals;
225 * Set the field unsigned
226 * @deprecated since moodle 2.3
227 * @param bool $unsigned
229 public function setUnsigned($unsigned=true) {
233 * Set the field notnull
234 * @param bool $notnull
236 public function setNotNull($notnull=true) {
237 $this->notnull
= $notnull;
241 * Set the field sequence
242 * @param bool $sequence
244 public function setSequence($sequence=true) {
245 $this->sequence
= $sequence;
249 * Set the field default
250 * @param mixed $default
252 public function setDefault($default) {
253 // Check, warn and auto-fix '' (empty) defaults for CHAR NOT NULL columns, changing them
254 // to NULL so XMLDB will apply the proper default
255 if ($this->type
== XMLDB_TYPE_CHAR
&& $this->notnull
&& $default === '') {
256 $this->errormsg
= 'XMLDB has detected one CHAR NOT NULL column (' . $this->name
. ") with '' (empty string) as DEFAULT value. This type of columns must have one meaningful DEFAULT declared or none (NULL). XMLDB have fixed it automatically changing it to none (NULL). The process will continue ok and proper defaults will be created accordingly with each DB requirements. Please fix it in source (XML and/or upgrade script) to avoid this message to be displayed.";
257 $this->debug($this->errormsg
);
260 // Check, warn and autofix TEXT|BINARY columns having a default clause (only null is allowed)
261 if (($this->type
== XMLDB_TYPE_TEXT ||
$this->type
== XMLDB_TYPE_BINARY
) && $default !== null) {
262 $this->errormsg
= 'XMLDB has detected one TEXT/BINARY column (' . $this->name
. ") with some DEFAULT defined. This type of columns cannot have any default value. Please fix it in source (XML and/or upgrade script) to avoid this message to be displayed.";
263 $this->debug($this->errormsg
);
266 $this->default = $default;
270 * Load data from XML to the table
271 * @param array $xmlarr
274 public function arr2xmldb_field($xmlarr) {
279 // traverse_xmlize($xmlarr); //Debug
280 // print_object ($GLOBALS['traverse_array']); //Debug
281 // $GLOBALS['traverse_array']=""; //Debug
283 // Process table attributes (name, type, length
284 // notnull, sequence, decimals, comment, previous, next)
285 if (isset($xmlarr['@']['NAME'])) {
286 $this->name
= trim($xmlarr['@']['NAME']);
288 $this->errormsg
= 'Missing NAME attribute';
289 $this->debug($this->errormsg
);
293 if (isset($xmlarr['@']['TYPE'])) {
294 // Check for valid type
295 $type = $this->getXMLDBFieldType(trim($xmlarr['@']['TYPE']));
299 $this->errormsg
= 'Invalid TYPE attribute';
300 $this->debug($this->errormsg
);
304 $this->errormsg
= 'Missing TYPE attribute';
305 $this->debug($this->errormsg
);
309 if (isset($xmlarr['@']['LENGTH'])) {
310 $length = trim($xmlarr['@']['LENGTH']);
311 // Check for integer values
312 if ($this->type
== XMLDB_TYPE_INTEGER ||
313 $this->type
== XMLDB_TYPE_NUMBER ||
314 $this->type
== XMLDB_TYPE_CHAR
) {
315 if (!(is_numeric($length)&&(intval($length)==floatval($length)))) {
316 $this->errormsg
= 'Incorrect LENGTH attribute for int, number or char fields';
317 $this->debug($this->errormsg
);
319 } else if (!$length) {
320 $this->errormsg
= 'Zero LENGTH attribute';
321 $this->debug($this->errormsg
);
325 // Remove length from text and binary
326 if ($this->type
== XMLDB_TYPE_TEXT ||
327 $this->type
== XMLDB_TYPE_BINARY
) {
330 // Finally, set the length
331 $this->length
= $length;
334 if (isset($xmlarr['@']['NOTNULL'])) {
335 $notnull = strtolower(trim($xmlarr['@']['NOTNULL']));
336 if ($notnull == 'true') {
337 $this->notnull
= true;
338 } else if ($notnull == 'false') {
339 $this->notnull
= false;
341 $this->errormsg
= 'Incorrect NOTNULL attribute (true/false allowed)';
342 $this->debug($this->errormsg
);
347 if (isset($xmlarr['@']['SEQUENCE'])) {
348 $sequence = strtolower(trim($xmlarr['@']['SEQUENCE']));
349 if ($sequence == 'true') {
350 $this->sequence
= true;
351 } else if ($sequence == 'false') {
352 $this->sequence
= false;
354 $this->errormsg
= 'Incorrect SEQUENCE attribute (true/false allowed)';
355 $this->debug($this->errormsg
);
360 if (isset($xmlarr['@']['DEFAULT'])) {
361 $this->setDefault(trim($xmlarr['@']['DEFAULT']));
365 if (isset($xmlarr['@']['DECIMALS'])) {
366 $decimals = trim($xmlarr['@']['DECIMALS']);
367 // Check for integer values
368 if ($this->type
== XMLDB_TYPE_NUMBER ||
369 $this->type
== XMLDB_TYPE_FLOAT
) {
370 if (!(is_numeric($decimals)&&(intval($decimals)==floatval($decimals)))) {
371 $this->errormsg
= 'Incorrect DECIMALS attribute for number field';
372 $this->debug($this->errormsg
);
374 } else if ($this->length
<= $decimals){
375 $this->errormsg
= 'Incorrect DECIMALS attribute (bigget than length)';
376 $this->debug($this->errormsg
);
380 $this->errormsg
= 'Incorrect DECIMALS attribute for non-number field';
381 $this->debug($this->errormsg
);
385 if ($this->type
== XMLDB_TYPE_NUMBER
) {
389 // Finally, set the decimals
390 if ($this->type
== XMLDB_TYPE_NUMBER ||
391 $this->type
== XMLDB_TYPE_FLOAT
) {
392 $this->decimals
= $decimals;
395 if (isset($xmlarr['@']['COMMENT'])) {
396 $this->comment
= trim($xmlarr['@']['COMMENT']);
399 // Set some attributes
401 $this->loaded
= true;
403 $this->calculateHash();
408 * This function returns the correct XMLDB_TYPE_XXX value for the
409 * string passed as argument
410 * @param string $type
413 public function getXMLDBFieldType($type) {
415 $result = XMLDB_TYPE_INCORRECT
;
417 switch (strtolower($type)) {
419 $result = XMLDB_TYPE_INTEGER
;
422 $result = XMLDB_TYPE_NUMBER
;
425 $result = XMLDB_TYPE_FLOAT
;
428 $result = XMLDB_TYPE_CHAR
;
431 $result = XMLDB_TYPE_TEXT
;
434 $result = XMLDB_TYPE_BINARY
;
437 $result = XMLDB_TYPE_DATETIME
;
440 // Return the normalized XMLDB_TYPE
445 * This function returns the correct name value for the
446 * XMLDB_TYPE_XXX passed as argument
450 public function getXMLDBTypeName($type) {
454 switch (strtolower($type)) {
455 case XMLDB_TYPE_INTEGER
:
458 case XMLDB_TYPE_NUMBER
:
461 case XMLDB_TYPE_FLOAT
:
464 case XMLDB_TYPE_CHAR
:
467 case XMLDB_TYPE_TEXT
:
470 case XMLDB_TYPE_BINARY
:
473 case XMLDB_TYPE_DATETIME
:
474 $result = 'datetime';
477 // Return the normalized name
482 * This function calculate and set the hash of one xmldb_field
483 * @param bool $recursive
484 * @return void, modifies $this->hash
486 public function calculateHash($recursive = false) {
487 if (!$this->loaded
) {
490 $defaulthash = is_null($this->default) ?
'' : sha1($this->default);
491 $key = $this->name
. $this->type
. $this->length
.
492 $this->notnull
. $this->sequence
.
493 $this->decimals
. $this->comment
. $defaulthash;
494 $this->hash
= md5($key);
499 * This function will output the XML text for one field
502 public function xmlOutput() {
504 $o.= ' <FIELD NAME="' . $this->name
. '"';
505 $o.= ' TYPE="' . $this->getXMLDBTypeName($this->type
) . '"';
507 $o.= ' LENGTH="' . $this->length
. '"';
509 if ($this->notnull
) {
514 $o.= ' NOTNULL="' . $notnull . '"';
515 if (!$this->sequence
&& $this->default !== null) {
516 $o.= ' DEFAULT="' . $this->default . '"';
518 if ($this->sequence
) {
523 $o.= ' SEQUENCE="' . $sequence . '"';
524 if ($this->decimals
!== null) {
525 $o.= ' DECIMALS="' . $this->decimals
. '"';
527 if ($this->comment
) {
528 $o.= ' COMMENT="' . htmlspecialchars($this->comment
, ENT_COMPAT
) . '"';
536 * This function will set all the attributes of the xmldb_field object
537 * based on information passed in one ADOField
538 * @param database_column_info $adofield
539 * @return void, sets $this->type
541 public function setFromADOField($adofield) {
543 // Calculate the XMLDB_TYPE
544 switch (strtolower($adofield->type
)) {
550 $this->type
= XMLDB_TYPE_INTEGER
;
556 $this->type
= XMLDB_TYPE_NUMBER
;
560 $this->type
= XMLDB_TYPE_FLOAT
;
565 $this->type
= XMLDB_TYPE_CHAR
;
571 $this->type
= XMLDB_TYPE_TEXT
;
577 $this->type
= XMLDB_TYPE_BINARY
;
581 $this->type
= XMLDB_TYPE_DATETIME
;
584 $this->type
= XMLDB_TYPE_TEXT
;
586 // Calculate the length of the field
587 if ($adofield->max_length
> 0 &&
588 ($this->type
== XMLDB_TYPE_INTEGER ||
589 $this->type
== XMLDB_TYPE_NUMBER ||
590 $this->type
== XMLDB_TYPE_FLOAT ||
591 $this->type
== XMLDB_TYPE_CHAR
)) {
592 $this->length
= $adofield->max_length
;
594 if ($this->type
== XMLDB_TYPE_TEXT
) {
595 $this->length
= null;
597 if ($this->type
== XMLDB_TYPE_BINARY
) {
598 $this->length
= null;
600 // Calculate the decimals of the field
601 if ($adofield->max_length
> 0 &&
603 ($this->type
== XMLDB_TYPE_NUMBER ||
604 $this->type
== XMLDB_TYPE_FLOAT
)) {
605 $this->decimals
= $adofield->scale
;
607 // Calculate the notnull field
608 if ($adofield->not_null
) {
609 $this->notnull
= true;
611 // Calculate the default field
612 if ($adofield->has_default
) {
613 $this->default = $adofield->default_value
;
615 // Calculate the sequence field
616 if ($adofield->auto_increment
) {
617 $this->sequence
= true;
620 $this->loaded
= true;
621 $this->changed
= true;
625 * Returns the PHP code needed to define one xmldb_field
626 * @param bool $includeprevious
629 public function getPHP($includeprevious=true) {
634 switch ($this->getType()) {
635 case XMLDB_TYPE_INTEGER
:
636 $result .= 'XMLDB_TYPE_INTEGER' . ', ';
638 case XMLDB_TYPE_NUMBER
:
639 $result .= 'XMLDB_TYPE_NUMBER' . ', ';
641 case XMLDB_TYPE_FLOAT
:
642 $result .= 'XMLDB_TYPE_FLOAT' . ', ';
644 case XMLDB_TYPE_CHAR
:
645 $result .= 'XMLDB_TYPE_CHAR' . ', ';
647 case XMLDB_TYPE_TEXT
:
648 $result .= 'XMLDB_TYPE_TEXT' . ', ';
650 case XMLDB_TYPE_BINARY
:
651 $result .= 'XMLDB_TYPE_BINARY' . ', ';
653 case XMLDB_TYPE_DATETIME
:
654 $result .= 'XMLDB_TYPE_DATETIME' . ', ';
656 case XMLDB_TYPE_TIMESTAMP
:
657 $result .= 'XMLDB_TYPE_TIMESTAMP' . ', ';
661 $length = $this->getLength();
662 $decimals = $this->getDecimals();
663 if (!empty($length)) {
664 $result .= "'" . $length;
665 if (!empty($decimals)) {
666 $result .= ', ' . $decimals;
672 // Unsigned is not used any more since Moodle 2.3
675 $notnull = $this->getNotnull();
676 if (!empty($notnull)) {
677 $result .= 'XMLDB_NOTNULL' . ', ';
682 $sequence = $this->getSequence();
683 if (!empty($sequence)) {
684 $result .= 'XMLDB_SEQUENCE' . ', ';
689 $default = $this->getDefault();
690 if ($default !== null && !$this->getSequence()) {
691 $result .= "'" . $default . "'";
695 // Previous (decided by parameter)
696 if ($includeprevious) {
697 $previous = $this->getPrevious();
698 if (!empty($previous)) {
699 $result .= ", '" . $previous . "'";
709 * Shows info in a readable format
712 public function readableInfo() {
715 $o .= $this->getXMLDBTypeName($this->type
);
717 if ($this->type
== XMLDB_TYPE_INTEGER ||
718 $this->type
== XMLDB_TYPE_NUMBER ||
719 $this->type
== XMLDB_TYPE_FLOAT ||
720 $this->type
== XMLDB_TYPE_CHAR
) {
722 $o .= ' (' . $this->length
;
723 if ($this->type
== XMLDB_TYPE_NUMBER ||
724 $this->type
== XMLDB_TYPE_FLOAT
) {
725 if ($this->decimals
!== null) {
726 $o .= ', ' . $this->decimals
;
733 if ($this->notnull
) {
737 if ($this->default !== null) {
739 if ($this->type
== XMLDB_TYPE_CHAR ||
740 $this->type
== XMLDB_TYPE_TEXT
) {
741 $o .= "'" . $this->default . "'";
743 $o .= $this->default;
747 if ($this->sequence
) {
748 $o .= ' auto-numbered';
755 * Validates the field restrictions.
757 * The error message should not be localised because it is intended for developers,
758 * end users and admins should never see these problems!
760 * @param xmldb_table $xmldb_table optional when object is table
761 * @return string null if ok, error message if problem found
763 public function validateDefinition(xmldb_table
$xmldb_table=null) {
765 return 'Invalid xmldb_field->validateDefinition() call, $xmldb_table is required.';
768 $name = $this->getName();
769 if (strlen($name) > self
::NAME_MAX_LENGTH
) {
770 return 'Invalid field name in table {'.$xmldb_table->getName().'}: field "'.$this->getName().'" name is too long.'
771 .' Limit is '.self
::NAME_MAX_LENGTH
.' chars.';
773 if (!preg_match('/^[a-z][a-z0-9_]*$/', $name)) {
774 return 'Invalid field name in table {'.$xmldb_table->getName().'}: field "'.$this->getName().'" name includes invalid characters.';
777 switch ($this->getType()) {
778 case XMLDB_TYPE_INTEGER
:
779 $length = $this->getLength();
780 if (!is_number($length) or $length <= 0 or $length > self
::INTEGER_MAX_LENGTH
) {
781 return 'Invalid field definition in table {'.$xmldb_table->getName().'}: XMLDB_TYPE_INTEGER field "'.$this->getName().'" has invalid length';
783 $default = $this->getDefault();
784 if (!empty($default) and !is_number($default)) {
785 return 'Invalid field definition in table {'.$xmldb_table->getName().'}: XMLDB_TYPE_INTEGER field "'.$this->getName().'" has invalid default';
789 case XMLDB_TYPE_NUMBER
:
790 $maxlength = self
::NUMBER_MAX_LENGTH
;
791 $length = $this->getLength();
792 if (!is_number($length) or $length <= 0 or $length > $maxlength) {
793 return 'Invalid field definition in table {'.$xmldb_table->getName().'}: XMLDB_TYPE_NUMBER field "'.$this->getName().'" has invalid length';
795 $decimals = $this->getDecimals();
796 $decimals = empty($decimals) ?
0 : $decimals; // fix missing decimals
797 if (!is_number($decimals) or $decimals < 0 or $decimals > $length) {
798 return 'Invalid field definition in table {'.$xmldb_table->getName().'}: XMLDB_TYPE_NUMBER field "'.$this->getName().'" has invalid decimals';
800 if ($length - $decimals > self
::INTEGER_MAX_LENGTH
) {
801 return 'Invalid field definition in table {'.$xmldb_table->getName().'}: XMLDB_TYPE_NUMBER field "'.
802 $this->getName().'" has too big whole number part';
804 $default = $this->getDefault();
805 if (!empty($default) and !is_numeric($default)) {
806 return 'Invalid field definition in table {'.$xmldb_table->getName().'}: XMLDB_TYPE_NUMBER field "'.$this->getName().'" has invalid default';
810 case XMLDB_TYPE_FLOAT
:
811 $length = $this->getLength();
812 $length = empty($length) ?
6 : $length; // weird, it might be better to require something here...
813 if (!is_number($length) or $length <= 0 or $length > self
::FLOAT_MAX_LENGTH
) {
814 return 'Invalid field definition in table {'.$xmldb_table->getName().'}: XMLDB_TYPE_FLOAT field "'.$this->getName().'" has invalid length';
816 $decimals = $this->getDecimals();
817 $decimals = empty($decimals) ?
0 : $decimals; // fix missing decimals
818 if (!is_number($decimals) or $decimals < 0 or $decimals > $length) {
819 return 'Invalid field definition in table {'.$xmldb_table->getName().'}: XMLDB_TYPE_FLOAT field "'.$this->getName().'" has invalid decimals';
821 $default = $this->getDefault();
822 if (!empty($default) and !is_numeric($default)) {
823 return 'Invalid field definition in table {'.$xmldb_table->getName().'}: XMLDB_TYPE_FLOAT field "'.$this->getName().'" has invalid default';
827 case XMLDB_TYPE_CHAR
:
828 if ($this->getLength() > self
::CHAR_MAX_LENGTH
) {
829 return 'Invalid field definition in table {'.$xmldb_table->getName(). '}: XMLDB_TYPE_CHAR field "'.$this->getName().'" is too long.'
830 .' Limit is '.self
::CHAR_MAX_LENGTH
.' chars.';
834 case XMLDB_TYPE_TEXT
:
837 case XMLDB_TYPE_BINARY
:
840 case XMLDB_TYPE_DATETIME
:
843 case XMLDB_TYPE_TIMESTAMP
: