/** * Generate the given field on the table, modifying whatever already exists as necessary. * * @param string $table The table name. * @param string $field The field name. * @param array|string $spec The field specification. If passed in array syntax, the specific database * driver takes care of the ALTER TABLE syntax. If passed as a string, its assumed to * be prepared as a direct SQL framgment ready for insertion into ALTER TABLE. In this case you'll * need to take care of database abstraction in your DBField subclass. */ public function requireField($table, $field, $spec) { //TODO: this is starting to get extremely fragmented. //There are two different versions of $spec floating around, and their content changes depending //on how they are structured. This needs to be tidied up. $fieldValue = null; $newTable = false; // backwards compatibility patch for pre 2.4 requireField() calls $spec_orig = $spec; if (!is_string($spec)) { $spec['parts']['name'] = $field; $spec_orig['parts']['name'] = $field; //Convert the $spec array into a database-specific string $spec = $this->{$spec}['type']($spec['parts'], true); } // Collations didn't come in until MySQL 4.1. Anything earlier will throw a syntax error if you try and use // collations. // TODO: move this to the MySQLDatabase file, or drop it altogether? if (!$this->database->supportsCollations()) { $spec = preg_replace('/ *character set [^ ]+( collate [^ ]+)?( |$)/', '\\2', $spec); } if (!isset($this->tableList[strtolower($table)])) { $newTable = true; } if (is_array($spec)) { $specValue = $this->{$spec_orig}['type']($spec_orig['parts']); } else { $specValue = $spec; } // We need to get db-specific versions of the ID column: if ($spec_orig == $this->IdColumn() || $spec_orig == $this->IdColumn(true)) { $specValue = $this->IdColumn(true); } if (!$newTable) { $fieldList = $this->fieldList($table); if (isset($fieldList[$field])) { if (is_array($fieldList[$field])) { $fieldValue = $fieldList[$field]['data_type']; } else { $fieldValue = $fieldList[$field]; } } } // Get the version of the field as we would create it. This is used for comparison purposes to see if the // existing field is different to what we now want if (is_array($spec_orig)) { $spec_orig = $this->{$spec_orig}['type']($spec_orig['parts']); } if ($newTable || $fieldValue == '') { $this->transCreateField($table, $field, $spec_orig); $this->alterationMessage("Field {$table}.{$field}: created as {$spec_orig}", "created"); } else { if ($fieldValue != $specValue) { // If enums/sets are being modified, then we need to fix existing data in the table. // Update any records where the enum is set to a legacy value to be set to the default. // One hard-coded exception is SiteTree - the default for this is Page. foreach (array('enum', 'set') as $enumtype) { if (preg_match("/^{$enumtype}/i", $specValue)) { $newStr = preg_replace("/(^{$enumtype}\\s*\\(')|('\$\\).*)/i", "", $spec_orig); $new = preg_split("/'\\s*,\\s*'/", $newStr); $oldStr = preg_replace("/(^{$enumtype}\\s*\\(')|('\$\\).*)/i", "", $fieldValue); $old = preg_split("/'\\s*,\\s*'/", $newStr); $holder = array(); foreach ($old as $check) { if (!in_array($check, $new)) { $holder[] = $check; } } if (count($holder)) { $default = explode('default ', $spec_orig); $default = $default[1]; if ($default == "'SiteTree'") { $default = "'Page'"; } $query = "UPDATE \"{$table}\" SET {$field}={$default} WHERE {$field} IN ("; for ($i = 0; $i + 1 < count($holder); $i++) { $query .= "'{$holder[$i]}', "; } $query .= "'{$holder[$i]}')"; $this->query($query); $amount = $this->database->affectedRows(); $this->alterationMessage("Changed {$amount} rows to default value of field {$field}" . " (Value: {$default})"); } } } $this->transAlterField($table, $field, $spec_orig); $this->alterationMessage("Field {$table}.{$field}: changed to {$specValue} <i style=\"color: #AAA\">(from {$fieldValue})</i>", "changed"); } } }