예제 #1
0
파일: sql_role.php 프로젝트: cepharum/txf
 /**
  * Makes user adopting current role.
  *
  * @param user $user user to adopt current role
  * @return $this
  * @throws \Exception on failing to adopt role
  */
 public function makeAdoptedBy(user $user)
 {
     $role = $this;
     if (!$this->_source->transaction()->wrap(function (datasource\connection $db) use($role, $user) {
         $userID = $user->getUUID();
         $roleID = $role->id;
         $count = $db->createQuery('user_role')->addCondition('user_uuid=?', true, $userID)->addCondition('role_id=?', true, $roleID)->execute(true)->cell();
         if ($count > 0) {
             // role has been adopted before
             return true;
         }
         $qSet = $db->qualifyDatasetName('user_role');
         return $db->test('INSERT INTO ' . $qSet . ' (user_uuid,role_id) VALUES (?,?)', $userID, $roleID) !== false;
     })) {
         throw new \RuntimeException(sprintf('adopting role %s by user %s failed', $this->label, $user->getName()));
     }
     return $this;
 }
예제 #2
0
파일: model.php 프로젝트: cepharum/txf
 /**
  * Removes current instance of model from datasource.
  *
  * @note On deleting current instance all tightly related instances are
  *       deleted implicitly.
  *
  * @note On deleting current instance this object is released and can't be
  *       used for managing model instance, anymore.
  *
  * @throws \RuntimeException
  * @throws datasource_exception
  */
 public function delete()
 {
     if (!is_array($this->_id)) {
         throw new \RuntimeException('item does not exist (anymore)');
     }
     /*
      * wrap deletion of item and its tight relations in a transaction
      */
     $item = $this;
     $set = static::set();
     $relations = static::$relations;
     $class = get_called_class();
     if (!$this->_source->transaction()->wrap(function (connection $connection) use($item, $set, $relations, $class) {
         // cache information on item to delete
         $idValues = array_values($item->id());
         $record = count($relations) ? $item->load() : array();
         /*
          * step 1) actually delete current item
          */
         $qSet = $connection->qualifyDatasetName($set);
         if ($connection->test(sprintf('DELETE FROM %s WHERE %s', $qSet, $item->filter()), $idValues) === false) {
             throw new datasource_exception($connection, 'failed to delete requested model instance');
         }
         /*
          * step 2) update all related items
          */
         // on deleting item relations have to be updated
         // - always null references on current item to be deleted
         foreach ($relations as $relationName => $relationSpec) {
             // detect if relation is "tight"
             $isTightlyBound = in_array('tight', (array) @$relationSpec['options']) || @$relationSpec['options']['tight'];
             // get first reference in relation
             /** @var model_relation $relation */
             $relation = call_user_func(array($class, "relation"), $relationName);
             $firstNode = $relation->nodeAtIndex(0);
             $secondNode = $relation->nodeAtIndex(1);
             $secondNodeSet = $secondNode->getName(true);
             // prepare collection of information on second node
             $onSecond = array('null' => array(), 'filter' => array('properties' => array(), 'values' => array()));
             // extract reusable code to prepare filter for selecting record
             // of second node actually related to deleted item
             $getFilter = function () use(&$onSecond, $connection, $firstNode, $secondNode, $record) {
                 // retrieve _qualified and quoted_ names of predecessor's properties
                 $onSecond['filter']['properties'] = $secondNode->getPredecessorNames($connection);
                 foreach ($firstNode->getSuccessorNames() as $property) {
                     $onSecond['filter']['values'][] = @$record[$property];
                 }
             };
             // inspect type of relationship between first and second node of
             // current relation
             if ($secondNode->canBindOnPredecessor()) {
                 // second node in relation is referencing first one
                 // -> there are items of second node's model referring to
                 //    item removed above
                 // -> find records of all those items ...
                 $getFilter();
                 // ... at least for nulling their references on deleted item
                 $onSecond['null'] = $onSecond['filter']['properties'];
             } else {
                 // first node in relation is referencing second one
                 // -> deleted item was referencing item of second node's model
                 //    -> there is basically no need to update any foreign
                 //       references on deleted item
                 if ($isTightlyBound) {
                     // relation is marked as "tight"
                     // -> need to delete item referenced by deleted item
                     $getFilter();
                 }
             }
             // convert filtering properties of second node into set of assignments
             $filter = array_map(function ($name) {
                 return "{$name}=?";
             }, $onSecond['filter']['properties']);
             if ($isTightlyBound) {
                 // in tight relation immediately related elements are
                 // deleted as well
                 $secondModel = $secondNode->getModel();
                 if ($secondModel->isVirtual()) {
                     // second model is virtual, only
                     // -> it's okay to simply delete matching records in datasource
                     $qSet = $connection->qualifyDatasetName($secondNodeSet);
                     $term = implode(' AND ', $filter);
                     if (!$connection->test("DELETE FROM {$qSet} WHERE {$term}", $onSecond['filter']['values'])) {
                         throw new datasource_exception($connection, 'failed to delete instances of tightly related items in relation ' . $relationName);
                     }
                     // TODO: add support for tightly bound relation in opposite reference of this virtual node
                 } else {
                     // query data source for IDs of all tightly related items
                     $query = $connection->createQuery($secondNodeSet);
                     // - select related items using properties involved in relation
                     foreach ($onSecond['filter']['properties'] as $index => $name) {
                         $query->addFilter("{$name}=?", true, $onSecond['filter']['values'][$index]);
                     }
                     // - fetch all properties used to identify items
                     $ids = $secondModel->getIdProperties();
                     foreach ($ids as $index => $name) {
                         $query->addProperty($connection->quoteName($name), "i{$index}");
                     }
                     // iterate over all matches for deleting every one
                     $matches = $query->execute();
                     $iCount = count($ids);
                     while ($match = $matches->row()) {
                         // extract properly sorted ID from matching record
                         $id = array();
                         for ($i = 0; $i < $iCount; $i++) {
                             $id[$ids[$i]] = $match["i{$i}"];
                         }
                         // select item of model and delete it
                         $secondModel->selectInstance($connection, $id)->delete();
                     }
                 }
             } else {
                 if (count($onSecond['null'])) {
                     // need to null foreign references on deleted item
                     $values = array_merge(array_pad(array(), count($onSecond['filter']['values']), null), $onSecond['filter']['values']);
                     $qSet = $connection->qualifyDatasetName($secondNodeSet);
                     $matching = implode(' AND ', $filter);
                     $setting = implode(',', $filter);
                     if (!$connection->test("UPDATE {$qSet} SET {$setting} WHERE {$matching}", $values)) {
                         throw new datasource_exception($connection, 'failed to null references on deleted item in relation ' . $relationName);
                     }
                 }
             }
         }
         return true;
     })) {
         throw new datasource_exception($this->_source, 'failed to completely delete item and its tightly bound relations');
     }
     // drop that item now ...
     $this->_id = null;
 }
예제 #3
0
파일: editor.php 프로젝트: cepharum/txf
 /**
  * Processes input on current editor.
  *
  * @param callable $validatorCallback
  * @return bool|string false on input failures requiring user action,
  *                     "saved" on input successfully saved to data source,
  *                     "cancel" on user pressing cancel button,
  *                     "delete" on user deleting record
  */
 public function processInput($validatorCallback = null)
 {
     if ($this->hasInput()) {
         switch (input::vget('_cmd')) {
             case 'cancel':
                 // permit closing editor due to user requesting to cancel editing
                 return 'cancel';
             case 'delete':
                 // delete current edited item
                 if ($this->may['delete'] && $this->item) {
                     $ctx = $this;
                     $item = $this->item;
                     $fields = $this->fields;
                     $this->datasource->transaction()->wrap(function () use($ctx, $item, $fields) {
                         foreach ($fields as $field) {
                             /** @var model_editor_field $field */
                             $field->type()->onDeleting($ctx, $item, $field);
                         }
                         $item->delete();
                         return true;
                     });
                     $this->item = null;
                     return 'delete';
                 }
                 view::flash(\de\toxa\txf\_L('You must not delete this item.'), 'error');
                 return false;
             case 'save':
                 // extract some protected properties from current instance to be used in transaction-wrapped callback
                 $ctx = $this;
                 $class = $this->class;
                 $source = $this->datasource;
                 $fields = $this->fields;
                 $enabled = $this->enabled;
                 $item = $this->item;
                 $fixed = $this->getFixed();
                 $errors = array();
                 $this->onCreating = !$this->hasItem();
                 if (!$this->onCreating && !$this->may['edit']) {
                     view::flash(\de\toxa\txf\_L('You must not edit this item.'), 'error');
                     return false;
                 }
                 // wrap modification on model in transaction
                 $success = $source->transaction()->wrap(function () use($ctx, $class, $source, $fields, $enabled, $fixed, &$item, &$errors, $validatorCallback) {
                     $properties = array();
                     foreach ($fields as $property => $definition) {
                         /** @var model_editor_field $definition */
                         if (!count($enabled) || !@$enabled[$property]) {
                             try {
                                 // normalize input
                                 $input = call_user_func(array($definition->type(), 'normalize'), $ctx->getValue($property, $definition->isCustom()), $property, $ctx);
                                 // validate input
                                 $success = call_user_func(array($definition->type(), 'validate'), $input, $property, $ctx);
                                 // save input if valid
                                 if ($success) {
                                     $properties[$property] = $input;
                                 } else {
                                     $errors[$property] = \de\toxa\txf\_L('Your input is invalid.');
                                 }
                             } catch (\Exception $e) {
                                 $errors[$property] = $e->getMessage();
                             }
                         }
                     }
                     if (count($errors)) {
                         return false;
                     }
                     if (is_callable($validatorCallback)) {
                         // provide opportunity to qualify properties for validation
                         $qualified = $properties;
                         foreach ($fields as $field) {
                             /** @var model_editor_field $field */
                             $qualified = $field->type()->beforeValidating($ctx, $item, $qualified, $field);
                         }
                         // invoke custom callback given those qualified copy of properties for validating
                         $localErrors = call_user_func($validatorCallback, $qualified, $errors, $item ? $item->id() : null);
                         if ($localErrors === false || is_string($localErrors) || is_array($localErrors) && count($localErrors)) {
                             if (is_array($localErrors)) {
                                 $errors = array_merge($errors, $localErrors);
                             } else {
                                 if (is_string($localErrors)) {
                                     view::flash($localErrors, 'error');
                                 }
                             }
                             return false;
                         }
                     }
                     if ($item) {
                         // on updating item -> don't adjust values of
                         // properties marked as fixed
                         foreach ($fixed as $name => $value) {
                             unset($properties[$name]);
                         }
                     } else {
                         // creating new item -> ensure to use fixed initial
                         // values provided additionally
                         foreach ($fixed as $name => $value) {
                             $properties[$name] = $value;
                         }
                     }
                     // optionally pre-process saving properties of item
                     foreach ($fields as $field) {
                         /** @var model_editor_field $field */
                         $properties = $field->type()->beforeStoring($ctx, $item, $properties, $field);
                     }
                     if ($item) {
                         // update properties of existing item
                         foreach ($properties as $name => $value) {
                             $item->__set($name, $value);
                         }
                     } else {
                         // create new item
                         $item = $class->getMethod('create')->invoke(null, $source, $properties);
                         // tell all elements to have item now
                         foreach ($fields as $field) {
                             /** @var model_editor_field $field */
                             $field->type()->onSelectingItem($ctx, $item, $field);
                         }
                     }
                     // optionally post-process saving properties of item
                     foreach ($fields as $field) {
                         /** @var model_editor_field $field */
                         $item = $field->type()->afterStoring($ctx, $item, $properties, $field);
                     }
                     return true;
                 });
                 // transfer adjusted properties back to protected scope of current instance
                 $this->errors = $errors;
                 // write back item created or probably replaced by afterStoring() call in transaction
                 $this->item = $item;
                 if ($success) {
                     // permit closing editor after having saved all current input
                     view::flash(\de\toxa\txf\_L('Your changes have been saved.'));
                     return 'saved';
                 }
                 view::flash(\de\toxa\txf\_L('Failed to save your changes.'), 'error');
         }
     }
     // don't close editor
     return false;
 }