public function save()
{
$this->delete(false);
$object = $this->model->getObject();
$validLanguages = Tool::getValidLanguages();
$context = $this->model->getContext();
if ($context && $context["containerType"] == "fieldcollection") {
$containerKey = $context["containerKey"];
$container = Object\Fieldcollection\Definition::getByKey($containerKey);
} else {
$container = $this->model->getClass();
}
$fieldDefinitions = $container->getFielddefinition("localizedfields")->getFielddefinitions();
foreach ($validLanguages as $language) {
$inheritedValues = Object\AbstractObject::doGetInheritedValues();
Object\AbstractObject::setGetInheritedValues(false);
$insertData = ["ooo_id" => $this->model->getObject()->getId(), "language" => $language];
if ($container instanceof Object\Fieldcollection\Definition) {
$insertData["fieldname"] = $context["fieldname"];
$insertData["index"] = $context["index"];
}
foreach ($fieldDefinitions as $fd) {
if (method_exists($fd, "save")) {
// for fieldtypes which have their own save algorithm eg. objects, multihref, ...
$context = $this->model->getContext() ? $this->model->getContext() : [];
if ($context["containerType"] == "fieldcollection") {
$context["subContainerType"] = "localizedfield";
}
$childParams = ["context" => $context, "language" => $language];
$fd->save($this->model, $childParams);
} else {
if (is_array($fd->getColumnType())) {
$insertDataArray = $fd->getDataForResource($this->model->getLocalizedValue($fd->getName(), $language, true), $object);
$insertData = array_merge($insertData, $insertDataArray);
} else {
$insertData[$fd->getName()] = $fd->getDataForResource($this->model->getLocalizedValue($fd->getName(), $language, true), $object);
}
}
}
$storeTable = $this->getTableName();
$queryTable = $this->getQueryTableName() . "_" . $language;
$this->db->insertOrUpdate($storeTable, $insertData);
if ($container instanceof Object\ClassDefinition) {
// query table
$data = [];
$data["ooo_id"] = $this->model->getObject()->getId();
$data["language"] = $language;
$this->inheritanceHelper = new Object\Concrete\Dao\InheritanceHelper($object->getClassId(), "ooo_id", $storeTable, $queryTable);
$this->inheritanceHelper->resetFieldsToCheck();
$sql = "SELECT * FROM " . $queryTable . " WHERE ooo_id = " . $object->getId() . " AND language = '" . $language . "'";
$oldData = [];
try {
$oldData = $this->db->fetchRow($sql);
} catch (\Exception $e) {
// if the table doesn't exist -> create it!
if (strpos($e->getMessage(), "exist")) {
// the following is to ensure consistent data and atomic transactions, while having the flexibility
// to add new languages on the fly without saving all classes having localized fields
// first we need to roll back all modifications, because otherwise they would be implicitly committed
// by the following DDL
$this->db->rollBack();
// this creates the missing table
$this->createUpdateTable();
// at this point we throw an exception so that the transaction gets repeated in Object::save()
throw new \Exception("missing table created, start next run ... ;-)");
}
}
// get fields which shouldn't be updated
$untouchable = [];
// @TODO: currently we do not support lazyloading in localized fields
$inheritanceEnabled = $object->getClass()->getAllowInherit();
$parentData = null;
if ($inheritanceEnabled) {
// get the next suitable parent for inheritance
$parentForInheritance = $object->getNextParentForInheritance();
if ($parentForInheritance) {
// we don't use the getter (built in functionality to get inherited values) because we need to avoid race conditions
// we cannot Object\AbstractObject::setGetInheritedValues(true); and then $this->model->getLocalizedValue($key, $language)
// so we select the data from the parent object using FOR UPDATE, which causes a lock on this row
// so the data of the parent cannot be changed while this transaction is on progress
$parentData = $this->db->fetchRow("SELECT * FROM " . $queryTable . " WHERE ooo_id = ? AND language = ? FOR UPDATE", [$parentForInheritance->getId(), $language]);
}
}
foreach ($fieldDefinitions as $fd) {
if ($fd->getQueryColumnType()) {
$key = $fd->getName();
// exclude untouchables if value is not an array - this means data has not been loaded
if (!(in_array($key, $untouchable) and !is_array($this->model->{$key}))) {
$localizedValue = $this->model->getLocalizedValue($key, $language);
$insertData = $fd->getDataForQueryResource($localizedValue, $object);
$isEmpty = $fd->isEmpty($localizedValue);
if (is_array($insertData)) {
$columnNames = array_keys($insertData);
$data = array_merge($data, $insertData);
} else {
$columnNames = [$key];
$data[$key] = $insertData;
}
// if the current value is empty and we have data from the parent, we just use it
if ($isEmpty && $parentData) {
foreach ($columnNames as $columnName) {
if (array_key_exists($columnName, $parentData)) {
$data[$columnName] = $parentData[$columnName];
if (is_array($insertData)) {
$insertData[$columnName] = $parentData[$columnName];
} else {
$insertData = $parentData[$columnName];
}
}
}
}
if ($inheritanceEnabled && $fd->getFieldType() != "calculatedValue") {
//get changed fields for inheritance
if ($fd->isRelationType()) {
if (is_array($insertData)) {
$doInsert = false;
foreach ($insertData as $insertDataKey => $insertDataValue) {
if ($isEmpty && $oldData[$insertDataKey] == $parentData[$insertDataKey]) {
// do nothing, ... value is still empty and parent data is equal to current data in query table
} elseif ($oldData[$insertDataKey] != $insertDataValue) {
$doInsert = true;
break;
}
}
if ($doInsert) {
$this->inheritanceHelper->addRelationToCheck($key, $fd, array_keys($insertData));
}
} else {
if ($isEmpty && $oldData[$key] == $parentData[$key]) {
// do nothing, ... value is still empty and parent data is equal to current data in query table
} elseif ($oldData[$key] != $insertData) {
$this->inheritanceHelper->addRelationToCheck($key, $fd);
}
}
} else {
if (is_array($insertData)) {
foreach ($insertData as $insertDataKey => $insertDataValue) {
if ($isEmpty && $oldData[$insertDataKey] == $parentData[$insertDataKey]) {
// do nothing, ... value is still empty and parent data is equal to current data in query table
} elseif ($oldData[$insertDataKey] != $insertDataValue) {
$this->inheritanceHelper->addFieldToCheck($insertDataKey, $fd);
}
}
} else {
if ($isEmpty && $oldData[$key] == $parentData[$key]) {
// do nothing, ... value is still empty and parent data is equal to current data in query table
} elseif ($oldData[$key] != $insertData) {
// data changed, do check and update
$this->inheritanceHelper->addFieldToCheck($key, $fd);
}
}
}
}
} else {
Logger::debug("Excluding untouchable query value for object [ " . $this->model->getId() . " ] key [ {$key} ] because it has not been loaded");
}
}
}
$queryTable = $this->getQueryTableName() . "_" . $language;
$this->db->insertOrUpdate($queryTable, $data);
if ($inheritanceEnabled) {
$this->inheritanceHelper->doUpdate($object->getId(), true);
}
$this->inheritanceHelper->resetFieldsToCheck();
}
Object\AbstractObject::setGetInheritedValues($inheritedValues);
}
// foreach language
}