/** * Retrieves HTML code of relating elements of initially related or * explicitly selected element. * * @param array $data custom data to be passed to template on rendering * @param string $template name of custom template to use instead of default one on rendering * @param int $listNodeAtIndex index of node to list properties of * (default: node at opposite end of relation) * @return string rendering result */ public function render($data = array(), $template = null, $listNodeAtIndex = -1) { $query = $this->query(); // extend query to fetch all properties of selected node's model $query->addProperty($this->datasource->qualifyDatasetName($this->nodeAtIndex($listNodeAtIndex)->getName()) . '.*'); // process query $matches = $query->execute()->all(); // start variable space initialized using provided set of custom data $data = variable_space::fromArray($data); // add fetched relation instances to variable space $data->update('matches', $matches); // add reference on current relation manager instance $data->update('relation', $this); // render variable space using selected or default template return view::render($template ? $template : 'model/relation/generic', $data); }
/** * 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; }
/** * Compiles SQL term for use in WHERE clauses to select records matching all * properties in given filter. * * Resulting term is using parameter markers for values of those properties * to be matched. These values have to be given on querying data source. * * @param connection $source data source to use for quoting names * of filtering properties * @param array $filter set of "properties" and "values" * @return string SQL-term */ protected function _compileFilterTerm(connection $source, $filter) { return implode(' AND ', array_map(function ($n) use($source) { return $source->quoteName($n) . '=?'; }, $filter['properties'])); }
/** * Qualifies and/or quotes provided property names. * * @param array $arrNames set of property names to process * @param string|null $strSetOrAlias name/alias of data set, provide for qualified names, omit for unqualified names * @param connection $source used optionally to quote names for use in querying connected data source * @return array set of qualified and/or quoted names of properties */ protected function _qualifyNames($arrNames, $strSetOrAlias = null, connection $source = null) { $parts = array(); if (is_string($strSetOrAlias)) { $parts[] = $source ? $source->qualifyDatasetName(trim($strSetOrAlias)) : $strSetOrAlias; } foreach ($arrNames as $key => $name) { $temp = $parts; $temp[] = $source ? $source->quoteName(trim($name)) : trim($name); $arrNames[$key] = implode('.', $temp); } return $arrNames; }
/** * Retrieves query on datasource prepared to basically operate on current * model's data set. * * @param string $alias optional alias of model's data set in query * @return query customizable query on current model */ public function query($alias = null) { $alias = trim($alias); return $this->_source->createQuery(static::set() . ($alias != '' ? " {$alias}" : '')); }
/** * 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; }
/** * Retrieves name of data set optionally combined with declared alias. * * On providing datasource connection either part of resulting name is * quoted (and qualified) according to quoting and qualification rules of * connected datasource. * * @param connection $db * @return string */ public function getFullName(connection $db = null) { $name = $this->getName(true); $alias = $this->alias ? $this->alias : null; if ($db) { $name = $db->qualifyDatasetName($name); if ($alias) { $alias = $db->quoteName($alias); } } return $alias ? $name . ' ' . $alias : $name; }
/** * Declares data set of model in provided data source. * * @throws datasource_exception on failing to declare model's data set * @param connection $source * @return model_relation_model current instance */ public function declareInDatasource(connection $source) { $setName = $this->getSetName(); // test runtime cache for including mark on having declared data set in // provided data source before if (array_key_exists($setName, self::$declaredCached)) { foreach (self::$declaredCached[$setName] as $s) { if ($s === $source) { return $this; } } } if ($this->isVirtual()) { // wrapping faked model // -> use provided description to declare model's set in data source if (!$source->createDataset($setName, $this->definition, $this->primaries)) { throw new datasource_exception($source, 'failed to create data set of model ' . $setName); } } else { // wrapping actually existing model // -> call its model::updateSchema() on provided data source $this->reflection->getMethod('updateSchema')->invoke(null, $source); } // add mark to runtime cache on having declared model's data set in // provided data source self::$declaredCached[$setName] = array_merge((array) self::$declaredCached[$setName], array($source)); return $this; }