The many to many relation.

@param $model : the model wanted to be related to the
current model
@param $local : if not null would be the local column
of the relation
@param $remote : if not null would be the $remote column
of the relation
766224
public function belongsToMany($model, $intermediate = null, $local = null, $remote = null)
     {
         return (new ManyToMany())->ini($model, $this, $intermediate, $local, $remote);
     }
983121
public static function relation($local, $foreign)
    {
        $relations = Config::table($local)->relations();
        if (isset($relations->{$foreign}))
        {
            return true;
        }

        throw new dbException('Relation "' . $local . '" to "' . $foreign . '" doesn\'t exist');
    }
1126182
private function deleteManyToManyRelationIntermediateRecords(
        \Tx_Oelib_Model $model
    ) {
        foreach (array_keys($this->relations) as $key) {
            if ($this->isManyToManyRelationConfigured($key)) {
                $relationConfiguration =
                    $this->getRelationConfigurationFromTca($key);
                $mnTable = $relationConfiguration['MM'];

                if (isset($relationConfiguration['MM_opposite_field'])) {
                    $where = 'uid_foreign=' . $model->getUid();
                } else {
                    $where = 'uid_local=' . $model->getUid();
                }
                \Tx_Oelib_Db::delete($mnTable, $where);
            }
        }
    }
1140326
public function initRelation($model, $field = null)
    {
        if ($field == null) {
            $field = post(self::PARAM_FIELD);
        }

        $this->config = $this->originalConfig;
        $this->model = $model;
        $this->field = $field;

        if ($field == null) {
            return;
        }

        if (!$this->model) {
            throw new ApplicationException(Lang::get(
                'backend::lang.relation.missing_model',
                ['class'=>get_class($this->controller)]
            ));
        }

        if (!$this->model instanceof Model) {
            throw new ApplicationException(Lang::get(
                'backend::lang.model.invalid_class',
                ['model'=>get_class($this->model), 'class'=>get_class($this->controller)]
            ));
        }

        if (!$this->getConfig($field)) {
            throw new ApplicationException(Lang::get('backend::lang.relation.missing_definition', compact('field')));
        }

        if ($extraConfig = post(self::PARAM_EXTRA_CONFIG)) {
            $this->applyExtraConfig($extraConfig);
        }

        $this->alias = camel_case('relation ' . $field);
        $this->config = $this->makeConfig($this->getConfig($field), $this->requiredRelationProperties);
        $this->controller->relationExtendConfig($this->config, $this->field, $this->model);

        /*
         * Relationship details
         */
        $this->relationName = $field;
        $this->relationType = $this->model->getRelationType($field);
        $this->relationObject = $this->model->{$field}();
        $this->relationModel = $this->relationObject->getRelated();

        $this->manageId = post('manage_id');
        $this->foreignId = post('foreign_id');
        $this->readOnly = $this->getConfig('readOnly');
        $this->deferredBinding = $this->getConfig('deferredBinding') || !$this->model->exists;
        $this->viewMode = $this->evalViewMode();
        $this->manageMode = $this->evalManageMode();
        $this->manageTitle = $this->evalManageTitle();
        $this->toolbarButtons = $this->evalToolbarButtons();

        /*
         * Toolbar widget
         */
        if ($this->toolbarWidget = $this->makeToolbarWidget()) {
            $this->toolbarWidget->bindToController();
        }

        /*
         * Search widget
         */
        if ($this->searchWidget = $this->makeSearchWidget()) {
            $this->searchWidget->bindToController();
        }

        /*
         * View widget
         */
        if ($this->viewWidget = $this->makeViewWidget()) {
            $this->controller->relationExtendViewWidget($this->viewWidget, $this->field, $this->model);
            $this->viewWidget->bindToController();
        }

        /*
         * Manage widget
         */
        if ($this->manageWidget = $this->makeManageWidget()) {
            $this->controller->relationExtendManageWidget($this->manageWidget, $this->field, $this->model);
            $this->manageWidget->bindToController();
        }

        /*
         * Pivot widget
         */
        if ($this->manageMode == 'pivot' && $this->pivotWidget = $this->makePivotWidget()) {
            $this->controller->relationExtendPivotWidget($this->pivotWidget, $this->field, $this->model);
            $this->pivotWidget->bindToController();
        }
    }
780036
public function initBySetting($model, $setting)
    {
        parent::initBySetting($model, $setting);
        if (is_array($setting)) {
            if (isset($setting['remote_class_type'])) {
                $this->remoteModelType = $setting['remote_class_type'];
            }
        }
    }
1297261
public function hasMany($related, $foreignKey = null, $localKey = null)
    {
        $instance = $this->newRelatedInstance($related);

        $foreignKey = $foreignKey ?: $this->getForeignKey();

        $localKey = $localKey ?: $this->getKeyName();

        return new HasMany(
            $instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey
        );
    }
1059585
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
        );
    }
32850
public static function hydrateRecursive(Model $model, array $items, Relation $parentRelation = null)
    {
        $instance = $model;

        $items = array_map(function ($item) use ($instance, $parentRelation) {
            // Convert all null relations into empty arrays
            $item = $item ?: [];
            
            return static::newFromBuilderRecursive($instance, $item, $parentRelation);
        }, $items);

        return $instance->newCollection($items);
    }
414188
public static function getRelationWidgetOptions($model, $relation, $activeRelation, $multiple = false)
    {
        /** @var \netis\crud\db\ActiveRecord $relModel */
        $relModel = new $activeRelation->modelClass;
        list($createRoute, $searchRoute, $indexRoute) = FormBuilder::getRelationRoutes(
            $model,
            $relModel,
            $activeRelation
        );

        if ($indexRoute === null) {
            return self::getRelationWidgetStaticOptions($model, $relation, $activeRelation, $multiple);
        }

        $isMany = $activeRelation->multiple;
        $foreignKeys = array_values($activeRelation->link);
        $foreignKey = reset($foreignKeys);
        $dbColumns = $model->getTableSchema()->columns;
        $primaryKey = $relModel::primaryKey();

        if (($labelAttributes = $relModel->getBehavior('labels')->attributes) !== null) {
            $fields = array_merge($primaryKey, $labelAttributes);
        } else {
            $fields = $primaryKey;
        }

        $value = self::getRelationValue($model, $relation, $activeRelation);
        $allowClear = $multiple || $isMany ? true : !$model->isAttributeRequired($foreignKey)
            && (!isset($dbColumns[$foreignKey]) || $dbColumns[$foreignKey]->allowNull);

        $jsPrimaryKey = json_encode($primaryKey);
        $jsSeparator = \netis\crud\crud\Action::COMPOSITE_KEY_SEPARATOR;
        $jsId = <<<JavaScript
function(object){
    var keys = $jsPrimaryKey, values = [];
    for (var i = 0; i < keys.length; i++) {
         values.push(object[keys[i]]);
    }
    return netis.implodeEscaped('$jsSeparator', values);
}
JavaScript;

        // check if only one option is available and if yes - set it as selected value
        if (!$allowClear && trim($value) === '') {
            $checkedRelations = $relModel->getCheckedRelations(Yii::$app->user->id, $activeRelation->modelClass . '.read');
            $relQuery = $relModel::find()
                ->select($primaryKey)
                ->from($relModel::tableName() . ' t')
                ->authorized($relModel, $checkedRelations, Yii::$app->user->getIdentity())
                ->asArray();
            if ($relQuery->count() === 1) {
                $value = $relQuery->one();
                $value = Action::implodeEscaped(Action::KEYS_SEPARATOR, $value);
            }
        }

        $label = null;
        if ($model instanceof \netis\crud\db\ActiveRecord) {
            $label = $model->getRelationLabel($activeRelation, Html::getAttributeName($relation));
        }
        $ajaxResults = new JsExpression('s2helper.results');
        $clientEvents = null;
        if ($indexRoute !== null && ($searchRoute !== null || $createRoute !== null)) {
            list ($ajaxResults, $clientEvents) = self::getRelationAjaxOptions(
                $searchRoute,
                $createRoute,
                $jsPrimaryKey,
                $label,
                $relation
            );
        }

        //we get prefix from $relation because it could be in format [3]relation and we need to have [3]foreign_key here
        $relationName = Html::getAttributeName($relation);
        $prefixedFk = str_replace($relation, $foreignKey, $relationName);
        return [
            'class' => 'maddoger\widgets\Select2',
            'model' => $model,
            'attribute' => $isMany ? $relation : $prefixedFk,
            'clientOptions' => array_merge(
                [
                    'formatResult' => new JsExpression('s2helper.formatResult'),
                    'formatSelection' => new JsExpression('s2helper.formatSelection'),
                    'id' => new JsExpression($jsId),
                    'width' => '100%',
                    'allowClear' => $allowClear,
                    'closeOnSelect' => true,
                    'initSelection' => new JsExpression($multiple ? 's2helper.initMulti' : 's2helper.initSingle'),
                    'ajax' => [
                        'url' => Url::toRoute(array_merge($indexRoute, [
                            '_format' => 'json',
                            'fields' => implode(',', $fields),
                        ])),
                        'dataFormat' => 'json',
                        'quietMillis' => 300,
                        'data' => new JsExpression('s2helper.data'),
                        'results' => $ajaxResults,
                    ],
                ],
                $multiple ? ['multiple' => true] : []
            ),
            'clientEvents' => $clientEvents,
            'options' => [
                'class' => 'select2',
                'value' => $value,
                'placeholder' => self::getPrompt(),
                //for now handle relations with single column primary keys
                'data-relation-pk' => count($primaryKey) === 1 ? reset($primaryKey) : null,
            ],
        ];
    }
263303
public static function belongsTo($model, $options=null)
    {
        $cls = get_called_class();
        if (!array_key_exists($cls, self::$_belongs_to)) {
            self::$_belongs_to[$cls] = array();
        }
        self::$_belongs_to[$cls][$model] = $options ? $options : false;
    }
403650
private function deleteOneToManyRelations(\Tx_Oelib_Model $model)
    {
        $data = $model->getData();

        foreach ($this->relations as $key => $mapperName) {
            if ($this->isOneToManyRelationConfigured($key)) {
                $relatedModels = $data[$key];
                if (!is_object($relatedModels)) {
                    continue;
                }

                $mapper = \Tx_Oelib_MapperRegistry::get($mapperName);
                /** @var \Tx_Oelib_Model $relatedModel */
                foreach ($relatedModels as $relatedModel) {
                    $mapper->delete($relatedModel);
                }
            }
        }
    }
681889
protected function loadRelatedModels(Model $model) {
        // get the relations to load
        $relations = $this->exposedRelationsFromRequest($model);

        foreach ($relations as $relation) {
            // if this relation is loaded via a method, then call said method
            if (in_array($relation, $model->relationsFromMethod())) {
                $model->$relation = $model->$relation();
                continue;
            }

            $model->load($relation);
        }
    }
283040
public function initBySetting($model, $setting)
    {
        parent::initBySetting($model, $setting);
        if (is_array($setting)) {
            if (!isset($setting['table'])) {
                throw new \Stack\Exception\MissingSetting('"table" missing for the ManyToMany relation.');
            }
            if (!isset($setting['remote_attribute_name'])) {
                throw new \Stack\Exception\MissingSetting('"remote_attribute_name" missing for the ManyToMany relation.');
            }
            $this->table = $setting['table'];
            $this->remoteAttributeName = $setting['remote_attribute_name'];
        }
    }
335655
protected function isOneToMany($related, $model, $local, $remote)
    {
        return $this->getType(get_class($model), $related, $local, $remote) == OneToManyRelation;
    }
1174096
protected function remoteColumn($model, $column)
    {
        $table = $model::$table;
        //
        return !is_null($column) ? $column : $this->idKey($table);
    }
328300
protected function saveBelongsToRelations(Model $model)
    {
        foreach ($model->getRelations() as $name => $relation) {
            if ($model->{$name}() instanceof BelongsTo && ! is_null($relation)) {
                $relation->save();
                $model->{$name}()->associate($relation);
            }
        }
    }
1126517
public function populateJoinedRelations($model, array $row)
    {
        foreach ($row as $key => $value) {
            if (empty($this->join) || !is_array($value) || $model->hasAttribute($key)) {
                continue;
            }
            foreach ($this->join as $join) {
                $keys = array_keys($join);
                $name = array_shift($keys);
                $closure = array_shift($join);

                if (is_int($name)) {
                    $name = $closure;
                    $closure = null;
                }
                if ($name !== $key) {
                    continue;
                }
                if ($model->isRelationPopulated($name)) {
                    continue 2;
                }
                $records = [];
                $relation = $model->getRelation($name);
                $relationClass = $relation->modelClass;
                if ($closure !== null) {
                    call_user_func($closure, $relation);
                }
                $relation->prepare();

                if ($relation->multiple) {
                    foreach ($value as $item) {
                        $relatedModel = $relationClass::instantiate($item);
                        $relatedModelClass = get_class($relatedModel);
                        $relatedModelClass::populateRecord($relatedModel, $item);
                        $relation->populateJoinedRelations($relatedModel, $item);
                        $relation->addInverseRelation($relatedModel, $model);
                        $relatedModel->trigger(BaseActiveRecord::EVENT_AFTER_FIND);
                        if ($relation->indexBy !== null) {
                            $index = is_string($relation->indexBy)
                                ? $relatedModel[$relation->indexBy]
                                : call_user_func($relation->indexBy, $relatedModel);
                            $records[$index] = $relatedModel;
                        } else {
                            $records[] = $relatedModel;
                        }
                    }
                } else {
                    $relatedModel = $relationClass::instantiate($value);
                    $relatedModelClass = get_class($relatedModel);
                    $relatedModelClass::populateRecord($relatedModel, $value);
                    $relation->populateJoinedRelations($relatedModel, $value);
                    $relation->addInverseRelation($relatedModel, $model);
                    $relatedModel->trigger(BaseActiveRecord::EVENT_AFTER_FIND);
                    $records = $relatedModel;
                }

                $model->populateRelation($name, $records);
            }
        }
    }