You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

410 lines
14 KiB

<?php
/**
* Created by Error202
* Date: 24.08.2018
*/
namespace core\behaviors;
use yii\base\Behavior;
use yii\base\InvalidConfigException;
use yii\db\ActiveRecord;
use yii\db\ActiveQuery;
class LanguageBehavior extends Behavior
{
/**
* Attributes for translate, ex.: ['name', 'content']
* @var array
*/
public $attributes;
/**
* Class name for language active record entity
* @var string
*/
public $virtualClassName = 'VirtualTranslate';
/**
* Field name for language in translate table
* @var string
*/
public $languageField = 'language';
/**
* Field name for tables relative, ex.: 'post_id'
* @var array|string
*/
public $relativeField;
/**
* Available languages, ex.: ['en', 'ru']
* @var array
*/
public $translatedLanguages;
/**
* Default language, ex.: 'en'
* @var string
*/
public $defaultLanguage;
/**
* Translate table name, ex.: 'post_lng'
* @var string
*/
public $tableName;
/**
* Abridge the language ID.
* @var boolean whether to abridge the language ID.
*/
public $abridge = true;
/**
* Delete relative if foreign key not set on delete: cascade
* @var bool
*/
public $forceDelete = false;
private $_ownerClassName;
private $_ownerClassShortName;
private $_ownerPrimaryKey;
private $_languageAttributes = [];
/**
* Control events firing
* @return array
*/
public function events()
{
return [
ActiveRecord::EVENT_AFTER_FIND => 'afterFind',
ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate',
ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert',
ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
];
}
public function attach($owner)
{
/** @var ActiveRecord $owner */
parent::attach($owner);
if (empty($this->translatedLanguages) || !is_array($this->translatedLanguages)) {
throw new InvalidConfigException('Please specify array of available languages for the ' . get_class($this) . ' in the ' . get_class($this->owner) . ' or in the application parameters',
101);
}
if (array_values($this->translatedLanguages) !== $this->translatedLanguages) { //associative array
$this->translatedLanguages = array_keys($this->translatedLanguages);
}
if (!$this->defaultLanguage) {
throw new InvalidConfigException('Please specify default language for the ' . get_class($this));
}
if (empty($this->attributes) || !is_array($this->attributes)) {
throw new InvalidConfigException('Please specify translated attributes for the ' . get_class($this) . ' in the '
. get_class($this->owner), 103);
}
$this->_ownerClassName = get_class($this->owner);
$this->_ownerClassShortName = $this->getShortClassName($this->_ownerClassName);
/** @var ActiveRecord $className */
$className = $this->_ownerClassName;
$this->_ownerPrimaryKey = $className::primaryKey()[0];
if (!isset($this->relativeField)) {
throw new InvalidConfigException('Please specify relativeField for the ' . get_class($this) . ' in the '
. get_class($this->owner), 105);
}
//$rules = $owner->rules();
//$validators = $owner->getValidators();
/*foreach ($rules as $rule) {
if (in_array($rule[1], $this->excludedValidators))
continue;
$rule_attributes = is_array($rule[0]) ? $rule[0] : [$rule[0]];
$attributes = array_intersect($this->attributes, $rule_attributes);
if (empty($attributes))
continue;
$rule_attributes = [];
foreach ($attributes as $key => $attribute) {
foreach ($this->languages as $language)
if ($language != $this->defaultLanguage)
$rule_attributes[] = $this->getAttributeName($attribute, $language);
}
if (isset($rule['skipOnEmpty']) && !$rule['skipOnEmpty'])
$rule['skipOnEmpty'] = !$this->requireTranslations;
$params = array_slice($rule, 2);
if ($rule[1] !== 'required' || $this->requireTranslations) {
$validators[] = Validator::createValidator($rule[1], $owner, $rule_attributes, $params);
} elseif ($rule[1] === 'required') {
$validators[] = Validator::createValidator('safe', $owner, $rule_attributes, $params);
}
}*/
$this->createLanguageClass();
$translation = new $this->virtualClassName;
foreach ($this->translatedLanguages as $language) {
foreach ($this->attributes as $attribute) {
$attributeName = $attribute;
$this->setLanguageAttribute($attribute . '_' . $language, $translation->{$attributeName});
//$this->owner->__set($attribute . '_' . $language, $translation->{$attributeName});
//$this->owner->{$attribute . '_' . $language} = $translation->{$attributeName};
//$this->owner->createProperty($attribute . '_' . $language, $translation->{$attributeName});
if ($language == $this->defaultLanguage) {
$this->setLanguageAttribute($attribute, $translation->{$attributeName});
//$this->owner->__set($attribute, $translation->{$attributeName});
}
}
}
}
/**
* Insert event
*/
public function afterInsert()
{
$this->saveTranslations();
}
/**
* Update event
*/
public function afterUpdate()
{
/** @var ActiveRecord $owner */
$owner = $this->owner;
//if ($owner->isRelationPopulated('translations')) {
if ($translationRecords = $owner->translations) {
$translations = $this->indexByLanguage($owner->getRelatedRecords()['translations']);
//$translations = $this->indexByLanguage($translationRecords);
$this->saveTranslations($translations);
}
}
/**
* Find event
*/
public function afterFind()
{
/** @var ActiveRecord $owner */
$owner = $this->owner;
if ($owner->isRelationPopulated('translations') && $related = $owner->getRelatedRecords()['translations']) {
$translations = $this->indexByLanguage($related);
foreach ($this->translatedLanguages as $language) {
foreach ($this->attributes as $attribute) {
foreach ($translations as $translation) {
if ($translation->{$this->languageField} == $language) {
$attributeName = $attribute;
$this->setLanguageAttribute($attribute . '_' . $language, $translation->{$attributeName});
if ($language == $this->defaultLanguage) {
$this->setLanguageAttribute($attribute, $translation->{$attributeName});
}
}
}
}
}
} else {
if (!$owner->isRelationPopulated('translation')) {
$owner->translation;
}
$translation = $owner->getRelatedRecords()['translation'];
if ($translation) {
foreach ($this->attributes as $attribute) {
$attribute_name = $attribute;
$owner->setLanguageAttribute($attribute, $translation->$attribute_name);
}
}
}
foreach ($this->attributes as $attribute) {
if ($owner->hasAttribute($attribute) && $this->getLangAttribute($attribute)) {
$owner->setAttribute($attribute, $this->getLangAttribute($attribute));
}
}
}
public function afterDelete()
{
if ($this->forceDelete) {
/** @var ActiveRecord $owner */
$owner = $this->owner;
$owner->unlinkAll('translations', true);
}
}
public function createLanguageClass()
{
if (!class_exists($this->virtualClassName, false)) {
eval('
use yii\db\ActiveRecord;
class ' . $this->virtualClassName . ' extends ActiveRecord
{
public static function tableName()
{
return \'' . $this->tableName . '\';
}
}');
}
}
private function saveTranslations($translations = [])
{
/** @var ActiveRecord $owner */
$owner = $this->owner;
if (!isset($owner->_form) || !$owner->_form) {
return;
}
foreach ($this->translatedLanguages as $language) {
$isDefaultLanguage = $language == $this->defaultLanguage;
if (!isset($translations[$language])) {
/** @var ActiveRecord $translation */
$translation = new $this->virtualClassName;
$translation->{$this->languageField} = $language;
if (is_array($this->relativeField)) {
foreach ($this->relativeField as $field) {
$translation->{$field} = $owner->{$field};
}
} else {
$translation->{$this->relativeField} = $owner->getPrimaryKey();
}
} else {
$translation = $translations[$language];
}
$save = false;
foreach ($this->attributes as $attribute) {
//$value = $isDefaultLanguage ? $owner->$attribute : $owner->{$attribute . '_' . $language};
$value = $isDefaultLanguage ? $owner->_form->$attribute : $owner->_form->{$attribute . '_' . $language};
if ($value !== null) {
//$field = $isDefaultLanguage ? $attribute : $attribute . '_' . $language;
$field = $attribute;
$translation->$field = $value;
$save = true;
}
}
if ($translation->isNewRecord && !$save) {
continue;
}
$translation->save();
}
}
private function getShortClassName($className)
{
return substr($className, strrpos($className, '\\') + 1);
}
public function setLanguageAttribute($name, $value)
{
$this->_languageAttributes[$name] = $value;
}
protected function indexByLanguage(array $records)
{
$sorted = [];
foreach ($records as $record) {
$sorted[$record->{$this->languageField}] = $record;
}
unset($records);
return $sorted;
}
/**
* Relation to model translations
* @return ActiveQuery
*/
public function getTranslations()
{
/** @var ActiveRecord */
$condition = [];
if (is_array($this->relativeField)) {
foreach ($this->relativeField as $field) {
//$condition[$field] = $this->owner->{$field};
$condition[$field] = $field;
}
return $this->owner->hasMany($this->virtualClassName, $condition);
} else {
return $this->owner->hasMany($this->virtualClassName, [$this->relativeField => $this->_ownerPrimaryKey]);
}
}
public function getTranslation($language = null)
{
//if (basename(\Yii::$app->getBasePath()) === 'backend') {
// $language = $language ?: $this->defaultLanguage;
//}
//else {
$language = $language ?: \Yii::$app->language;
//}
// if translate exists
if (is_array($this->relativeField)) {
$condition = [];
foreach ($this->relativeField as $field) {
$condition[$field] = $this->owner->{$field};
}
$translate = $this->virtualClassName::find()
->andWhere($condition)
->andWhere([$this->languageField => $language])
->one();
} else {
$translate = $this->virtualClassName::find()
->andWhere([$this->relativeField => $this->owner->id])
->andWhere([$this->languageField => $language])
->one();
}
$language = $translate ? $language : $this->defaultLanguage;
if (is_array($this->relativeField)) {
$condition = [];
foreach ($this->relativeField as $field) {
$condition[$field] = $field;
}
return $this->owner->hasOne($this->virtualClassName, $condition)
->where([$this->languageField => $language]);
} else {
return $this->owner->hasOne($this->virtualClassName, [$this->relativeField => $this->_ownerPrimaryKey])
->where([$this->languageField => $language]);
}
}
public function findTranslation($language = null)
{
$language = $language ?: $this->defaultLanguage;
//$class = call_user_func(array($this->virtualClassName, 'getInstance'));
if (is_array($this->relativeField)) {
$condition = [];
foreach ($this->relativeField as $field) {
$condition[$field] = $this->owner{$field};
}
return $this->virtualClassName::find()
->andWhere($condition)
->andWhere([$this->languageField => $language])
->one();
} else {
return $this->virtualClassName::find()
->andWhere([$this->relativeField => $this->owner->id])
->andWhere([$this->languageField => $language])
->one();
}
}
public function hasLangAttribute($name)
{
return array_key_exists($name, $this->_languageAttributes);
}
public function getLangAttribute($name)
{
return $this->hasLangAttribute($name) ? $this->_languageAttributes[$name] : null;
}
}