Ejemplo n.º 1
0
 /**
  * @param ActiveRecord $model
  * @param $relationName
  */
 public function __construct(ActiveRecord $model, $relationName)
 {
     $this->owner = $model;
     $this->relationName = $relationName;
     $this->relation = $this->owner->getRelation($relationName);
     /** @var ActiveQuery $via */
     $via = is_array($this->relation->via) ? $this->relation->via[1] : $this->relation->via;
     /** @var \yii\db\ActiveRecord $viaClass */
     $this->viaTable = ($viaClass = $via->modelClass) ? $viaClass::tableName() : reset($via->from);
     $this->relationAttribute = $this->relation->link;
     foreach ($via->link as $viaAttribute => $ownerAttribute) {
         $this->condition[$viaAttribute] = $this->owner->{$ownerAttribute};
     }
 }
Ejemplo n.º 2
0
 public static function relation(ActiveRecord $model, $relation_name, $options = [])
 {
     /* @var ActiveRecord|YcmModelUtilTrait $model */
     $relation = $model->getRelation($relation_name);
     $config = [$relation_name, 'widget', 'widgetClass' => Select2::className(), 'data' => RelationHelper::getSelectChoices($model, $relation_name), 'hideSearch' => false, 'options' => ['multiple' => $relation->multiple, 'placeholder' => 'Select...'], 'pluginOptions' => ['allowClear' => true]];
     return ArrayHelper::merge($config, $options);
 }
Ejemplo n.º 3
0
 /**
  * @param ActiveRecord $model
  * @param $relation_name
  * @return null|\yii\db\ActiveQuery|\yii\db\ActiveQueryInterface
  */
 public static function getRelation(ActiveRecord $model, $relation_name)
 {
     $relation = null;
     foreach (explode('.', $relation_name) as $relation_subname) {
         $relation = $model->getRelation($relation_subname);
         $model = new $relation->modelClass();
     }
     return $relation;
 }
Ejemplo n.º 4
0
 /**
  * Get available relation choices
  * @param $relation_name
  * @return mixed
  */
 public static function getSelectChoices(ActiveRecord $model, $relation_name)
 {
     $class = $model->className();
     if (!isset(self::$relationsChoices[$class][$relation_name])) {
         self::$relationsChoices[$class][$relation_name] = [];
         $relation = $model->getRelation($relation_name, false);
         if ($relation) {
             self::$relationsChoices[$class][$relation_name] = ModelHelper::getSelectChoices(new $relation->modelClass());
         }
     }
     return self::$relationsChoices[$class][$relation_name];
 }
 public function afterSave()
 {
     $data = [];
     if (isset($_POST[$this->owner->formName()])) {
         $data = $_POST[$this->owner->formName()];
     }
     if ($data) {
         foreach ($data as $attribute => $value) {
             if (!in_array($attribute, $this->relations)) {
                 continue;
             }
             if (is_array($value)) {
                 $relation = $this->owner->getRelation($attribute);
                 if ($relation->via !== null) {
                     /** @var ActiveRecord $foreignModel */
                     $foreignModel = $relation->modelClass;
                     $this->owner->unlinkAll($attribute, true);
                     if (is_array(current($value))) {
                         foreach ($value as $data) {
                             if (isset($data[$foreignModel::primaryKey()[0]]) && $data[$foreignModel::primaryKey()[0]] > 0) {
                                 $fm = $foreignModel::findOne($data[$foreignModel::primaryKey()[0]]);
                                 $fm->load($data, '');
                                 $this->owner->link($attribute, $fm);
                             }
                         }
                     } else {
                         foreach ($value as $fk) {
                             if (preg_match('~^\\d+$~', $fk)) {
                                 $this->owner->link($attribute, $foreignModel::findOne($fk));
                             }
                         }
                     }
                 }
             } else {
                 $this->owner->unlinkAll($attribute, true);
             }
         }
     }
 }
Ejemplo n.º 6
0
 /**
  * Loads a specific translation model
  *
  * @param string $language the language to return
  *
  * @return null|\yii\db\ActiveQuery|static
  */
 private function getNewTranslation($language)
 {
     $translation = null;
     /** @var \yii\db\ActiveQuery $relation */
     $relation = $this->owner->getRelation($this->relation);
     /** @var ActiveRecord $class */
     $class = $relation->modelClass;
     //if ($translation === null) {
     $translation = new $class();
     $translation->{key($relation->link)} = $this->owner->getPrimaryKey();
     $translation->{$this->languageAttribute} = $language;
     //}
     return $translation;
 }
Ejemplo n.º 7
0
 protected static function editableRelationConfig(ActiveRecord $model, $relation_name)
 {
     /* @var ActiveRecord|YcmModelUtilTrait $model */
     $relation = $model->getRelation($relation_name);
     /* @var Module $module */
     $module = \Yii::$app->getModule('ycm-utils');
     /* @var ActiveRecord|YcmModelUtilTrait $relationModel */
     $relationModel = \Yii::createObject($relation->modelClass);
     $modelChoices = ModelHelper::getSelectChoices($relationModel);
     /**
      * @todo #1 implement fill ajax loading with ajax mapping
      * @fixme #2 Relation is multiple, after live edit fix JS error `Cannot read property '[object Array]' of null`
      */
     return ['attribute' => $relation->multiple ? $relation_name . $model->method_postfix_relation_ids : reset($relation->link), 'label' => ucfirst($relation_name), 'filter' => $modelChoices, 'value' => $relation->multiple ? function ($m) use($relation_name, $modelChoices, $model) {
         return implode(', ', array_map(function ($relation_id) use($modelChoices) {
             return $modelChoices[$relation_id];
         }, array_values((array) $m->{$relation_name . $model->method_postfix_relation_ids})));
     } : null, 'editableOptions' => ['inputType' => Editable::INPUT_SELECT2, 'size' => 'lg', 'options' => ['options' => ['multiple' => $relation->multiple], 'data' => $modelChoices, 'pluginOptions' => count($modelChoices) > $model->ajax_enable_threshold ? ['minimumInputLength' => 3, 'ajax' => ['url' => Url::to(['/ycm/model/choices', 'name' => $module->ycm->getModelName($relationModel)]), 'dataType' => 'json', 'processResults' => new JsExpression('function (results) { return results; }')]] : null], 'displayValueConfig' => !$relation->multiple ? $modelChoices : null]];
 }
Ejemplo n.º 8
0
 /**
  * Renders grid column for list value of via table data
  * @param ActiveRecord|bool $model
  * @param $attribute
  * @param array $options
  * @return array
  * @throws \Exception
  */
 public static function viaListFormat($model, $attribute, $options = [])
 {
     $relation = $model->getRelation($attribute);
     $relationClass = $relation->modelClass;
     $columns = Yii::$app->db->getTableSchema($relationClass::tableName())->columnNames;
     $titles = array_intersect(['title', 'name', 'username'], $columns);
     if (!($title = reset($titles))) {
         throw new \Exception(Yii::t('app', 'Relation does not have any title column'));
     }
     return array_merge(['attribute' => $attribute, 'format' => 'raw', 'value' => !$model->isNewRecord ? implode(', ', $relation->select($title)->column()) : function ($data) use($attribute, $title) {
         return implode(', ', $data->getRelation($attribute)->select($title)->column());
     }, 'filter' => false], $options);
 }
Ejemplo n.º 9
0
 /**
  * @param array $formFields
  * @param \yii\db\ActiveRecord $model
  * @param string $relation
  * @param array $hiddenAttributes
  * @param array $safeAttributes
  * @param bool $multiple true for multiple values inputs, usually used for search forms
  * @return array
  * @throws InvalidConfigException
  */
 protected static function addRelationField($formFields, $model, $relation, $hiddenAttributes, $safeAttributes, $multiple = false)
 {
     $activeRelation = $model->getRelation(Html::getAttributeName($relation));
     if (!$activeRelation->multiple) {
         // validate foreign keys only for hasOne relations
         $isHidden = false;
         foreach ($activeRelation->link as $left => $right) {
             if (!in_array($right, $safeAttributes)) {
                 return $formFields;
             }
             if (isset($hiddenAttributes[$right])) {
                 $formFields[$relation] = Html::activeHiddenInput($model, $right);
                 unset($hiddenAttributes[$right]);
                 $isHidden = true;
             }
         }
         if ($isHidden) {
             return $formFields;
         }
     }
     if (!Yii::$app->user->can($activeRelation->modelClass . '.read')) {
         return $formFields;
     }
     if (count($activeRelation->link) > 1) {
         throw new InvalidConfigException('Composite key relations are not supported by ' . get_called_class());
     }
     if ($activeRelation->multiple) {
         if (($field = static::getHasManyRelationField($model, $relation, $activeRelation)) !== null) {
             $formFields[$relation] = $field;
         }
     } else {
         if (($field = static::getHasOneRelationField($model, $relation, $activeRelation, $multiple)) !== null) {
             $formFields[$relation] = $field;
         }
     }
     return $formFields;
 }
Ejemplo n.º 10
0
 /**
  * @param ActiveRecord $model
  * @param string $relationName
  * @return string
  * @throws \Exception
  */
 protected static function getNameAttribute($model, $relationName)
 {
     /** @var ActiveRecord $class */
     $class = $model->getRelation($relationName)->modelClass;
     $attributes = Yii::$app->db->getTableSchema($class::tableName())->columnNames;
     if (!($existingNames = array_intersect(['name', 'title', 'username'], $attributes))) {
         throw new \Exception("Relation does not have name attribute: " . $class);
     }
     return reset($existingNames);
 }
Ejemplo n.º 11
0
 /**
  * @return \yii\db\ActiveQuery|\yii\db\ActiveQueryInterface
  */
 protected function getUploadRelation()
 {
     return $this->owner->getRelation($this->uploadRelation);
 }
Ejemplo n.º 12
0
 /**
  * Loads a specific relation with $data.
  * incremental: 		existing subitems are neither removed nor unlinked.
  * non-incremental:		existing (loaded) subitems are unlinked and/or deleted.
  * 
  * @param  ActiveRecord  $model 			model
  * @param  string        $relationName      the relation's name
  * @param  array         $data              data to load, including relational data
  * @param  array         $config            configuration array
  * @internal
  */
 private function setRelation(&$model, $relationName, &$data, &$config)
 {
     if (!$model->hasProperty($relationName)) {
         throw new \yii\base\UnknownPropertyException(sprintf('model {%s} has no relation {%s}', $model->className(), $relationName));
     }
     $relation =& $config[self::RELATIONS][$relationName];
     $formName = ArrayHelper::getValue($relation, self::FORMNAME, false);
     $scenario = ArrayHelper::getValue($relation, self::SCENARIO, $this->defaultScenario);
     $incremental = ArrayHelper::getValue($relation, self::INCREMENTAL, $this->defaultIncremental);
     $delete = ArrayHelper::getValue($relation, self::DELETE, $this->defaultDelete);
     $relationData = $formName == false ? $data : $data[$formName];
     $rel = $model->getRelation($relationName);
     $pattern = new $rel->modelClass();
     $recursive = !$pattern->hasMethod('isActiveDocument');
     $models = null;
     $relation[self::REMOVE] = [];
     $relation[self::LINK] = [];
     // relation is a collection or a single component
     if ($rel->multiple) {
         $models = [];
         if ($incremental) {
             // loop through array data and load sub models
             foreach ($relationData as $key => $value) {
                 $m = $this->loadModel($rel->modelClass, $scenario, $value, $relation, $recursive);
                 $models[] = $m;
             }
         } else {
             $sort = ArrayHelper::getValue($relation, self::SORTABLE, null);
             if ($sort !== null) {
                 $index = 0;
                 foreach ($relationData as $key => &$value) {
                     $relationData[$key][$sort] = $index++;
                 }
             }
             // loop through relation data, load data and detect removable sub models
             foreach ($model->{$relationName} as $item) {
                 $keys = $item->getPrimaryKey(true);
                 // try to find subitem in data
                 reset($relationData);
                 $found = false;
                 foreach ($relationData as $key => &$value) {
                     // normalize
                     if (!empty($formName)) {
                         $value = $value[$formName];
                     }
                     $modelKeys = array_intersect_key($value, $keys);
                     if (count(array_diff_assoc($modelKeys, $keys)) == 0) {
                         $m = $this->loadExistingModel($item, $scenario, $value, $relation, $recursive);
                         $models[] = $m;
                         $found = true;
                         // processed, so remove from data array
                         unset($relationData[$key]);
                         break;
                     }
                 }
                 // we have an existing item, but it was not loaded by $data, so mark for remove.
                 if (!$found) {
                     $relation[self::REMOVE][] = $item;
                 }
             }
             // everything left in $relationData is new model data
             // model might be existing, but not linked
             foreach ($relationData as $key => $value) {
                 // normalize
                 if (!empty($formName)) {
                     $value = $value[$formName];
                 }
                 $m = $this->loadModel($rel->modelClass, $scenario, $value, $relation, $recursive);
                 $models[] = $m;
                 $relation[self::LINK][$this->serializeKey($model)][] = $m;
             }
         }
     } else {
         // relation is a single component
         $oldItem = $model->{$relationName};
         $models = $this->loadModel($rel->modelClass, $scenario, $value, $relation, $recursive);
         if (!$incremental) {
             if ($oldItem !== null) {
                 $keys = $oldItem->getPrimaryKey(true);
                 if ($models !== null) {
                     $modelKeys = $models->getPrimaryKey(true);
                     if (count(array_diff_assoc($keys, $modelKeys)) !== 0) {
                         $relation[self::REMOVE][] = $oldItem;
                     }
                 } else {
                     $relation[self::REMOVE][] = $models;
                 }
             }
         }
     }
     if ($models !== null) {
         $model->populateRelation($relationName, $models);
     }
 }
Ejemplo n.º 13
0
 /**
  * @inheritdoc
  */
 public function getRelation($name, $throwException = true)
 {
     if (isset($this->relations[$name])) {
         return $this->relations[$name]();
     }
     return parent::getRelation($name, $throwException);
 }
 /**
  * @param ActiveRecord $model
  * @param array $with
  * @return ActiveRelationInterface[]
  */
 private function normalizeRelations($model, $with)
 {
     $relations = [];
     foreach ($with as $name => $callback) {
         if (is_integer($name)) {
             $name = $callback;
             $callback = null;
         }
         if (($pos = strpos($name, '.')) !== false) {
             // with sub-relations
             $childName = substr($name, $pos + 1);
             $name = substr($name, 0, $pos);
         } else {
             $childName = null;
         }
         if (!isset($relations[$name])) {
             $relation = $model->getRelation($name);
             $relation->primaryModel = null;
             $relations[$name] = $relation;
         } else {
             $relation = $relations[$name];
         }
         if (isset($childName)) {
             $relation->with[$childName] = $callback;
         } elseif ($callback !== null) {
             call_user_func($callback, $relation);
         }
     }
     return $relations;
 }
Ejemplo n.º 15
0
 /**
  * Loads posted data into model relation models
  * @param ActiveRecord $model
  * @param $relationName
  * @param $data
  * @param array $requiredData
  * @return array
  */
 public static function loadRelation(ActiveRecord $model, $relationName, $data, $requiredData = [])
 {
     $relationClass = $model->getRelation($relationName)->modelClass;
     return static::loadMultiple(@$data[(new $relationClass())->formName()], $relationClass, $model->{$relationName}, $requiredData);
 }
 /**
  * For each related model, try to save it first.
  * If set in the owner model, operation is done in a transactional way so if one of the models should not validate
  * or be saved, a rollback will occur.
  * This is done during the before validation process to be able to set the related foreign keys.
  * @param ActiveRecord $model
  * @param ModelEvent $event
  * @return bool
  */
 public function _saveRelatedRecords(ActiveRecord $model, ModelEvent $event)
 {
     if ($model->isNewRecord && $model->isTransactional($model::OP_INSERT) || !$model->isNewRecord && $model->isTransactional($model::OP_UPDATE) || $model->isTransactional($model::OP_ALL)) {
         $this->_transaction = $model->getDb()->beginTransaction();
     }
     try {
         foreach ($this->relations as $relationName) {
             if (array_key_exists($relationName, $this->_oldRelationValue)) {
                 // Relation was not set, do nothing...
                 $relation = $model->getRelation($relationName);
                 if (!empty($model->{$relationName})) {
                     if ($relation->multiple === false) {
                         // Save Has one relation new record
                         $pettyRelationName = Inflector::camel2words($relationName, true);
                         $this->_saveModelRecord($model->{$relationName}, $event, $pettyRelationName, $relationName);
                     } else {
                         // Save Has many relations new records
                         /** @var ActiveRecord $relationModel */
                         foreach ($model->{$relationName} as $i => $relationModel) {
                             $pettyRelationName = Inflector::camel2words($relationName, true) . " #{$i}";
                             $this->_validateRelationModel($pettyRelationName, $relationName, $relationModel, $event);
                         }
                     }
                 }
             }
         }
         if (!$event->isValid) {
             throw new Exception("One of the related model could not be validated");
         }
     } catch (Exception $e) {
         if ($this->_transaction instanceof Transaction && $this->_transaction->isActive) {
             $this->_transaction->rollBack();
             // If anything goes wrong, transaction will be rolled back
         }
         $event->isValid = false;
         // Stop saving, something went wrong
         return false;
     }
     return true;
 }
Ejemplo n.º 17
0
 /**
  * @inheritdoc
  */
 public function init()
 {
     parent::init();
     $this->relation = $this->model->getRelation($this->attribute);
 }
 /**
  * Returns queries that contain necessary joins and condition
  * to select only those records which are related directly or indirectly
  * with the current user.
  * @param ActiveRecord $model     must have the AuthorizerBehavior attached
  * @param array $relations        list of model relations to check, supports dot notation for indirect relations
  * @param IdentityInterface $user if null, Yii::$app->user->identity will be used
  * @param array $baseConditions
  * @param array $baseParams
  * @return ActiveQuery[]
  */
 public function getCompositeRelatedUserQuery($model, array $relations, $user, $baseConditions = [], $baseParams = [])
 {
     $schema = $model->getDb()->getSchema();
     $userPk = array_map([$schema, 'quoteSimpleColumnName'], $user::primaryKey());
     $result = [];
     if (count($userPk) > 1) {
         throw new InvalidCallException('Composite primary key in User model is not supported.');
     } else {
         $userPk = reset($userPk);
     }
     $mainQuery = $model->find();
     if (empty($mainQuery->from)) {
         $mainQuery->from = [$model->tableName() . ' t'];
     }
     $mainQuery->distinct = true;
     foreach ($relations as $relationName) {
         if (($pos = strpos($relationName, '.')) === false) {
             $relation = $model->getRelation($relationName);
             if (!$relation->multiple) {
                 $query = $mainQuery;
             } else {
                 $query = $model->find();
                 if (empty($query->from)) {
                     $query->from = [$model->tableName() . ' t'];
                 }
             }
             $query->innerJoinWith([$relationName => function ($query) use($relation, $relationName) {
                 /** @var ActiveRecord $modelClass */
                 $modelClass = $relation->modelClass;
                 return $query->from([$modelClass::tableName() . ' ' . $relationName]);
             }]);
             $column = $schema->quoteSimpleTableName($relationName) . '.' . $userPk;
             $query->orWhere($column . ' IS NOT NULL AND ' . $column . ' = :current_user_id');
             $query->addParams([':current_user_id' => $user->getId()]);
             if ($relation->multiple) {
                 $query->andWhere($baseConditions, $baseParams);
                 $result[] = $query;
             }
         } else {
             $userRelationName = substr($relationName, $pos + 1);
             $relationName = substr($relationName, 0, $pos);
             $relation = $model->getRelation($relationName);
             /** @var ActiveRecord $relationModel */
             $relationModel = new $relation->modelClass();
             $userRelation = $relationModel->getRelation($userRelationName);
             $userQuery = $relationModel->find();
             if (empty($userQuery->from)) {
                 $userQuery->from = [$relationModel->tableName() . ' t'];
             }
             $userQuery->distinct();
             $userQuery->select($this->quoteColumn('t', $relationModel::primaryKey(), $schema));
             //$userQuery->innerJoinWith($userRelationName);
             $userQuery->innerJoinWith([$userRelationName => function ($query) use($userRelation, $userRelationName) {
                 /** @var ActiveRecord $modelClass */
                 $modelClass = $userRelation->modelClass;
                 return $query->from([$modelClass::tableName() . ' ' . $userRelationName]);
             }]);
             $userQuery->andWhere($schema->quoteSimpleTableName($userRelationName) . '.' . $userPk . ' = :current_user_id');
             $command = $userQuery->createCommand($model->getDb());
             $query = $model->find();
             if (empty($query->from)) {
                 $query->from = [$model->tableName() . ' t'];
             }
             $query->distinct();
             //$query->innerJoinWith($relationName);
             $query->innerJoinWith([$relationName => function ($query) use($relation, $relationName) {
                 /** @var ActiveRecord $modelClass */
                 $modelClass = $relation->modelClass;
                 return $query->from([$modelClass::tableName() . ' ' . $relationName]);
             }]);
             $fk = $this->quoteColumn($relationName, $relationModel::primaryKey(), $schema);
             $query->orWhere('COALESCE(' . (is_array($relationModel::primaryKey()) ? 'ROW(' . $fk . ')' : $fk) . ' IN (' . $command->getSql() . '), false)');
             $query->addParams([':current_user_id' => $user->getId()]);
             $query->andWhere($baseConditions, $baseParams);
             $result[] = $query;
         }
     }
     $mainQuery->andWhere($baseConditions, $baseParams);
     $result[] = $mainQuery;
     return $result;
 }