@param \yii\db\ActiveRecord[]|\DevGroup\DataStructure\traits\PropertiesTrait[] $models
@param PropertyGroup $propertyGroup

@return bool
500246
public static function unbindGroupFromModels(&$models, PropertyGroup $propertyGroup)
    {
        foreach ($models as $model) {
            if (in_array($propertyGroup->id, $model->propertyGroupIds) === false) {
                // maybe it'll be better to throw special exception in such case
                return false;
            }
            $query = (new Query())->select('property_id')->from(PropertyPropertyGroup::tableName())->where(
                [
                    'property_group_id' => $propertyGroup->id,
                ]
            );
            $subQuerySql = (new Query())->select('property_id')->from(
                $model::bindedPropertyGroupsTable() . ' opg'
            )->innerJoin(
                PropertyPropertyGroup::tableName() . ' ppg',
                'opg.property_group_id = ppg.property_group_id'
            )->groupBy('property_id')->having('COUNT(*) = 1')->where(
                [
                    'model_id' => $model->id,
                ]
            )->createCommand()->getRawSql();
            $propertyIdsToDelete = $query->andWhere('property_id IN (' . $subQuerySql . ')')->column();
            $storageHandlers = PropertyStorageHelper::getHandlersForModel(reset($models));
            foreach ($storageHandlers as $handler) {
                $handler->deleteProperties($models, $propertyIdsToDelete);
            }
            $model::getDb()->createCommand()->delete(
                $model::bindedPropertyGroupsTable(),
                [
                    'model_id' => $model->id,
                    'property_group_id' => $propertyGroup->id,
                ]
            )->execute();
            TagDependency::invalidate($model->getTagDependencyCacheComponent(), [$model->objectTag()]);
        }
        return true;
    }
406253
public static function parseByOneToMany($model, $propertyName, $annotation)
    {
        $className = BeanFactory::getObjectClass($model);

        $struct = new OneToMany($className, $propertyName, $annotation);
        $leftField = $struct->getLeftField();
        $rightField = $struct->getRightField();
        $rightModel = $struct->getRightModel();

        foreach($model->$propertyName as $index => $row)
        {
            if(!$row instanceof $rightModel)
            {
                $row = $rightModel::newInstance($row);
                $model->$propertyName[$index] = $row;
            }
            $row[$rightField] = $model->$leftField;
            $row->insert();
        }
    }
527527
public static function getAutoAddedGroupsIds($applicablePropertyModelId)
    {
        return Yii::$app->cache->lazy(
            function () use ($applicablePropertyModelId) {
                return array_map(
                    function ($item) {
                        return intval($item);
                    },
                    PropertyGroup::find()
                        ->select('id')
                        ->where(['applicable_property_model_id' => $applicablePropertyModelId, 'is_auto_added' => 1])
                        ->orderBy(['sort_order' => SORT_ASC])
                        ->column()
                );
            },
            'AutoAddedGroupsIds:' . $applicablePropertyModelId,
            86400 // @todo Add dependencies
        );
    }
20647
public function formProperties(array $group = null)
    {
        $obj   = $this->obj();
        $props = $obj->metadata()->properties();

        // We need to sort form properties by form group property order if a group exists
        if (!empty($group)) {
            $props = array_merge(array_flip($group), $props);
        }

        foreach ($props as $propertyIdent => $propertyMetadata) {
            if (method_exists($obj, 'filterPropertyMetadata')) {
                $propertyMetadata = $obj->filterPropertyMetadata($propertyMetadata, $propertyIdent);
            }

            if (!is_array($propertyMetadata)) {
                throw new UnexpectedValueException(sprintf(
                    'Invalid property data for "%1$s", received %2$s',
                    $propertyIdent,
                    (is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata))
                ));
            }

            $formProperty = $this->getOrCreateFormProperty($propertyIdent, $propertyMetadata);

            if (!$formProperty->hidden()) {
                yield $propertyIdent => $formProperty;
            }
        }
    }
953018
private function parsePropertyAnnotations(array $propertyAnnotations, string $className, string $propertyName, $propertyValue)
    {
        $isRef = false;
        $injectProperty = "";

        // 没有任何注解
        if (empty($propertyAnnotations) || !isset($propertyAnnotations[$propertyName])
            || !$this->isParseProperty($propertyAnnotations[$propertyName])
        ) {
            return [null, false];
        }

        // 属性注解解析
        foreach ($propertyAnnotations[$propertyName] as $propertyAnnotation) {
            $annotationClass = get_class($propertyAnnotation);
            if (!in_array($annotationClass, $this->getPropertyAnnotations())) {
                continue;
            }

            // 解析器
            $annotationParser = $this->getAnnotationParser($propertyAnnotation);
            if ($annotationParser === null) {
                $injectProperty = null;
                $isRef = false;
                continue;
            }
            list($injectProperty, $isRef) = $annotationParser->parser($className, $propertyAnnotation, $propertyName, "", $propertyValue);
        }

        return [$injectProperty, $isRef];
    }
184199
protected function addProperties(array $properties, TraitDescriptor $traitDescriptor): void
    {
        foreach ($properties as $property) {
            $propertyDescriptor = $this->getBuilder()->buildDescriptor($property);
            if ($propertyDescriptor instanceof PropertyDescriptor) {
                $propertyDescriptor->setParent($traitDescriptor);
                $traitDescriptor->getProperties()->set($propertyDescriptor->getName(), $propertyDescriptor);
            }
        }
    }
418493
public static function initByPolymorphicToOne($model, $propertyName, $annotation)
    {
        foreach($annotation as $annotationItem)
        {
            if($model->{$annotationItem->type} == $annotationItem->typeValue)
            {
                $leftField = $annotationItem->modelField;
                $rightField = $annotationItem->field;
                if(class_exists($annotationItem->model))
                {
                    $modelClass = $annotationItem->model;
                }
                else
                {
                    $modelClass = Imi::getClassNamespace($className) . '\\' . $annotationItem->model;
                }
                if(null === $model->$rightField)
                {
                    $leftModel = $modelClass::newInstance();
                }
                else
                {
                    $leftModel = $modelClass::query()->where($leftField, '=', $model->$rightField)->select()->get();
                    if(null === $leftModel)
                    {
                        $leftModel = $modelClass::newInstance();
                    }
                }
                $model->$propertyName = $leftModel;
                break;
            }
        }
    }
1351298
public function hasChangeFor($propertyName)
    {
        foreach ($this->getIterator() as $change) {
            /* @var ScalarChange $change */
            if ($change->isFor($propertyName)) {
                return true;
            }
        }

        return false;
    }
1320386
public function run($propertyGroupId, $id = null)
    {
        /** @var PropertyGroup $propertyGroup */
        $propertyGroup = PropertyGroup::loadModel(
            $propertyGroupId,
            false,
            true,
            86400,
            new NotFoundHttpException("PropertyGroup model with specified id not found")
        );

        /** @var Property $model */
        $model = Property::loadModel(
            $id,
            true,
            true,
            86400,
            new NotFoundHttpException("PropertyGroup model with specified id not found")
        );
        $wizardData = Module::getInstance()->prepareWizardData();
        $wizardData['isNewRecord'] = $model->isNewRecord;
        $wizardData = Json::encode($wizardData);
        $this->controller->getView()->registerJs("window.WizardData = {$wizardData};", View::POS_HEAD);
        if ($model->isNewRecord === false) {
            // populate translations relation as we need to save all
            $model->translations;
        }
        $post = Yii::$app->request->post();
        $canSave = Yii::$app->user->can('dst-property-edit');
        if (false === empty($post) && false === $canSave) {
            throw new ForbiddenHttpException(
                Yii::t('yii', 'You are not allowed to perform this action.')
            );
        }
        if (Yii::$app->request->isPost && $model->load($post)) {
            foreach (Yii::$app->request->post('PropertyTranslation', []) as $language => $data) {
                foreach ($data as $attribute => $translation) {
                    $model->translate($language)->$attribute = $translation;
                }
            }

            $event = new ModelEditAction($model);
            $this->controller->trigger(
                $model->isNewRecord ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE,
                $event
            );

            $params = $model->params;
            $handlerParams = Yii::$app->request->post(Property::PACKED_ADDITIONAL_RULES, []);
            $params[Property::PACKED_ADDITIONAL_RULES] = $handlerParams;
            $model->params = $params;

            if ($event->isValid === true && $model->save() && !Yii::$app->request->isPjax) {
                \yii\caching\TagDependency::invalidate(
                    $propertyGroup->getTagDependencyCacheComponent(),
                    [
                        $propertyGroup::commonTag(),
                        $propertyGroup->objectTag()
                    ]
                );
                if ($id === null) {
                    // That was new record - link it to property group
                    $propertyGroup->link(
                        'properties',
                        $model,
                        [
                            'sort_order_group_properties' => count($propertyGroup->properties),
                        ]
                    );
                }

                $event = new ModelEditAction($model);
                $this->controller->trigger($id === null ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, $event);

                if ($event->isValid === true) {
                    Yii::$app->session->setFlash('success', Module::t('app', 'Property saved.'));

                    return $this->controller->redirect([
                        $this->listGroupPropertiesActionId,
                        'id' => $propertyGroup->id,
                    ]);
                }
            } else {
                Yii::$app->session->setFlash('error', Module::t('app', 'Error occurred while saving property.'));
            }
        }

        return $this->render([
            'model' => $model,
            'listGroupPropertiesActionId' => $this->listGroupPropertiesActionId,
            'listPropertyGroupsActionId' => $this->listPropertyGroupsActionId,
            'propertyGroup' => $propertyGroup,
            'canSave' => $canSave,
        ]);
    }
710112
public static function parse($model, $propertyName, $annotation)
    {
        if(!$model->$propertyName)
        {
            return;
        }
        $className = BeanFactory::getObjectClass($model);
        $autoInsert = AnnotationManager::getPropertyAnnotations($className, $propertyName, AutoInsert::class)[0] ?? null;
        $autoSave = AnnotationManager::getPropertyAnnotations($className, $propertyName, AutoSave::class)[0] ?? null;

        if($autoInsert)
        {
            if(!$autoInsert->status)
            {
                return;
            }
        }
        else if(!$autoSave || !$autoSave->status)
        {
            return;
        }

        if($annotation instanceof \Imi\Model\Annotation\Relation\OneToOne)
        {
            static::parseByOneToOne($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\OneToMany)
        {
            static::parseByOneToMany($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\ManyToMany)
        {
            static::parseByManyToMany($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\PolymorphicOneToOne)
        {
            static::parseByPolymorphicOneToOne($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\PolymorphicOneToMany)
        {
            static::parseByPolymorphicOneToMany($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\PolymorphicManyToMany)
        {
            static::parseByPolymorphicManyToMany($model, $propertyName, $annotation);
        }
    }
1099241
public function storeValues(&$models)
    {
        if (count($models) === 0) {
            return true;
        }

        /** @var \yii\db\ActiveRecord|\DevGroup\DataStructure\traits\PropertiesTrait|\DevGroup\TagDependencyHelper\TagDependencyTrait $firstModel */
        $firstModel = reset($models);

        $db = $firstModel->getDb();

        /** @var \yii\db\Command[] $queries */
        $queries = [];

        $existRows = (new Query())->select('model_id')
            ->from($firstModel->tableInheritanceTable())
            ->where(PropertiesHelper::getInCondition($models))
            ->column($firstModel->getDb());

        foreach ($models as $model) {
            /** @var \yii\db\ActiveRecord|\DevGroup\DataStructure\traits\PropertiesTrait|\DevGroup\DataStructure\traits\PropertiesTrait $model */
            $model->ensurePropertiesAttributes();

            $modelTableInheritancePairs = [];

            foreach ($model->propertiesAttributes as $propertyId => $key) {
                $property = Property::findById($propertyId);
                if ($property->storage_id === $this->storageId) {
                    // check if this property changed
                    if (in_array($propertyId, $model->changedProperties)) {
                        $modelTableInheritancePairs[$key] = $model->$key;
                    }
                }
            }
            if (count($modelTableInheritancePairs) > 0) {
                if (in_array($model->id, $existRows) === true) {
                    $queries[] = $db->createCommand()->update(
                        $firstModel->tableInheritanceTable(),
                        $modelTableInheritancePairs,
                        [
                            'model_id' => $model->id
                        ]
                    );
                } else {
                    $modelTableInheritancePairs['model_id'] = $model->id;
                    $queries[] = $db->createCommand()
                        ->insert(
                            $firstModel->tableInheritanceTable(),
                            $modelTableInheritancePairs
                        );
                }
            }
        }
        if (count($queries) > 0) {
            $db->transaction(function ($db) use ($queries) {
                foreach ($queries as $query) {
                    $query->execute();
                }
            });
        }
        return true;
    }
37824
public static function initByPolymorphicOneToMany($model, $propertyName, $annotation)
    {
        $className = BeanFactory::getObjectClass($model);

        if(class_exists($annotation->model))
        {
            $modelClass = $annotation->model;
        }
        else
        {
            $modelClass = Imi::getClassNamespace($className) . '\\' . $annotation->model;
        }

        $struct = new PolymorphicOneToMany($className, $propertyName, $annotation);
        $leftField = $struct->getLeftField();
        $rightField = $struct->getRightField();

        $model->$propertyName = new ArrayList($modelClass);
        if(null !== $model->$leftField)
        {
            $list = $modelClass::query()->where($annotation->type, '=', $annotation->typeValue)->where($rightField, '=', $model->$leftField)->select()->getArray();
            if(null !== $list)
            {
                $model->$propertyName->append(...$list);
            }
        }
    }
423557
protected function checkLoadProperties($propertiesFile, $propertyType)
    {

        if ($propertiesFile->exists() && $propertiesFile->isDirectory()) {
            $message = $propertyType." is a directory!";
            $this->_haltOnErrorAction(null, $message, Project::MSG_ERR);
            return;
        }

        if ($propertiesFile->exists() && !$propertiesFile->canRead()) {
            $message = "Can not read from the specified ".$propertyType."!";
            $this->_haltOnErrorAction(null, $message, Project::MSG_ERR);
            return;
        }

        $fileParserFactory = new FileParserFactory();
        $fileParser        = $fileParserFactory->createParser($propertiesFile->getFileExtension());
        $properties        = new Properties(null, $fileParser);

        $properties->load($propertiesFile);

        return $properties->getProperties();

    }
74572
private static function saveModelPropertyRow(ActiveRecord $model, Property $propertyModel)
    {
        $modelId = $model->id;
        $propertyId = $propertyModel->id;
        $key = $propertyModel->key;
        $values = (array) $model->$key;
        if (count($values) === 0) {
            return [];
        }
        $valueField = self::dataTypeToEavColumn($propertyModel->data_type);
        $rows = [];
        if (true === $propertyModel->canTranslate()) {
            foreach ($values as $langId => $data) {
                $rows = array_merge($rows, self::prepareRows($data, $modelId, $propertyId, $valueField, $langId));
            }
        } else {
            $rows = array_merge($rows, self::prepareRows($values, $modelId, $propertyId, $valueField));
        }
        return $rows;
    }
1025456
public function shouldSkipProperty(PropertyMetadata $property)
    {
        if (!$property->getGroups()) {
            return true;
        }

        foreach ($property->getGroups() as $group) {
            if (isset($this->groups[$group])) {
                return false;
            }
        }

        return true;
    }
977237
private function shouldSkipField(string $propertyName, array $excludedFields): bool
    {
        if ('id' === $propertyName) {
            return true;
        }

        return \in_array($propertyName, $excludedFields, true);
    }
844763
protected function assertValidProperties(array $properties)
    {
        $domainName = Options::get(
            $properties,
            self::PROPERTY_HOST
        );

        if (empty($domainName)) {
            throw new PropertyMissing('Host (domain name) is required to create site');
        }

        $languageIso9392t = Options::get(
            $properties,
            self::PROPERTY_LANGUAGE_ISO_939_2T
        );

        if (empty($languageIso9392t)) {
            throw new PropertyMissing('LanguageIso9392t code is required to create site');
        }

        $countryIso3 = Options::get(
            $properties,
            self::PROPERTY_COUNTRY_ISO3
        );

        if (empty($countryIso3)) {
            throw new PropertyMissing('CountryIso3 code is required to create site');
        }

        $title = Options::get(
            $properties,
            self::PROPERTY_TITLE
        );

        if (empty($title)) {
            throw new PropertyMissing('Title is required to create site');
        }
    }