/** * Writes all changes to this object to the database. * - It will insert a record whenever ID isn't set, otherwise update. * - All relevant tables will be updated. * - $this->onBeforeWrite() gets called beforehand. * - Extensions such as Versioned will ammend the database-write to ensure that a version is saved. * * @uses DataExtension->augmentWrite() * * @param boolean $showDebug Show debugging information * @param boolean $forceInsert Run INSERT command rather than UPDATE, even if record already exists * @param boolean $forceWrite Write to database even if there are no changes * @param boolean $writeComponents Call write() on all associated component instances which were previously * retrieved through {@link getComponent()}, {@link getComponents()} or {@link getManyManyComponents()} * (Default: false) * * @return int The ID of the record * @throws ValidationException Exception that can be caught and handled by the calling function */ public function write($showDebug = false, $forceInsert = false, $forceWrite = false, $writeComponents = false) { $firstWrite = false; $this->brokenOnWrite = true; $isNewRecord = false; if (self::get_validation_enabled()) { $valid = $this->validate(); if (!$valid->valid()) { // Used by DODs to clean up after themselves, eg, Versioned $this->extend('onAfterSkippedWrite'); throw new ValidationException($valid, "Validation error writing a {$this->class} object: " . $valid->message() . ". Object not written.", E_USER_WARNING); return false; } } $this->onBeforeWrite(); if ($this->brokenOnWrite) { user_error("{$this->class} has a broken onBeforeWrite() function. Make sure that you call parent::onBeforeWrite().", E_USER_ERROR); } // New record = everything has changed if ($this->ID && is_numeric($this->ID) && !$forceInsert) { $dbCommand = 'update'; // Update the changed array with references to changed obj-fields foreach ($this->record as $k => $v) { if (is_object($v) && method_exists($v, 'isChanged') && $v->isChanged()) { $this->changed[$k] = true; } } } else { $dbCommand = 'insert'; $this->changed = array(); foreach ($this->record as $k => $v) { $this->changed[$k] = 2; } $firstWrite = true; } // No changes made if ($this->changed) { foreach ($this->getClassAncestry() as $ancestor) { if (self::has_own_table($ancestor)) { $ancestry[] = $ancestor; } } // Look for some changes to make if (!$forceInsert) { unset($this->changed['ID']); } $hasChanges = false; foreach ($this->changed as $fieldName => $changed) { if ($changed) { $hasChanges = true; break; } } if ($hasChanges || $forceWrite || !$this->record['ID']) { // New records have their insert into the base data table done first, so that they can pass the // generated primary key on to the rest of the manipulation $baseTable = $ancestry[0]; if ((!isset($this->record['ID']) || !$this->record['ID']) && isset($ancestry[0])) { DB::query("INSERT INTO \"{$baseTable}\" (\"Created\") VALUES (" . DB::getConn()->now() . ")"); $this->record['ID'] = DB::getGeneratedID($baseTable); $this->changed['ID'] = 2; $isNewRecord = true; } // Divvy up field saving into a number of database manipulations $manipulation = array(); if (isset($ancestry) && is_array($ancestry)) { foreach ($ancestry as $idx => $class) { $classSingleton = singleton($class); foreach ($this->record as $fieldName => $fieldValue) { if (isset($this->changed[$fieldName]) && $this->changed[$fieldName] && ($fieldType = $classSingleton->hasOwnTableDatabaseField($fieldName))) { $fieldObj = $this->dbObject($fieldName); if (!isset($manipulation[$class])) { $manipulation[$class] = array(); } // if database column doesn't correlate to a DBField instance... if (!$fieldObj) { $fieldObj = DBField::create_field('Varchar', $this->record[$fieldName], $fieldName); } // Both CompositeDBFields and regular fields need to be repopulated $fieldObj->setValue($this->record[$fieldName], $this->record); if ($class != $baseTable || $fieldName != 'ID') { $fieldObj->writeToManipulation($manipulation[$class]); } } } // Add the class name to the base object if ($idx == 0) { $manipulation[$class]['fields']["LastEdited"] = "'" . SS_Datetime::now()->Rfc2822() . "'"; if ($dbCommand == 'insert') { $manipulation[$class]['fields']["Created"] = "'" . SS_Datetime::now()->Rfc2822() . "'"; //echo "<li>$this->class - " .get_class($this); $manipulation[$class]['fields']["ClassName"] = DB::getConn()->prepStringForDB($this->class); } } // In cases where there are no fields, this 'stub' will get picked up on if (self::has_own_table($class)) { $manipulation[$class]['command'] = $dbCommand; $manipulation[$class]['id'] = $this->record['ID']; } else { unset($manipulation[$class]); } } } $this->extend('augmentWrite', $manipulation); // New records have their insert into the base data table done first, so that they can pass the // generated ID on to the rest of the manipulation if (isset($isNewRecord) && $isNewRecord && isset($manipulation[$baseTable])) { $manipulation[$baseTable]['command'] = 'update'; } DB::manipulate($manipulation); $this->onAfterWrite(); $this->changed = null; } elseif ($showDebug) { echo "<b>Debug:</b> no changes for DataObject<br />"; // Used by DODs to clean up after themselves, eg, Versioned $this->extend('onAfterSkippedWrite'); } // Clears the cache for this object so get_one returns the correct object. $this->flushCache(); if (!isset($this->record['Created'])) { $this->record['Created'] = SS_Datetime::now()->Rfc2822(); } $this->record['LastEdited'] = SS_Datetime::now()->Rfc2822(); } else { // Used by DODs to clean up after themselves, eg, Versioned $this->extend('onAfterSkippedWrite'); } // Write relations as necessary if ($writeComponents) { $this->writeComponents(true); } return $this->record['ID']; }
/** * Augment a write-record request. * @param SQLQuery $manipulation Query to augment. */ function augmentWrite(&$manipulation) { if (!$this->stat('enabled')) { return false; } if (($lang = self::current_lang()) && !self::is_default_lang()) { $tables = array_keys($manipulation); foreach ($tables as $table) { if (self::table_exists("{$table}_lang")) { $manipulation["{$table}_lang"] = $manipulation[$table]; if ($manipulation[$table]['command'] == 'insert') { $fakeID = $this->owner->ID; // In an insert we've to populate our fields and generate a new id (since the passed one it's relative to $table) $SessionOrigID = Session::get($this->owner->ID . '_originalLangID'); $manipulation["{$table}_lang"]['fields']['OriginalLangID'] = $this->owner->ID = $SessionOrigID ? $SessionOrigID : self::$creatingFromID; $manipulation["{$table}_lang"]['RecordID'] = $manipulation["{$table}_lang"]['fields']['OriginalLangID']; // populate lang field $manipulation["{$table}_lang"]['fields']['Lang'] = "'{$lang}'"; // get a valid id, pre-inserting DB::query("INSERT INTO {$table}_lang SET Created = NOW(), Lang = '{$lang}'"); $manipulation["{$table}_lang"]['id'] = $manipulation["{$table}_lang"]['fields']['ID'] = DB::getGeneratedID("{$table}_lang"); $manipulation["{$table}_lang"]['command'] = 'update'; // we don't have to insert anything in $table if we are inserting in $table_lang unset($manipulation[$table]); // now dataobjects may create a record before the real write in the base table, so we have to delete it - 20/08/2007 if (is_numeric($fakeID)) { DB::query("DELETE FROM {$table} WHERE ID={$fakeID}"); } } else { if (!isset($manipulation[$table]['fields']['OriginalLangID'])) { // for those updates that may become inserts populate these fields $manipulation["{$table}_lang"]['fields']['OriginalLangID'] = $this->owner->ID; $manipulation["{$table}_lang"]['fields']['Lang'] = "'{$lang}'"; } $id = $manipulation["{$table}_lang"]['id']; if (!$id) { user_error("Couldn't find ID in manipulation", E_USER_ERROR); } if (isset($manipulation["{$table}_lang"]['where'])) { $manipulation["{$table}_lang"]['where'] .= "AND (Lang = '{$lang}') AND (OriginalLangID = {$id})"; } else { $manipulation["{$table}_lang"]['where'] = "(Lang = '{$lang}') AND (OriginalLangID = {$id})"; } $realID = DB::query("SELECT ID FROM {$table}_lang WHERE (OriginalLangID = {$id}) AND (Lang = '{$lang}') LIMIT 1")->value(); $manipulation["{$table}_lang"]['id'] = $realID; $manipulation["{$table}_lang"]['RecordID'] = $manipulation["{$table}_lang"]['fields']['OriginalLangID']; // we could be updating non-translatable fields at the same time, so these will remain foreach ($manipulation[$table]['fields'] as $field => $dummy) { if ($this->isInAugmentedTable($field, $table)) { unset($manipulation[$table]['fields'][$field]); } } if (count($manipulation[$table]['fields']) == 0) { unset($manipulation[$table]); } } foreach ($manipulation["{$table}_lang"]['fields'] as $field => $dummy) { if (!$this->isInAugmentedTable($field, $table)) { unset($manipulation["{$table}_lang"]['fields'][$field]); } } } } } }
/** * Construct a child of this Folder with the given name. * It does this without actually using the object model, as this starts messing * with all the data. Rather, it does a direct database insert. */ function constructChild($name) { // Determine the class name - File, Folder or Image $baseDir = $this->FullPath; if(is_dir($baseDir . $name)) { $className = "Folder"; } else { $className = File::get_class_for_file_extension(pathinfo($name, PATHINFO_EXTENSION)); } if(Member::currentUser()) $ownerID = Member::currentUser()->ID; else $ownerID = 0; $filename = Convert::raw2sql($this->Filename . $name); if($className == 'Folder' ) $filename .= '/'; $name = Convert::raw2sql($name); DB::query("INSERT INTO \"File\" (\"ClassName\", \"ParentID\", \"OwnerID\", \"Name\", \"Filename\", \"Created\", \"LastEdited\", \"Title\") VALUES ('$className', $this->ID, $ownerID, '$name', '$filename', " . DB::getConn()->now() . ',' . DB::getConn()->now() . ", '$name')"); return DB::getGeneratedID("File"); }
/** * Construct a child, as Folder does, except that the child is not directly * owned by the dynamic template, but the folder object $subFolder under it. * @param String $name * @param String $subFolder * @return int ID of file created. */ function constructChildInFolder($name, $subFolder) { // Determine the class name - File, Folder or Image $baseDir = $subFolder->FullPath; if (is_dir($baseDir . $name)) { $className = "Folder"; } else { // Could use getimagesize to get the type of the image $ext = strtolower(substr($name, strrpos($name, '.') + 1)); switch ($ext) { case "gif": case "jpg": case "jpeg": case "png": $className = "Image"; break; default: $className = "File"; } } if (Member::currentUser()) { $ownerID = Member::currentUser()->ID; } else { $ownerID = 0; } $filename = DB::getConn()->addslashes($subFolder->Filename . $name); if ($className == 'Folder') { $filename .= '/'; } $name = DB::getConn()->addslashes($name); DB::query("INSERT INTO \"File\" \n\t\t\t(\"ClassName\", \"ParentID\", \"OwnerID\", \"Name\", \"Filename\", \"Created\", \"LastEdited\", \"Title\")\n\t\t\tVALUES ('{$className}', {$subFolder->ID}, {$ownerID}, '{$name}', '{$filename}', " . DB::getConn()->now() . ',' . DB::getConn()->now() . ", '{$name}')"); return DB::getGeneratedID("File"); }
/** * * @param string $name * @return number */ public function constructChildSecuredWithSecuredFlag($name) { // Determine the class name - File, Folder or Image $baseDir = $this->owner->FullPath; if (is_dir($baseDir . $name)) { $className = "Folder"; } else { $className = File::get_class_for_file_extension(pathinfo($name, PATHINFO_EXTENSION)); } $ownerID = 0; if (Member::currentUser()) { $ownerID = Member::currentUser()->ID; } $filename = Convert::raw2sql($this->owner->Filename . $name); if ($className == 'Folder') { $filename .= '/'; } $name = Convert::raw2sql($name); $secured = $this->owner->Secured ? '1' : '0'; DB::query("INSERT INTO \"File\"\n\t\t\t(\"ClassName\",\"ParentID\",\"OwnerID\",\"Name\",\"Filename\",\"Created\",\"LastEdited\",\"Title\",\"Secured\")\n\t\t\tVALUES ('{$className}', " . $this->owner->ID . ", {$ownerID}, '{$name}', '{$filename}', " . DB::getConn()->now() . ',' . DB::getConn()->now() . ", '{$name}', '{$secured}')"); return DB::getGeneratedID("File"); }
/** * Writes the fixture into the database directly using a database manipulation * * @param string $table * @param array $items */ protected function writeSQL($table, $items) { foreach ($items as $identifier => $fields) { $manipulation = array($table => array("fields" => array(), "command" => "insert")); foreach ($fields as $fieldName => $fieldVal) { $manipulation[$table]["fields"][$fieldName] = "'" . $this->parseFixtureVal($fieldVal) . "'"; } DB::manipulate($manipulation); $this->fixtureDictionary[$table][$identifier] = DB::getGeneratedID($table); } }
/** * Construct a child of this Folder with the given name. * It does this without actually using the object model, as this starts messing * with all the data. Rather, it does a direct database insert. */ function constructChild($name) { // Determine the class name - File, Folder or Image $baseDir = $this->FullPath; if(is_dir($baseDir . $name)) { $className = "Folder"; } else { // Could use getimagesize to get the type of the image $ext = strtolower(substr($name,strrpos($name,'.')+1)); switch($ext) { case "gif": case "jpg": case "jpeg": case "png": $className = "Image"; break; default: $className = "File"; } } if(Member::currentUser()) $ownerID = Member::currentUser()->ID; else $ownerID = 0; $filename = addslashes($this->Filename . $name); if($className == 'Folder' ) $filename .= '/'; $name = addslashes($name); DB::query("INSERT INTO `File` SET ClassName = '$className', ParentID = $this->ID, OwnerID = $ownerID, Name = '$name', Filename = '$filename', Created = NOW(), LastEdited = NOW(), Title = '$name'"); return DB::getGeneratedID("File"); }
public function importPass() { $query = $this->getRemoteObjectsQuery(); $items = $this->task->query($query); $itemsCount = $items->numRecords(); $this->task->message(" * Found {$itemsCount} items to import"); $total = 0; $updated = 0; $inserted = 0; foreach ($items as $item) { $this->task->progress(++$total, $itemsCount); // Build select query for existing object $values = array(); $select = new SQLQuery("ID", "\"{$this->tableName}\""); foreach ($this->fields as $field => $class) { $value = intval($item[$field]); $values[] = $value; $select->addWhere(sprintf("\"{$field}\" = %d", $value)); } $values[] = $item['ID']; // Check for existing record if ($localID = DB::query($select->sql())->value()) { // Update local many_many record instead of making new row DB::query(sprintf('UPDATE "%s" SET "LegacyID" = %d WHERE "ID" = %d', $this->tableName, $item['ID'], $localID)); $updated++; } else { // Insert mapping into the many_many table $insert = "INSERT INTO \"{$this->tableName}\" ("; $insert .= '"' . implode('", "', array_keys($this->fields)) . '"'; $insert .= ', LegacyID'; $insert .= ') VALUES ('; $insert .= implode(', ', $values); $insert .= ')'; DB::query($insert); $localID = DB::getGeneratedID($this->tableName); $inserted++; } // Mark this relation as imported in the remote table $conn = $this->task->getRemoteConnection(); $conn->query(sprintf('UPDATE "%s" SET "_ImportedID" = %d, "_ImportedDate" = NOW() WHERE "ID" = %d', $this->tableName, $localID, $item['ID'])); } // Done! $this->task->message(" * Result: {$inserted} added, {$updated} updated"); }
/** * Writes all changes to this object to the database. * - It will insert a record whenever ID isn't set, otherwise update. * - All relevant tables will be updated. * - $this->onBeforeWrite() gets called beforehand. * - Extensions such as Versioned will ammend the database-write to ensure that a version is saved. * - Calls to {@link DataObjectLog} can be used to see everything that's been changed. * * @param boolean $showDebug Show debugging information * @param boolean $forceInsert Run INSERT command rather than UPDATE, even if record already exists * @param boolean $forceWrite Write to database even if there are no changes * * @return int The ID of the record */ public function write($showDebug = false, $forceInsert = false, $forceWrite = false) { $firstWrite = false; $this->brokenOnWrite = true; $isNewRecord = false; $this->onBeforeWrite(); if ($this->brokenOnWrite) { user_error("{$this->class} has a broken onBeforeWrite() function. Make sure that you call parent::onBeforeWrite().", E_USER_ERROR); } // New record = everything has changed if ($this->ID && is_numeric($this->ID) && !$forceInsert) { $dbCommand = 'update'; } else { $dbCommand = 'insert'; $this->changed = array(); foreach ($this->record as $k => $v) { $this->changed[$k] = 2; } $firstWrite = true; } // No changes made if ($this->changed) { foreach ($this->getClassAncestry() as $ancestor) { if (ClassInfo::hasTable($ancestor)) { $ancestry[] = $ancestor; } } // Look for some changes to make unset($this->changed['ID']); $hasChanges = false; foreach ($this->changed as $fieldName => $changed) { if ($changed) { $hasChanges = true; break; } } if ($hasChanges || $forceWrite || !$this->record['ID']) { // New records have their insert into the base data table done first, so that they can pass the // generated primary key on to the rest of the manipulation if (!$this->record['ID'] && isset($ancestry[0])) { $baseTable = $ancestry[0]; DB::query("INSERT INTO `{$baseTable}` SET Created = NOW()"); $this->record['ID'] = DB::getGeneratedID($baseTable); $this->changed['ID'] = 2; $isNewRecord = true; } // Divvy up field saving into a number of database manipulations if (isset($ancestry) && is_array($ancestry)) { foreach ($ancestry as $idx => $class) { $classSingleton = singleton($class); foreach ($this->record as $fieldName => $value) { if (isset($this->changed[$fieldName]) && $this->changed[$fieldName] && ($fieldType = $classSingleton->fieldExists($fieldName))) { $manipulation[$class]['fields'][$fieldName] = $value ? "'" . addslashes($value) . "'" : singleton($fieldType)->nullValue(); } } // Add the class name to the base object if ($idx == 0) { $manipulation[$class]['fields']["LastEdited"] = "now()"; if ($dbCommand == 'insert') { $manipulation[$class]['fields']["Created"] = "now()"; //echo "<li>$this->class - " .get_class($this); $manipulation[$class]['fields']["ClassName"] = "'{$this->class}'"; } } // In cases where there are no fields, this 'stub' will get picked up on if (ClassInfo::hasTable($class)) { $manipulation[$class]['command'] = $dbCommand; $manipulation[$class]['id'] = $this->record['ID']; } else { unset($manipulation[$class]); } } } $this->extend('augmentWrite', $manipulation); // New records have their insert into the base data table done first, so that they can pass the // generated ID on to the rest of the manipulation if (isset($isNewRecord) && $isNewRecord && isset($manipulation[$baseTable])) { $manipulation[$baseTable]['command'] = 'update'; } DB::manipulate($manipulation); if (isset($isNewRecord) && $isNewRecord) { DataObjectLog::addedObject($this); } else { DataObjectLog::changedObject($this); } $this->changed = null; } elseif ($showDebug) { echo "<b>Debug:</b> no changes for DataObject<br />"; } // Clears the cache for this object so get_one returns the correct object. $this->flushCache(); if (!isset($this->record['Created'])) { $this->record['Created'] = date('Y-m-d H:i:s'); } $this->record['LastEdited'] = date('Y-m-d H:i:s'); } // Write ComponentSets as necessary if ($this->components) { foreach ($this->components as $component) { $component->write($firstWrite); } } return $this->record['ID']; }
/** * Writes the fixture into the database directly using a database manipulation. * Does not use blueprints. Only supports tables with a primary key. * * @param String $table Existing database table name * @param String $identifier Unique identifier for this fixture type * @param Array $data Map of properties * @return Int Database identifier */ public function createRaw($table, $identifier, $data) { $manipulation = array($table => array("fields" => array(), "command" => "insert")); foreach ($data as $fieldName => $fieldVal) { $manipulation[$table]["fields"][$fieldName] = "'" . $this->parseValue($fieldVal) . "'"; } DB::manipulate($manipulation); $id = DB::getGeneratedID($table); $this->fixtures[$table][$identifier] = $id; return $id; }