'getSlug', ActiveRecord::EVENT_AFTER_UPDATE => 'getSlug', ActiveRecord::EVENT_AFTER_DELETE => 'deleteSlug', ]; } /** * @inheritdoc */ public function attach($owner) { parent::attach($owner); if (!$owner instanceof ActiveRecord) { throw new yii\base\InvalidConfigException( \Yii::t('core', 'SlugBeahvior can only be attached to the instance of ActiveRecord') ); } } /** * Generate slug * * @param yii\base\Event $event * * @return void */ public function getSlug($event) { /** * @var string $attribute_value */ $attribute_value = $this->owner->getAttribute($this->inAttribute); if (!empty($attribute_value)) { /** * @var string $alias_value */ $alias_value = $this->aliasValue; $this->currentAlias = $this->findCurrentAlias(); if (empty($alias_value)) { $result = $this->generateSlug($attribute_value); } else { $result = $this->generateSlug($alias_value); } $suffix = 2; $new_result = $result; while (!empty(Alias::find()->where(['value' => $new_result])->one())) { $new_result = $result . '-' . $suffix; $suffix++; } $this->saveSlug($new_result); } } /** * Delete slug * * @param yii\base\Event $event * * @return void */ public function deleteSlug($event) { if (!empty($current = $this->findCurrentAlias())) { $current->delete(); } } /** * Get current Alias. Db query will be made first time or if $force flag is set. * * @param bool $force * * @return \artbox\core\models\Alias|null */ public function findCurrentAlias($force = false) { if ($force || !$this->executed) { /** * @var int $alias_id */ $alias_id = $this->owner->getAttribute($this->outAttribute); $currentAlias = null; if (!empty($alias_id)) { /** * @var Alias|null $currentAlias */ $currentAlias = Alias::find() ->where( [ 'id' => $alias_id, ] ) ->one(); } $this->executed = true; return $currentAlias; } else { return $this->currentAlias; } } /** * Generate unique slug. * * @param string $slug * * @return string */ private function generateSlug($slug) { $slug = $this->slugify($slug); $languageId = null; if ($this->owner->hasAttribute('language_id')) { $languageId = $this->owner->getAttribute('language_id'); } return SlugHelper::unify($slug, $this->currentAlias, $languageId); } /** * Generate slug from string according to $translit flag. * * @param string $slug * * @return string */ private function slugify($slug) { if ($this->translit) { return SlugHelper::slugify($slug); } else { return $this->slug($slug, '-', true); } } /** * Replace non-alphabetic symbols with $replcement symbol. * * @param string $string * @param string $replacement * @param bool $lowercase * * @return string */ private function slug($string, $replacement = '-', $lowercase = true) { $string = preg_replace('/[^\p{L}\p{Nd}]+/u', $replacement, $string); $string = trim($string, $replacement); return $lowercase ? strtolower($string) : $string; } /** * Check if $slug differs from current Alias value * * @param $slug * * @return bool */ private function hasChanged($slug) { if (!empty($this->currentAlias) && $this->currentAlias->value === $slug) { return false; } else { return true; } } /** * If $slug differs from Db Alias value, updates current if exists or create new Alias row. * * @param $slug * * @throws \yii\base\ErrorException */ protected function saveSlug($slug) { if (!$this->hasChanged($slug)) { return; } else { $this->populateFields(); $route = $this->buildRoute(); $owner = $this->owner; if (empty($alias = $this->findCurrentAlias())) { $alias = \Yii::createObject( Alias::className(), [ [ 'value' => $slug, 'route' => $route, 'entity' => $owner::className(), 'title' => ( !empty($title) ) ? $title : $owner->getAttribute($this->inAttribute), 'h1' => ( !empty($h1) ) ? $h1 : $owner->getAttribute($this->inAttribute), 'fields' => Json::encode($this->fields), 'language_id' => Language::getCurrent()->id, ], ] ); } else { $alias->value = $slug; $alias->route = $route; $alias->entity = $owner::className(); $alias->language_id = Language::getCurrent()->id; } if ($alias->save()) { $owner->{$this->outAttribute} = $alias->id; $this->detach(); $owner->save(false, [ $this->outAttribute ]); } else { $alias = Alias::find() ->where( [ 'route' => $alias->route, 'language_id' => $alias->language_id, ] ) ->one(); if ($alias) { $owner->{$this->outAttribute} = $alias->id; $this->detach(); $owner->save(false, [ $this->outAttribute ]); } else { throw new ErrorException( \Yii::t( 'core', 'Alias cannot be saved: ' . Json::encode($alias) . '. Errors: ' . Json::encode( $alias->getErrors() ) ) ); } } } } protected function generateData(string $attribute, int $lang) { if (!isset($this->meta[ $lang ][ $attribute ])) { return null; } $value = $this->meta[ $lang ][ $attribute ]; foreach ($this->attributes as $search => $replace) { $value = str_replace($search, $replace, $value); } return $value; } protected function populateFields() { $fields = array_keys($this->owner->attributes); foreach ($fields as $field) { $chain = $this->owner; if (!empty($chain->$field)) { $chain = $chain->$field; } else { $chain = ''; } $this->attributes[ '{{' . $field . '}}' ] = $chain; } } /** * Get $aliasValue. Try to get from cached value and tries to get from Db if empty. * * @return string */ public function getAliasValue() { if (empty($this->aliasValue) && !$this->executed) { $currentAlias = $this->findCurrentAlias(); if (!empty($currentAlias)) { $this->aliasValue = $currentAlias->value; return $currentAlias->value; } } return $this->aliasValue; } /** * Set $aliasValue * * @param $value */ public function setAliasValue($value) { $this->aliasValue = $value; } /** * Build Json route from $action and $params fields. * * @return null|string */ protected function buildRoute() { $owner = $this->owner; $route = []; $action = $this->action; if (empty($action)) { return null; } else { $route[ 0 ] = $action; } $params = $this->params; if (!empty($params) && is_array($params)) { foreach ($params as $param => $attribute) { $attributeValue = $owner->getAttribute($attribute); if (!empty($attributeValue)) { $route[ $param ] = $attributeValue; } } } return Json::encode($route); } }