Exemple #1
0
 /**
  * Create root node if multiple-root tree mode. Update node if it's not new
  *
  * @param boolean $runValidation
  *        should validations be executed on all models before allowing save()
  * @param array $attributes
  *        which attributes should be saved (default null means all changed attributes)
  * @param boolean $hasParentModel
  *        whether this method was called from the top level or by a parent
  *        If false, it means the method was called at the top level
  * @param boolean $fromSaveAll
  *        has the save() call come from saveAll() or not
  * @return boolean
  *        did save() successfully process
  */
 private function save($runValidation = true, $attributes = null, $hasParentModel = false, $fromSaveAll = false)
 {
     if ($this->owner->getReadOnly() && !$hasParentModel) {
         // return failure if we are at the top of the tree and should not be asking to saveAll
         // not allowed to amend or delete
         $message = 'Attempting to save on ' . Tools::getClassName($this->owner) . ' readOnly model';
         //$this->addActionError($message);
         throw new \fangface\db\Exception($message);
     } elseif ($this->owner->getReadOnly() && $hasParentModel) {
         $message = 'Skipping save on ' . Tools::getClassName($this->owner) . ' readOnly model';
         $this->addActionWarning($message);
         return true;
     } else {
         if ($runValidation && !$this->owner->validate($attributes)) {
             return false;
         }
         if ($this->owner->getIsNewRecord()) {
             return $this->makeRoot($attributes);
         }
         $updateChildPaths = false;
         if ($this->hasPaths && !$this->owner->getIsNewRecord()) {
             if ($this->owner->hasAttribute($this->pathAttribute)) {
                 if ($this->owner->hasChanged($this->pathAttribute)) {
                     $updateChildPaths = true;
                     if ($this->_previousPath == '') {
                         $this->_previousPath = $this->owner->getOldAttribute($this->pathAttribute);
                     }
                 }
             }
             if (!$updateChildPaths && $this->owner->hasAttribute($this->nameAttribute)) {
                 if ($this->owner->hasChanged($this->nameAttribute)) {
                     $this->_previousPath = $this->owner->getAttribute($this->pathAttribute);
                     $this->checkAndSetPath($this->owner);
                     if ($this->_previousPath != $this->owner->getAttribute($this->pathAttribute)) {
                         $updateChildPaths = true;
                     }
                 }
             }
         }
         $nameChanged = false;
         if ($this->owner->hasAttribute($this->nameAttribute) && $this->owner->hasChanged($this->nameAttribute)) {
             $nameChanged = true;
             if (!$this->beforeRenameNode($this->_previousPath)) {
                 return false;
             }
         }
         $result = false;
         $db = $this->owner->getDb();
         if ($db->getTransaction() === null) {
             $transaction = $db->beginTransaction();
         }
         try {
             $this->_ignoreEvent = true;
             //$result = $this->owner->update(false, $attributes);
             if (false && method_exists($this->owner, 'saveAll')) {
                 $result = $this->owner->saveAll(false, $hasParentModel, false, $attributes);
             } else {
                 $result = $this->owner->save(false, $attributes, $hasParentModel, $fromSaveAll);
             }
             $this->_ignoreEvent = false;
             if ($result && $updateChildPaths) {
                 // only if we have children
                 if ($this->owner->getAttribute($this->rightAttribute) > $this->owner->getAttribute($this->leftAttribute) + 1) {
                     $condition = $db->quoteColumnName($this->leftAttribute) . '>' . $this->owner->getAttribute($this->leftAttribute) . ' AND ' . $db->quoteColumnName($this->rightAttribute) . '<' . $this->owner->getAttribute($this->rightAttribute);
                     $params = [];
                     if ($this->hasManyRoots) {
                         $condition .= ' AND ' . $db->quoteColumnName($this->rootAttribute) . '=:' . $this->rootAttribute;
                         $params[':' . $this->rootAttribute] = $this->owner->getAttribute($this->rootAttribute);
                     }
                     $updateColumns = [];
                     $pathLength = Tools::strlen($this->_previousPath) + 1;
                     // SQL Server: SUBSTRING() rather than SUBSTR
                     // SQL Server: + instead of CONCAT
                     if ($db->getDriverName() == 'mssql') {
                         $updateColumns[$this->pathAttribute] = new Expression($db->quoteValue($this->owner->getAttribute($this->pathAttribute)) . ' + SUBSTRING(' . $db->quoteColumnName($this->pathAttribute) . ', ' . $pathLength . '))');
                     } else {
                         $updateColumns[$this->pathAttribute] = new Expression('CONCAT(' . $db->quoteValue($this->owner->getAttribute($this->pathAttribute)) . ', SUBSTR(' . $db->quoteColumnName($this->pathAttribute) . ', ' . $pathLength . '))');
                     }
                     $result = $this->owner->updateAll($updateColumns, $condition, $params);
                 }
             }
             if ($result && $nameChanged) {
                 $result = $this->afterRenameNode($this->_previousPath);
             }
         } catch (\Exception $e) {
             if (isset($transaction)) {
                 $transaction->rollback();
             }
             throw $e;
         }
         if (isset($transaction)) {
             if (!$result) {
                 $transaction->rollback();
             } else {
                 $transaction->commit();
             }
         }
         $this->_previousPath = '';
     }
     return $result;
 }
 /**
  * This method is called at the beginning of a deleteFull() request on a record array
  *
  * @param boolean $hasParentModel
  *        whether this method was called from the top level or by a parent
  *        If false, it means the method was called at the top level
  * @return boolean whether the deleteFull() method call should continue
  *        If false, deleteFull() will be cancelled.
  */
 public function beforeDeleteFullInternal($hasParentModel = false)
 {
     $this->clearActionErrors();
     $this->resetChildHasChanges();
     $canDeleteFull = true;
     if (!$hasParentModel) {
         //$event = new ModelEvent;
         //$this->trigger(self::EVENT_BEFORE_SAVE_ALL, $event);
         //$canSaveAll = $event->isValid;
     }
     if ($this->getReadOnly()) {
         // will be ignored during deleteFull()
     } elseif (!$this->getCanDelete()) {
         // will be ignored during deleteFull()
     } else {
         if ($canDeleteFull) {
             if ($this->count()) {
                 $iterator = $this->getIterator();
                 while ($iterator->valid()) {
                     $isReadOnly = false;
                     $canDelete = true;
                     if ($iterator->current() instanceof ActiveRecordReadOnlyInterface) {
                         $isReadOnly = $iterator->current()->getReadOnly();
                         $canDelete = $iterator->current()->getCanDelete();
                     }
                     if (!$isReadOnly && $canDelete) {
                         $this->setChildHasChanges($iterator->key());
                         if ($iterator->current() instanceof ActiveRecordSaveAllInterface) {
                             $this->setChildOldValues($iterator->key(), $iterator->current()->getResetDataForFailedSave());
                         } else {
                             $this->setChildOldValues($iterator->key(), array('new' => $iterator->current()->getIsNewRecord(), 'oldValues' => $iterator->current()->getOldAttributes(), 'current' => $iterator->current()->getAttributes()));
                         }
                         $canDeleteThis = true;
                         if ($iterator->current() instanceof ActiveRecordSaveAllInterface) {
                             $canDeleteThis = $iterator->current()->beforeDeleteFullInternal(true);
                             if (!$canDeleteThis) {
                                 if (method_exists($iterator->current(), 'hasActionErrors')) {
                                     if ($iterator->current()->hasActionErrors()) {
                                         $this->mergeActionErrors($iterator->current()->getActionErrors());
                                     }
                                 }
                             }
                         } elseif (method_exists($iterator->current(), 'beforeDeleteFull')) {
                             $canDeleteThis = $iterator->current()->beforeDeleteFull();
                             if (!$canDeleteThis) {
                                 $errors = $iterator->current()->getErrors();
                                 foreach ($errors as $errorField => $errorDescription) {
                                     $this->addActionError($errorDescription, 0, $errorField, Tools::getClassName($iterator->current()));
                                 }
                             }
                         }
                         if (!$canDeleteThis) {
                             $canDeleteFull = false;
                         }
                     }
                     $iterator->next();
                 }
             }
         }
     }
     if ($this->hasActionErrors()) {
         $canDeleteFull = false;
     } elseif (!$canDeleteFull) {
         $this->addActionError('beforeDeleteFullInternal checks failed');
     }
     if (!$canDeleteFull) {
         $this->resetChildHasChanges();
     }
     return $canDeleteFull;
 }
 /**
  * Save the current objects attributes
  *
  * @param boolean $runValidation
  *        should validations be executed on all models before allowing saveAll()
  * @param boolean $hasParentModel
  *        whether this method was called from the top level or by a parent
  *        If false, it means the method was called at the top level
  * @param boolean $fromSaveAll
  *        has the save() call come from saveAll() or not
  * @return boolean
  *        did save() successfully process
  */
 public function save($runValidation = true, $hasParentModel = false, $push = false, $fromSaveAll = false)
 {
     if ($this->getReadOnly() && !$hasParentModel) {
         // return failure if we are at the top of the tree and should not be asking to saveAll
         // not allowed to amend or delete
         $message = 'Attempting to save on ' . Tools::getClassName($this) . ' readOnly model';
         //$this->addActionError($message);
         throw new Exception($message);
     } elseif ($this->getReadOnly() && $hasParentModel) {
         $message = 'Skipping save on ' . Tools::getClassName($this) . ' readOnly model';
         $this->addActionWarning($message);
         return true;
     }
     $allOk = true;
     if (($this->loaded || $this->isNewRecord && $this->isNewPrepared) && $this->changedData) {
         if ($this->entityId === false) {
             throw new Exception('No entity id available for ' . __METHOD__ . '()');
         }
         if (!$this->objectId) {
             throw new Exception('No object id available for ' . __METHOD__ . '()');
         }
         $thisTime = time();
         $attributeDefs = $this->getEntityAttributeList();
         // we do not record modified, modifiedBy, created or createdBy against individual attributes but we will support
         // automatically updating them if these attributeNames have been setup as their own attributes for this entity
         if (\Yii::$app->has('user')) {
             try {
                 if (\Yii::$app->user->isGuest) {
                     $userId = 0;
                 } else {
                     $userId = \Yii::$app->user->getId();
                 }
             } catch (InvalidConfigException $e) {
                 if ($e->getMessage() == 'User::identityClass must be set.') {
                     $userId = 0;
                 } else {
                     throw $e;
                 }
             }
         }
         $extraChangeFields = array();
         if (array_key_exists('modifiedAt', $attributeDefs)) {
             if (!array_key_exists('modifiedAt', $this->changedData)) {
                 $exists = array_key_exists('modifiedAt', $this->data);
                 $this->changedData['modifiedAt'] = array_key_exists('modifiedAt', $this->data) ? $this->data['modifiedAt'] : Tools::DATE_TIME_DB_EMPTY;
                 $this->data['modifiedAt'] = date(Tools::DATETIME_DATABASE, $thisTime);
                 if ($this->lazyAttributes && array_key_exists('modifiedAt', $this->lazyAttributes)) {
                     unset($this->lazyAttributes['modifiedAt']);
                 }
             }
         }
         if (array_key_exists('modifiedBy', $attributeDefs)) {
             if (!array_key_exists('modifiedBy', $this->changedData)) {
                 if (!isset($this->data['modifiedBy']) || $this->data['modifiedBy'] != $userId) {
                     $this->changedData['modifiedBy'] = array_key_exists('modifiedBy', $this->data) ? $this->data['modifiedBy'] : 0;
                     $this->data['modifiedBy'] = $userId;
                     if ($this->lazyAttributes && array_key_exists('modifiedBy', $this->lazyAttributes)) {
                         unset($this->lazyAttributes['modifiedBy']);
                     }
                 }
             }
         }
         if (array_key_exists('createdAt', $attributeDefs)) {
             if (!array_key_exists('createdAt', $this->changedData)) {
                 $exists = array_key_exists('createdAt', $this->data);
                 if (!$exists || $exists && $this->data['createdAt'] == Tools::DATE_TIME_DB_EMPTY) {
                     $this->changedData['createdAt'] = array_key_exists('createdAt', $this->data) ? $this->data['createdAt'] : Tools::DATE_TIME_DB_EMPTY;
                     $this->data['createdAt'] = date(Tools::DATETIME_DATABASE, $thisTime);
                     if ($this->lazyAttributes && array_key_exists('created', $this->lazyAttributes)) {
                         unset($this->lazyAttributes['createdAt']);
                     }
                 }
             }
         }
         if (array_key_exists('createdBy', $attributeDefs)) {
             if (!array_key_exists('createdBy', $this->changedData)) {
                 $exists = array_key_exists('createdBy', $this->data);
                 if (!$exists || $exists && $this->data['createdBy'] != $userId) {
                     $this->changedData['createdBy'] = array_key_exists('createdBy', $this->data) ? $this->data['createdBy'] : 0;
                     $this->data['createdBy'] = $userId;
                     if ($this->lazyAttributes && array_key_exists('createdBy', $this->lazyAttributes)) {
                         unset($this->lazyAttributes['createdBy']);
                     }
                 }
             }
         }
         if (!$this->changedData) {
             $updateColumns = $this->data;
         } else {
             $updateColumns = array();
             foreach ($this->changedData as $field => $value) {
                 $updateColumns[$field] = $this->data[$field];
             }
         }
         foreach ($updateColumns as $attributeName => $attributeValue) {
             $attributeId = 0;
             $attributeDef = isset($attributeDefs[$attributeName]) ? $attributeDefs[$attributeName] : false;
             if ($attributeDef) {
                 $attributeId = $attributeDef['id'];
             }
             $ok = false;
             if ($attributeId) {
                 $attributeValue = Tools::formatAttributeValue($attributeValue, $attributeDef);
                 if ($attributeDef['deleteOnDefault'] && $attributeValue === Tools::formatAttributeValue($attributeDef['defaultValue'], $attributeDef)) {
                     // value is default so we will remove it from the attribtue table as not required
                     $ok = true;
                     if (array_key_exists($attributeName, $this->attributeValues)) {
                         $ok = $this->attributeValues[$attributeName]->deleteFull(true);
                         if ($ok) {
                             $this->setChildOldValues($attributeName, true, 'deleted');
                         } else {
                             if ($this->attributeValues[$attributeName]->hasActionErrors()) {
                                 $this->mergeActionErrors($this->attributeValues[$attributeName]->getActionErrors());
                             } else {
                                 $this->addActionError('Failed to delete attribute', 0, $attributeName);
                             }
                         }
                     }
                 } else {
                     switch (strtolower($attributeDef['dataType'])) {
                         case 'boolean':
                             $attributeValue = $attributeValue ? '1' : '0';
                             break;
                         default:
                             break;
                     }
                     if (is_null($attributeValue)) {
                         // typically where null is permitted it will be the default value with deleteOnDefault set, so should have been caught in the deleteOnDefault
                         if ($attributeDef['isNullable']) {
                             $attributeValue = '__NULL__';
                             // we do not want to allow null in the attribute database so use this string to denote null when it is permitted
                         } else {
                             $attributeValue = '__NULL__';
                             // needs to be caught elsewhere
                         }
                     }
                     if (!array_key_exists($attributeName, $this->attributeValues)) {
                         $this->attributeValues[$attributeName] = new $this->attributeValuesClass();
                         $this->attributeValues[$attributeName]->entityId = $this->entityId;
                         $this->attributeValues[$attributeName]->attributeId = $attributeId;
                         // this is a new entry that has not been included in the childHasChanges array yet
                         $this->setChildHasChanges($attributeName);
                         $this->setChildOldValues($attributeName, $this->attributeValues[$attributeName]->getResetDataForFailedSave());
                     }
                     if ($this->newObjectId) {
                         $this->attributeValues[$attributeName]->objectId = $this->newObjectId;
                     } else {
                         $this->attributeValues[$attributeName]->objectId = $this->objectId;
                     }
                     $this->attributeValues[$attributeName]->value = $attributeValue;
                     $ok = $this->attributeValues[$attributeName]->save(false, null, true, true);
                     if (!$ok) {
                         if ($this->attributeValues[$attributeName]->hasActionErrors()) {
                             $this->mergeActionErrors($this->attributeValues[$attributeName]->getActionErrors());
                         } else {
                             $this->addActionError('Failed to save attribute', 0, $attributeName);
                         }
                     }
                 }
             }
             if (!$ok) {
                 $allOk = false;
             }
         }
         if ($allOk) {
             $this->changedData = array();
             $this->loaded = true;
             $this->isNewRecord = false;
         }
     }
     if ($allOk && $this->loaded && $this->newObjectId) {
         // we need to update the objectId for all attributes belonging to
         // the current object to a new value taking into account that not
         // all attributes might have been loaded yet, if any.
         foreach ($this->attributeValues as $attributeName => $attributeValue) {
             $this->attributeValues[$attributeName]->objectId = $this->newObjectId;
             $this->attributeValues[$attributeName]->setOldAttribute('objectId', $this->newObjectId);
         }
         $attributeValuesClass = $this->attributeValuesClass;
         $ok = $attributeValuesClass::updateAll(array('objectId' => $this->newObjectId), array('objectId' => $this->objectId));
         $this->objectId = $this->newObjectId;
         $this->newObjectId = false;
     }
     return $allOk;
 }
 /**
  * Adds a new action warning
  * @param string $message new warning message
  * @param integer $code new warning code
  * @param string $attribute attribute to which the error applies
  * @param string $modelName model to which the error applies
  */
 public function addActionWarning($message, $code = 0, $attribute = '', $modelName = null)
 {
     $message = is_array($message) ? $message : array($message);
     $this->actionWarnings[] = array('message' => $message, 'code' => $code, 'attribute' => $attribute, 'model' => $modelName !== null ? $modelName : (true ? Tools::getClassName($this) : get_called_class()));
 }
 /**
  * PHP setter magic method.
  * This method is overridden so that AR attributes can be accessed like properties,
  * but only if the current model is not read only
  * @param string $name property name
  * @param mixed $value property value
  * @throws Exception if the current record is read only
  */
 public function __set($name, $value)
 {
     if ($this->getReadOnly()) {
         throw new Exception('Attempting to set attribute `' . $name . '` on a read only ' . Tools::getClassName($this) . ' model');
     }
     parent::__set($name, $value);
 }