Commit 08453431bb1751ff1f12f0e113b24ffda9257406
0 parents
first commit
Showing
15 changed files
with
1046 additions
and
0 deletions
Show diff stats
| 1 | +++ a/behaviors/LanguageBehavior.php | ||
| 1 | +<?php | ||
| 2 | + namespace artweb\artbox\language\behaviors; | ||
| 3 | + | ||
| 4 | + use artweb\artbox\language\models\Language; | ||
| 5 | + use yii\base\Behavior; | ||
| 6 | + use yii\base\InvalidConfigException; | ||
| 7 | + use yii\db\ActiveQuery; | ||
| 8 | + use yii\db\ActiveRecord; | ||
| 9 | + use yii\db\Transaction; | ||
| 10 | + use yii\web\Request; | ||
| 11 | + | ||
| 12 | + /** | ||
| 13 | + * Class LanguageBehavior | ||
| 14 | + * @property ActiveRecord $owner | ||
| 15 | + * @property string $ownerKey | ||
| 16 | + * @property string $langKey | ||
| 17 | + * @property ActiveRecord[] $langs | ||
| 18 | + * @property ActiveRecord $lang | ||
| 19 | + */ | ||
| 20 | + class LanguageBehavior extends Behavior | ||
| 21 | + { | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * @var ActiveRecord $objectLang Empty language model for $owner | ||
| 25 | + */ | ||
| 26 | + public $objectLang; | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * @var ActiveRecord[] $modelLangs | ||
| 30 | + */ | ||
| 31 | + public $modelLangs = []; | ||
| 32 | + | ||
| 33 | + private $ownerKey; | ||
| 34 | + | ||
| 35 | + private $langKey; | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * @var Transaction $transaction | ||
| 39 | + */ | ||
| 40 | + private $transaction; | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * @var bool $transactionStatus | ||
| 44 | + */ | ||
| 45 | + private $transactionStatus = false; | ||
| 46 | + | ||
| 47 | + public function events() | ||
| 48 | + { | ||
| 49 | + return [ | ||
| 50 | + ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave', | ||
| 51 | + ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave', | ||
| 52 | + ActiveRecord::EVENT_AFTER_INSERT => 'afterSave', | ||
| 53 | + ActiveRecord::EVENT_AFTER_UPDATE => 'afterSave', | ||
| 54 | + ]; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + /** | ||
| 58 | + * Get $owner primary key to link language model | ||
| 59 | + * @return string | ||
| 60 | + */ | ||
| 61 | + public function getOwnerKey():string | ||
| 62 | + { | ||
| 63 | + if(!empty( $this->ownerKey )) { | ||
| 64 | + return $this->ownerKey; | ||
| 65 | + } else { | ||
| 66 | + return $this->owner->primaryKey()[ 0 ]; | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + /** | ||
| 71 | + * Set which attribute to use as $owner primary key to link language model | ||
| 72 | + * | ||
| 73 | + * @param string $value | ||
| 74 | + */ | ||
| 75 | + public function setOwnerKey(string $value) | ||
| 76 | + { | ||
| 77 | + $this->ownerKey = $value; | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + /** | ||
| 81 | + * Get language model attribute that is used as foreign key to $owner | ||
| 82 | + * @return string | ||
| 83 | + */ | ||
| 84 | + public function getLangKey():string | ||
| 85 | + { | ||
| 86 | + if(!empty( $this->langKey )) { | ||
| 87 | + return $this->langKey; | ||
| 88 | + } else { | ||
| 89 | + $owner = $this->owner; | ||
| 90 | + return $owner::getTableSchema()->name . '_id'; | ||
| 91 | + } | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + /** | ||
| 95 | + * Set which attribute to use as language model foreign key to $owner | ||
| 96 | + * | ||
| 97 | + * @param $value | ||
| 98 | + */ | ||
| 99 | + public function setLangKey(string $value) | ||
| 100 | + { | ||
| 101 | + $this->langKey = $value; | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + /** | ||
| 105 | + * Additional checks to attach this behavior | ||
| 106 | + * | ||
| 107 | + * @param ActiveRecord $owner | ||
| 108 | + * | ||
| 109 | + * @throws InvalidConfigException | ||
| 110 | + */ | ||
| 111 | + public function attach($owner) | ||
| 112 | + { | ||
| 113 | + if(empty( $this->objectLang )) { | ||
| 114 | + $this->objectLang = $owner::className() . 'Lang'; | ||
| 115 | + } elseif(!is_string($this->objectLang)) { | ||
| 116 | + throw new InvalidConfigException('Object lang must be fully classified namespaced classname'); | ||
| 117 | + } | ||
| 118 | + try { | ||
| 119 | + $this->objectLang = \Yii::createObject($this->objectLang); | ||
| 120 | + } catch(\ReflectionException $exception) { | ||
| 121 | + throw new InvalidConfigException('Object lang must be fully classified namespaced classname'); | ||
| 122 | + } | ||
| 123 | + if(( !$owner instanceof ActiveRecord ) || ( !$this->objectLang instanceof ActiveRecord )) { | ||
| 124 | + throw new InvalidConfigException('Object lang must be fully classified namespaced classname'); | ||
| 125 | + } | ||
| 126 | + parent::attach($owner); | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + /** | ||
| 130 | + * Get query to get all language models for $owner indexed by language_id | ||
| 131 | + * @return ActiveQuery | ||
| 132 | + */ | ||
| 133 | + public function getLangs() | ||
| 134 | + { | ||
| 135 | + $objectLang = $this->objectLang; | ||
| 136 | + $owner = $this->owner; | ||
| 137 | + return $owner->hasMany($objectLang::className(), [ $this->getLangKey() => $this->getOwnerKey() ]) | ||
| 138 | + ->indexBy('language_id'); | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + /** | ||
| 142 | + * Get query to get language model for $owner for language_id, default to | ||
| 143 | + * Language::getCurrent() | ||
| 144 | + * | ||
| 145 | + * @param int $language_id | ||
| 146 | + * | ||
| 147 | + * @return ActiveQuery | ||
| 148 | + */ | ||
| 149 | + public function getLang(int $language_id = NULL) | ||
| 150 | + { | ||
| 151 | + if(empty( $language_id )) { | ||
| 152 | + $language_id = Language::getCurrent()->id; | ||
| 153 | + } | ||
| 154 | + $objectLang = $this->objectLang; | ||
| 155 | + $table_name = $objectLang::getTableSchema()->name; | ||
| 156 | + $owner = $this->owner; | ||
| 157 | + return $owner->hasOne($objectLang::className(), [ $this->getLangKey() => $this->getOwnerKey() ]) | ||
| 158 | + ->where([ $table_name . '.language_id' => $language_id ]); | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + /** | ||
| 162 | + * Generate language models for $owner for active languages. If $owner not new and language | ||
| 163 | + * models already inserted, models will be filled with them. | ||
| 164 | + * @return void | ||
| 165 | + */ | ||
| 166 | + public function generateLangs() | ||
| 167 | + { | ||
| 168 | + $owner = $this->owner; | ||
| 169 | + $languages = Language::find() | ||
| 170 | + ->where([ 'status' => true ]) | ||
| 171 | + ->orderBy([ 'id' => SORT_ASC ]) | ||
| 172 | + ->asArray() | ||
| 173 | + ->column(); | ||
| 174 | + $objectLang = $this->objectLang; | ||
| 175 | + $owner_key = $this->getOwnerKey(); | ||
| 176 | + $langs = []; | ||
| 177 | + if(!$owner->isNewRecord) { | ||
| 178 | + $langs = $this->getLangs() | ||
| 179 | + ->andFilterWhere([ 'language_id' => $languages ]) | ||
| 180 | + ->orderBy([ 'language_id' => SORT_ASC ]) | ||
| 181 | + ->all(); | ||
| 182 | + } | ||
| 183 | + foreach($languages as $language) { | ||
| 184 | + if(!array_key_exists($language, $langs)) { | ||
| 185 | + $langs[ $language ] = \Yii::createObject([ | ||
| 186 | + 'class' => $objectLang::className(), | ||
| 187 | + 'language_id' => $language, | ||
| 188 | + $this->getLangKey() => ( $owner->isNewRecord ? NULL : $owner->$owner_key ), | ||
| 189 | + ]); | ||
| 190 | + } | ||
| 191 | + } | ||
| 192 | + $this->modelLangs = $langs; | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + /** | ||
| 196 | + * Load language models with post data. | ||
| 197 | + * | ||
| 198 | + * @param Request $request | ||
| 199 | + */ | ||
| 200 | + public function loadLangs(Request $request) | ||
| 201 | + { | ||
| 202 | + foreach($request->post($this->objectLang->formName(), []) as $lang => $value) { | ||
| 203 | + if(!empty( $this->modelLangs[ $lang ] )) { | ||
| 204 | + $this->modelLangs[ $lang ]->attributes = $value; | ||
| 205 | + $this->modelLangs[ $lang ]->language_id = $lang; | ||
| 206 | + } | ||
| 207 | + } | ||
| 208 | + } | ||
| 209 | + | ||
| 210 | + /** | ||
| 211 | + * Link language models with $owner by setting language model language key to owner key of | ||
| 212 | + * owner | ||
| 213 | + * @return bool If $owner is new record then return false else true | ||
| 214 | + */ | ||
| 215 | + public function linkLangs() | ||
| 216 | + { | ||
| 217 | + $owner = $this->owner; | ||
| 218 | + // if($owner->isNewRecord) { | ||
| 219 | + // return false; | ||
| 220 | + // } | ||
| 221 | + $lang_key = $this->getLangKey(); | ||
| 222 | + $owner_key = $this->getOwnerKey(); | ||
| 223 | + $modelLangs = $this->modelLangs; | ||
| 224 | + foreach($modelLangs as $model_lang) { | ||
| 225 | + $model_lang->$lang_key = $owner->$owner_key; | ||
| 226 | + } | ||
| 227 | + return true; | ||
| 228 | + } | ||
| 229 | + | ||
| 230 | + /** | ||
| 231 | + * Try to save all language models to the db. Validation function is run for all models. | ||
| 232 | + * @return bool Whether all models are valid | ||
| 233 | + */ | ||
| 234 | + public function saveLangs() | ||
| 235 | + { | ||
| 236 | + $success = true; | ||
| 237 | + $modelLangs = $this->modelLangs; | ||
| 238 | + foreach($modelLangs as $model_lang) { | ||
| 239 | + if($model_lang->save() === false) { | ||
| 240 | + $success = false; | ||
| 241 | + } | ||
| 242 | + } | ||
| 243 | + return $success; | ||
| 244 | + } | ||
| 245 | + | ||
| 246 | + public function beforeSave($event) | ||
| 247 | + { | ||
| 248 | + /** | ||
| 249 | + * @var ActiveRecord $owner | ||
| 250 | + */ | ||
| 251 | + $owner = $this->owner; | ||
| 252 | + $db = $owner::getDb(); | ||
| 253 | + $this->transaction = $db->beginTransaction(); | ||
| 254 | + if($owner->hasAttribute('remote_id') && empty( $owner->remote_id )) { | ||
| 255 | + $owner->remote_id = strval(microtime(true) * 10000); | ||
| 256 | + } | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + public function afterSave($event) | ||
| 260 | + { | ||
| 261 | + if(!empty( $this->modelLangs )) { | ||
| 262 | + if($this->linkLangs() && $this->saveLangs()) { | ||
| 263 | + $this->transaction->commit(); | ||
| 264 | + $this->transactionStatus = true; | ||
| 265 | + } else { | ||
| 266 | + $this->transaction->rollBack(); | ||
| 267 | + $this->transactionStatus = false; | ||
| 268 | + } | ||
| 269 | + } else { | ||
| 270 | + $this->transaction->commit(); | ||
| 271 | + $this->transactionStatus = true; | ||
| 272 | + } | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + /** | ||
| 276 | + * @return bool | ||
| 277 | + */ | ||
| 278 | + public function getTransactionStatus():bool | ||
| 279 | + { | ||
| 280 | + return $this->transactionStatus; | ||
| 281 | + } | ||
| 282 | + } | ||
| 0 | \ No newline at end of file | 283 | \ No newline at end of file |
| 1 | +++ a/components/LanguageRequest.php | ||
| 1 | +<?php | ||
| 2 | + | ||
| 3 | + namespace artweb\artbox\language\components; | ||
| 4 | + | ||
| 5 | + use artweb\artbox\language\models\Language; | ||
| 6 | + use yii\base\InvalidConfigException; | ||
| 7 | + use yii\web\Request; | ||
| 8 | + | ||
| 9 | + class LanguageRequest extends Request | ||
| 10 | + { | ||
| 11 | + | ||
| 12 | + private $languageUrl; | ||
| 13 | + | ||
| 14 | + public function getLanguageUrl() | ||
| 15 | + { | ||
| 16 | + if($this->languageUrl === NULL) { | ||
| 17 | + $this->languageUrl = $this->getUrl(); | ||
| 18 | + | ||
| 19 | + $url_list = explode('/', $this->languageUrl); | ||
| 20 | + | ||
| 21 | + $language_url = isset( $url_list[ 1 ] ) ? $url_list[ 1 ] : NULL; | ||
| 22 | + Language::setCurrent($language_url); | ||
| 23 | + | ||
| 24 | + if($language_url !== NULL && $language_url === Language::getCurrent()->url && strpos($this->languageUrl, Language::getCurrent()->url) === 1) { | ||
| 25 | + $this->languageUrl = substr($this->languageUrl, strlen(Language::getCurrent()->url) + 1); | ||
| 26 | + } | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + return $this->languageUrl; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + protected function resolvePathInfo() | ||
| 33 | + { | ||
| 34 | + $pathInfo = $this->getLanguageUrl(); | ||
| 35 | + | ||
| 36 | + if(( $pos = strpos($pathInfo, '?') ) !== false) { | ||
| 37 | + $pathInfo = substr($pathInfo, 0, $pos); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + $pathInfo = urldecode($pathInfo); | ||
| 41 | + | ||
| 42 | + if(!preg_match('%^(?: | ||
| 43 | + [\x09\x0A\x0D\x20-\x7E] | ||
| 44 | + | [\xC2-\xDF][\x80-\xBF] | ||
| 45 | + | \xE0[\xA0-\xBF][\x80-\xBF] | ||
| 46 | + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} | ||
| 47 | + | \xED[\x80-\x9F][\x80-\xBF] | ||
| 48 | + | \xF0[\x90-\xBF][\x80-\xBF]{2} | ||
| 49 | + | [\xF1-\xF3][\x80-\xBF]{3} | ||
| 50 | + | \xF4[\x80-\x8F][\x80-\xBF]{2} | ||
| 51 | + )*$%xs', $pathInfo) | ||
| 52 | + ) { | ||
| 53 | + $pathInfo = utf8_encode($pathInfo); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + $scriptUrl = $this->getScriptUrl(); | ||
| 57 | + $baseUrl = $this->getBaseUrl(); | ||
| 58 | + | ||
| 59 | + if(strpos($pathInfo, $scriptUrl) === 0) { | ||
| 60 | + $pathInfo = substr($pathInfo, strlen($scriptUrl)); | ||
| 61 | + } elseif($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) { | ||
| 62 | + $pathInfo = substr($pathInfo, strlen($baseUrl)); | ||
| 63 | + } elseif(isset( $_SERVER[ 'PHP_SELF' ] ) && strpos($_SERVER[ 'PHP_SELF' ], $scriptUrl) === 0) { | ||
| 64 | + $pathInfo = substr($_SERVER[ 'PHP_SELF' ], strlen($scriptUrl)); | ||
| 65 | + } else { | ||
| 66 | + throw new InvalidConfigException('Unable to determine the path info of the current request.'); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + if($pathInfo === '/') { | ||
| 70 | + $pathInfo = substr($pathInfo, 1); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + return (string) $pathInfo; | ||
| 74 | + } | ||
| 75 | + } | ||
| 0 | \ No newline at end of file | 76 | \ No newline at end of file |
| 1 | +++ a/components/LanguageUrlManager.php | ||
| 1 | +<?php | ||
| 2 | + namespace artweb\artbox\language\components; | ||
| 3 | + | ||
| 4 | + use artweb\artbox\language\models\Language; | ||
| 5 | + use yii\web\UrlManager; | ||
| 6 | + | ||
| 7 | + class LanguageUrlManager extends UrlManager | ||
| 8 | + { | ||
| 9 | + | ||
| 10 | + /** | ||
| 11 | + * @inheritdoc | ||
| 12 | + */ | ||
| 13 | + public function createUrl($params) | ||
| 14 | + { | ||
| 15 | + if(isset( $params[ 'language_id' ] )) { | ||
| 16 | + | ||
| 17 | + $language = Language::findOne($params[ 'language_id' ]); | ||
| 18 | + if($language === NULL) { | ||
| 19 | + $language = Language::getDefaultLanguage(); | ||
| 20 | + } | ||
| 21 | + unset( $params[ 'language_id' ] ); | ||
| 22 | + } else { | ||
| 23 | + | ||
| 24 | + $language = Language::getCurrent(); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + $url = parent::createUrl($params); | ||
| 28 | + | ||
| 29 | + if($url == '/') { | ||
| 30 | + return '/' . $language->url; | ||
| 31 | + } else { | ||
| 32 | + return '/' . $language->url . $url; | ||
| 33 | + } | ||
| 34 | + } | ||
| 35 | + } | ||
| 0 | \ No newline at end of file | 36 | \ No newline at end of file |
| 1 | +++ a/composer.json | ||
| 1 | +{ | ||
| 2 | + "name": "artweb/artbox-language", | ||
| 3 | + "description": "Yii2 light-weight CMS", | ||
| 4 | + "license": "BSD-3-Clause", | ||
| 5 | + "require": { | ||
| 6 | + "php": ">=7.0", | ||
| 7 | + "yiisoft/yii2": "*", | ||
| 8 | + "developeruz/yii2-db-rbac": "*" | ||
| 9 | + }, | ||
| 10 | + "autoload": { | ||
| 11 | + "psr-4": { | ||
| 12 | + "artweb\\artbox\\language\\": "" | ||
| 13 | + } | ||
| 14 | + } | ||
| 15 | +} | ||
| 0 | \ No newline at end of file | 16 | \ No newline at end of file |
migrations/m160829_104745_create_table_language.php
0 → 100755
| 1 | +++ a/migrations/m160829_104745_create_table_language.php | ||
| 1 | +<?php | ||
| 2 | + | ||
| 3 | + use yii\db\Migration; | ||
| 4 | + | ||
| 5 | + class m160829_104745_create_table_language extends Migration | ||
| 6 | + { | ||
| 7 | + | ||
| 8 | + public function up() | ||
| 9 | + { | ||
| 10 | + $this->createTable( | ||
| 11 | + '{{%language}}', | ||
| 12 | + [ | ||
| 13 | + 'id' => $this->primaryKey(), | ||
| 14 | + 'url' => $this->string() | ||
| 15 | + ->notNull(), | ||
| 16 | + 'local' => $this->string() | ||
| 17 | + ->notNull(), | ||
| 18 | + 'name' => $this->string() | ||
| 19 | + ->notNull(), | ||
| 20 | + 'default' => $this->boolean() | ||
| 21 | + ->notNull() | ||
| 22 | + ->defaultValue(false), | ||
| 23 | + 'created_at' => $this->integer() | ||
| 24 | + ->notNull(), | ||
| 25 | + 'updated_at' => $this->integer() | ||
| 26 | + ->notNull(), | ||
| 27 | + ] | ||
| 28 | + ); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + public function down() | ||
| 32 | + { | ||
| 33 | + $this->dropTable('{{%language}}'); | ||
| 34 | + } | ||
| 35 | + } |
migrations/m160829_105345_add_default_languages.php
0 → 100755
| 1 | +++ a/migrations/m160829_105345_add_default_languages.php | ||
| 1 | +<?php | ||
| 2 | + | ||
| 3 | + use yii\db\Migration; | ||
| 4 | + | ||
| 5 | + class m160829_105345_add_default_languages extends Migration | ||
| 6 | + { | ||
| 7 | + | ||
| 8 | + public function up() | ||
| 9 | + { | ||
| 10 | + $this->batchInsert( | ||
| 11 | + '{{%language}}', | ||
| 12 | + [ | ||
| 13 | + 'id', | ||
| 14 | + 'url', | ||
| 15 | + 'local', | ||
| 16 | + 'name', | ||
| 17 | + 'default', | ||
| 18 | + 'created_at', | ||
| 19 | + 'updated_at', | ||
| 20 | + ], | ||
| 21 | + [ | ||
| 22 | + [ | ||
| 23 | + 1, | ||
| 24 | + 'en', | ||
| 25 | + 'en-EN', | ||
| 26 | + 'English', | ||
| 27 | + 0, | ||
| 28 | + time(), | ||
| 29 | + time(), | ||
| 30 | + ], | ||
| 31 | + [ | ||
| 32 | + 2, | ||
| 33 | + 'ru', | ||
| 34 | + 'ru-RU', | ||
| 35 | + 'Русский', | ||
| 36 | + 1, | ||
| 37 | + time(), | ||
| 38 | + time(), | ||
| 39 | + ], | ||
| 40 | + ] | ||
| 41 | + ); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public function down() | ||
| 45 | + { | ||
| 46 | + $this->delete( | ||
| 47 | + '{{%language}}', | ||
| 48 | + [ | ||
| 49 | + 'id' => [ | ||
| 50 | + 1, | ||
| 51 | + 2, | ||
| 52 | + ], | ||
| 53 | + ] | ||
| 54 | + ); | ||
| 55 | + } | ||
| 56 | + } |
migrations/m160901_140639_add_ukrainian_language.php
0 → 100755
| 1 | +++ a/migrations/m160901_140639_add_ukrainian_language.php | ||
| 1 | +<?php | ||
| 2 | + | ||
| 3 | + use yii\db\Migration; | ||
| 4 | + | ||
| 5 | + class m160901_140639_add_ukrainian_language extends Migration | ||
| 6 | + { | ||
| 7 | + | ||
| 8 | + public function up() | ||
| 9 | + { | ||
| 10 | + $this->batchInsert( | ||
| 11 | + '{{%language}}', | ||
| 12 | + [ | ||
| 13 | + 'id', | ||
| 14 | + 'url', | ||
| 15 | + 'local', | ||
| 16 | + 'name', | ||
| 17 | + 'default', | ||
| 18 | + 'created_at', | ||
| 19 | + 'updated_at', | ||
| 20 | + ], | ||
| 21 | + [ | ||
| 22 | + [ | ||
| 23 | + 3, | ||
| 24 | + 'ua', | ||
| 25 | + 'ua-UA', | ||
| 26 | + 'Українська', | ||
| 27 | + 0, | ||
| 28 | + time(), | ||
| 29 | + time(), | ||
| 30 | + ], | ||
| 31 | + ] | ||
| 32 | + ); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public function down() | ||
| 36 | + { | ||
| 37 | + $this->delete('{{%language}}', [ 'id' => [ 3 ] ]); | ||
| 38 | + } | ||
| 39 | + } |
| 1 | +++ a/migrations/m160927_124151_add_status_column.php | ||
| 1 | +<?php | ||
| 2 | + | ||
| 3 | + use yii\db\Migration; | ||
| 4 | + | ||
| 5 | + class m160927_124151_add_status_column extends Migration | ||
| 6 | + { | ||
| 7 | + | ||
| 8 | + public function up() | ||
| 9 | + { | ||
| 10 | + $this->addColumn('language', 'status', $this->boolean() | ||
| 11 | + ->notNull() | ||
| 12 | + ->defaultValue(false)); | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + public function down() | ||
| 16 | + { | ||
| 17 | + $this->dropColumn('language', 'status'); | ||
| 18 | + } | ||
| 19 | + } |
| 1 | +++ a/models/Language.php | ||
| 1 | +<?php | ||
| 2 | + | ||
| 3 | + namespace artweb\artbox\language\models; | ||
| 4 | + | ||
| 5 | + use Yii; | ||
| 6 | + use yii\db\ActiveRecord; | ||
| 7 | + | ||
| 8 | + /** | ||
| 9 | + * This is the model class for table "language". | ||
| 10 | + * | ||
| 11 | + * @property integer $id | ||
| 12 | + * @property string $url | ||
| 13 | + * @property string $local | ||
| 14 | + * @property string $name | ||
| 15 | + * @property boolean $default | ||
| 16 | + * @property integer $created_at | ||
| 17 | + * @property integer $updated_at | ||
| 18 | + */ | ||
| 19 | + class Language extends ActiveRecord | ||
| 20 | + { | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * @var null|self | ||
| 24 | + */ | ||
| 25 | + public static $current = null; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * @inheritdoc | ||
| 29 | + */ | ||
| 30 | + public static function tableName() | ||
| 31 | + { | ||
| 32 | + return 'language'; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * @inheritdoc | ||
| 37 | + */ | ||
| 38 | + public function behaviors() | ||
| 39 | + { | ||
| 40 | + return [ | ||
| 41 | + 'timestamp' => [ | ||
| 42 | + 'class' => 'yii\behaviors\TimestampBehavior', | ||
| 43 | + 'attributes' => [ | ||
| 44 | + ActiveRecord::EVENT_BEFORE_INSERT => [ | ||
| 45 | + 'created_at', | ||
| 46 | + 'updated_at', | ||
| 47 | + ], | ||
| 48 | + ActiveRecord::EVENT_BEFORE_UPDATE => [ | ||
| 49 | + 'updated_at', | ||
| 50 | + ], | ||
| 51 | + ], | ||
| 52 | + ], | ||
| 53 | + ]; | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * @inheritdoc | ||
| 58 | + */ | ||
| 59 | + public function rules() | ||
| 60 | + { | ||
| 61 | + return [ | ||
| 62 | + [ | ||
| 63 | + [ | ||
| 64 | + 'url', | ||
| 65 | + 'local', | ||
| 66 | + 'name', | ||
| 67 | + 'created_at', | ||
| 68 | + 'updated_at', | ||
| 69 | + ], | ||
| 70 | + 'required', | ||
| 71 | + ], | ||
| 72 | + [ | ||
| 73 | + [ 'default' ], | ||
| 74 | + 'boolean', | ||
| 75 | + ], | ||
| 76 | + [ | ||
| 77 | + [ | ||
| 78 | + 'created_at', | ||
| 79 | + 'updated_at', | ||
| 80 | + ], | ||
| 81 | + 'integer', | ||
| 82 | + ], | ||
| 83 | + [ | ||
| 84 | + [ | ||
| 85 | + 'url', | ||
| 86 | + 'local', | ||
| 87 | + 'name', | ||
| 88 | + ], | ||
| 89 | + 'string', | ||
| 90 | + 'max' => 255, | ||
| 91 | + ], | ||
| 92 | + ]; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + /** | ||
| 96 | + * @inheritdoc | ||
| 97 | + */ | ||
| 98 | + public function attributeLabels() | ||
| 99 | + { | ||
| 100 | + return [ | ||
| 101 | + 'id' => Yii::t('app', 'Language ID'), | ||
| 102 | + 'url' => Yii::t('app', 'Url'), | ||
| 103 | + 'local' => Yii::t('app', 'Local'), | ||
| 104 | + 'name' => Yii::t('app', 'Name'), | ||
| 105 | + 'default' => Yii::t('app', 'Default'), | ||
| 106 | + 'created_at' => Yii::t('app', 'Date Create'), | ||
| 107 | + 'updated_at' => Yii::t('app', 'Date Update'), | ||
| 108 | + ]; | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + /** | ||
| 112 | + * Get current language | ||
| 113 | + * | ||
| 114 | + * @return null|Language | ||
| 115 | + */ | ||
| 116 | + public static function getCurrent() | ||
| 117 | + { | ||
| 118 | + if (self::$current === null) { | ||
| 119 | + self::$current = self::getDefaultLanguage(); | ||
| 120 | + } | ||
| 121 | + return self::$current; | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + /** | ||
| 125 | + * Set current language by Url param | ||
| 126 | + * | ||
| 127 | + * @param null|string $url Language url param | ||
| 128 | + */ | ||
| 129 | + public static function setCurrent($url = null) | ||
| 130 | + { | ||
| 131 | + $language = self::getLanguageByUrl($url); | ||
| 132 | + self::$current = ( $language === null ) ? self::getDefaultLanguage() : $language; | ||
| 133 | + Yii::$app->language = self::$current->local; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + /** | ||
| 137 | + * Get default language | ||
| 138 | + * | ||
| 139 | + * @return null|Language | ||
| 140 | + */ | ||
| 141 | + public static function getDefaultLanguage() | ||
| 142 | + { | ||
| 143 | + /** | ||
| 144 | + * @var null|Language $language | ||
| 145 | + */ | ||
| 146 | + $language = self::find() | ||
| 147 | + ->where([ 'default' => true ]) | ||
| 148 | + ->one(); | ||
| 149 | + return $language; | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + /** | ||
| 153 | + * Get language by Url param | ||
| 154 | + * | ||
| 155 | + * @param null|string $url Language url param | ||
| 156 | + * | ||
| 157 | + * @return null|Language | ||
| 158 | + */ | ||
| 159 | + public static function getLanguageByUrl($url = null) | ||
| 160 | + { | ||
| 161 | + if ($url === null) { | ||
| 162 | + return null; | ||
| 163 | + } else { | ||
| 164 | + /** | ||
| 165 | + * @var null|Language $language | ||
| 166 | + */ | ||
| 167 | + $language = self::find() | ||
| 168 | + ->where([ 'url' => $url ]) | ||
| 169 | + ->one(); | ||
| 170 | + if ($language === null) { | ||
| 171 | + return null; | ||
| 172 | + } else { | ||
| 173 | + return $language; | ||
| 174 | + } | ||
| 175 | + } | ||
| 176 | + } | ||
| 177 | + } |
| 1 | +++ a/readme.txt | ||
| 1 | +Как включить мультиязычность на сайте: | ||
| 2 | +1. Запускаем миграцию: php yii migrate --migrationPath=common/modules/language/migrations | ||
| 3 | +2. Добавляем в файл конфигурации: | ||
| 4 | +'urlManager' => [ | ||
| 5 | + 'enablePrettyUrl' => true, | ||
| 6 | + 'showScriptName' => false, | ||
| 7 | + 'class'=>'artweb\artbox\language\components\LanguageUrlManager', | ||
| 8 | + 'rules'=>[ | ||
| 9 | + '/' => 'site/index', | ||
| 10 | + '<controller:\w+>/<action:\w+>/*'=>'<controller>/<action>', | ||
| 11 | + ] | ||
| 12 | +], | ||
| 13 | +3. Добавляем в файл конфигурации: | ||
| 14 | +'request' => [ | ||
| 15 | + 'class' => 'artweb\artbox\language\components\LanguageRequest' | ||
| 16 | +], | ||
| 17 | +4. Добавляем в файл конфигурации: | ||
| 18 | +'language'=>'ru-RU', | ||
| 19 | +'i18n' => [ | ||
| 20 | + 'translations' => [ | ||
| 21 | + '*' => [ | ||
| 22 | + 'class' => 'yii\i18n\PhpMessageSource', | ||
| 23 | + 'basePath' => '@frontend/messages', | ||
| 24 | + 'sourceLanguage' => 'en', | ||
| 25 | + 'fileMap' => [ | ||
| 26 | + ], | ||
| 27 | + ], | ||
| 28 | + ], | ||
| 29 | +], | ||
| 30 | +5. Переводы писать в файл frontend\messages\{language}\app.php, где {language} - нужный язык, например ru. | ||
| 31 | +6. Для вывода на странице сообщения с переводом используем функцию: Yii::t('app', {message}, $params = [], $language = null), | ||
| 32 | + где {message} - нужное сообщение, $params - массив параметров, $language - нужный язык (по умолчанию используется текущий язык). | ||
| 33 | +7. В наличие также виджет переключения языка: LanguagePicker::widget() | ||
| 34 | + | ||
| 35 | + | ||
| 36 | +Как использовать мультиязычность для Active Record: | ||
| 37 | +1. Создаем для таблицы {table} таблицу с языками {table_lang}. | ||
| 38 | +2. Создаем для класса {Table} класс с языками {TableLang}. | ||
| 39 | +3. Подключаеи для класса {Table} поведение LanguageBehavior: | ||
| 40 | +public function behaviors() { | ||
| 41 | + return [ | ||
| 42 | + 'language' => [ | ||
| 43 | + 'class' => LanguageBehavior::className(), | ||
| 44 | + 'objectLang' => {TableLang}::className() // optional, default to {TableLang}::className() | ||
| 45 | + 'ownerKey' => {Table}->id //optional, default to {Table}->primaryKey()[0] | ||
| 46 | + 'langKey' => {TableLang}->table_id //optional, default to {Table}->tableName().'_id' | ||
| 47 | + ], | ||
| 48 | + ]; | ||
| 49 | +} | ||
| 50 | +3.1. PHPDoc для {Table}: | ||
| 51 | + * * From language behavior * | ||
| 52 | + * @property {TableLang} $lang | ||
| 53 | + * @property {TableLang}[] $langs | ||
| 54 | + * @property {TableLang} $objectLang | ||
| 55 | + * @property string $ownerKey | ||
| 56 | + * @property string $langKey | ||
| 57 | + * @property {TableLang}[] $modelLangs | ||
| 58 | + * @property bool $transactionStatus | ||
| 59 | + * @method string getOwnerKey() | ||
| 60 | + * @method void setOwnerKey(string $value) | ||
| 61 | + * @method string getLangKey() | ||
| 62 | + * @method void setLangKey(string $value) | ||
| 63 | + * @method ActiveQuery getLangs() | ||
| 64 | + * @method ActiveQuery getLang( integer $language_id ) | ||
| 65 | + * @method {TableLang}[] generateLangs() | ||
| 66 | + * @method void loadLangs(Request $request) | ||
| 67 | + * @method bool linkLangs() | ||
| 68 | + * @method bool saveLangs() | ||
| 69 | + * @method bool getTransactionStatus() | ||
| 70 | + * * End language behavior * | ||
| 71 | +3.2. Убрать language behavior с наследуемых таблиц от {Table} ({TableSearch}...) | ||
| 72 | +4. Доступные полезные методы: | ||
| 73 | + {Table}->getLangs() - получить все текущие {TableLang} для {Table} проиндексированные по language_id | ||
| 74 | + {Table}->getLang($language_id = NULL) - получить {TableLang} для определенного языка (default: текущий язык) для {Table} | ||
| 75 | + {Table}->generateLangs() - получить массив {TableLang} под каждый язык, включая существующие записи, для {Table} | ||
| 76 | + {Table}->loadLangs($request) - заполнить массив {TableLang} данными с POST | ||
| 77 | + {Table}->linkLangs() - связать каждый элемент массива {TableLang} с текущей {Table} | ||
| 78 | + {Table}->saveLangs() - провалидировать и сохранить каждый элемент массива {TableLang} | ||
| 79 | +5. Добавить поля в форму (к примеру через Bootstrap Tabs). | ||
| 80 | + В наличии: | ||
| 81 | + LanguageForm::widget([ | ||
| 82 | + 'modelLangs' => {TableLang}[], | ||
| 83 | + 'formView' => string, | ||
| 84 | + 'form' => ActiveForm, | ||
| 85 | + ]); | ||
| 86 | +6. Обрабатывать данные в контроллере. | ||
| 87 | + 1. После создания/поиска {Table} создаем/находим языковые модели {Table}->generateLangs() | ||
| 88 | + 2. При POST запросе загружаем данные в языковые модели {Table}->loadLangs(Request $request) | ||
| 89 | + 3. После сохранения, если транзанкция успешна, то свойство {Table}->transactionStatus будет true, иначе возникла ошибка в какой то модели. | ||
| 90 | +7. Получать данные на публичной части сайта через {Table}->lang. |
| 1 | +++ a/widgets/LanguageForm.php | ||
| 1 | +<?php | ||
| 2 | + | ||
| 3 | + namespace artweb\artbox\language\widgets; | ||
| 4 | + | ||
| 5 | + use artweb\artbox\language\models\Language; | ||
| 6 | + use yii\base\InvalidConfigException; | ||
| 7 | + use yii\bootstrap\Widget; | ||
| 8 | + use yii\db\ActiveRecord; | ||
| 9 | + use yii\widgets\ActiveForm; | ||
| 10 | + | ||
| 11 | + class LanguageForm extends Widget | ||
| 12 | + { | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * @var ActiveRecord[] $modelLangs | ||
| 16 | + */ | ||
| 17 | + public $modelLangs = []; | ||
| 18 | + | ||
| 19 | + /** | ||
| 20 | + * @var string $formView | ||
| 21 | + */ | ||
| 22 | + public $formView; | ||
| 23 | + | ||
| 24 | + /** | ||
| 25 | + * @var string $idPrefix | ||
| 26 | + */ | ||
| 27 | + public $idPrefix = 'lang'; | ||
| 28 | + | ||
| 29 | + /** | ||
| 30 | + * @var ActiveForm $form | ||
| 31 | + */ | ||
| 32 | + private $form; | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * @var Language[] $languages | ||
| 36 | + */ | ||
| 37 | + private $languages = []; | ||
| 38 | + | ||
| 39 | + public function init() | ||
| 40 | + { | ||
| 41 | + parent::init(); | ||
| 42 | + if($this->formView === NULL) { | ||
| 43 | + throw new InvalidConfigException('Form view must be set'); | ||
| 44 | + } | ||
| 45 | + if(empty( $this->modelLangs ) || !is_array($this->modelLangs)) { | ||
| 46 | + throw new InvalidConfigException('Language models must be passed'); | ||
| 47 | + } | ||
| 48 | + if(empty( $this->getForm() )) { | ||
| 49 | + throw new InvalidConfigException('Form model must be set'); | ||
| 50 | + } | ||
| 51 | + $this->languages = Language::find() | ||
| 52 | + ->where([ 'status' => true ]) | ||
| 53 | + ->orderBy([ 'default' => SORT_DESC ]) | ||
| 54 | + ->indexBy('id') | ||
| 55 | + ->all(); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + public function run() | ||
| 59 | + { | ||
| 60 | + return $this->render('language_form_frame', [ | ||
| 61 | + 'languages' => $this->languages, | ||
| 62 | + 'form_view' => $this->formView, | ||
| 63 | + 'modelLangs' => $this->modelLangs, | ||
| 64 | + 'form' => $this->getForm(), | ||
| 65 | + 'idPrefix' => $this->idPrefix, | ||
| 66 | + ]); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + public function getForm(): ActiveForm | ||
| 70 | + { | ||
| 71 | + return $this->form; | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + public function setForm(ActiveForm $value) | ||
| 75 | + { | ||
| 76 | + $this->form = $value; | ||
| 77 | + } | ||
| 78 | + } | ||
| 0 | \ No newline at end of file | 79 | \ No newline at end of file |
| 1 | +++ a/widgets/LanguagePicker.php | ||
| 1 | +<?php | ||
| 2 | + | ||
| 3 | + namespace artweb\artbox\language\widgets; | ||
| 4 | + | ||
| 5 | + use artweb\artbox\language\models\Language; | ||
| 6 | + use yii\bootstrap\Widget; | ||
| 7 | + | ||
| 8 | + class LanguagePicker extends Widget | ||
| 9 | + { | ||
| 10 | + | ||
| 11 | + public function init() | ||
| 12 | + { | ||
| 13 | + | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + public function run() | ||
| 17 | + { | ||
| 18 | + return $this->render('view', [ | ||
| 19 | + 'current' => Language::getCurrent(), | ||
| 20 | + 'languages' => Language::find() | ||
| 21 | + ->where([ | ||
| 22 | + '!=', | ||
| 23 | + 'id', | ||
| 24 | + Language::getCurrent()->id, | ||
| 25 | + ]) | ||
| 26 | + ->all(), | ||
| 27 | + ]); | ||
| 28 | + } | ||
| 29 | + } | ||
| 0 | \ No newline at end of file | 30 | \ No newline at end of file |
| 1 | +++ a/widgets/views/language_form_frame.php | ||
| 1 | +<?php | ||
| 2 | + use artweb\artbox\language\models\Language; | ||
| 3 | + use yii\db\ActiveRecord; | ||
| 4 | + use yii\helpers\Html; | ||
| 5 | + use yii\web\View; | ||
| 6 | + use yii\widgets\ActiveForm; | ||
| 7 | + | ||
| 8 | + /** | ||
| 9 | + * @var Language[] $languages | ||
| 10 | + * @var string $form_view | ||
| 11 | + * @var ActiveRecord $modelLangs | ||
| 12 | + * @var ActiveForm $form | ||
| 13 | + * @var View $this | ||
| 14 | + * @var string $idPrefix | ||
| 15 | + */ | ||
| 16 | +?> | ||
| 17 | +<div> | ||
| 18 | + <?php | ||
| 19 | + if(count($languages) > 1) { | ||
| 20 | + ?> | ||
| 21 | + <ul class="nav nav-tabs text-uppercase"> | ||
| 22 | + <?php | ||
| 23 | + $first = true; | ||
| 24 | + foreach($modelLangs as $lang => $model_lang) { | ||
| 25 | + if(!array_key_exists($lang, $languages)) { | ||
| 26 | + continue; | ||
| 27 | + } | ||
| 28 | + echo Html::tag('li', Html::a($languages[ $lang ]->url, [ | ||
| 29 | + '', | ||
| 30 | + '#' => $idPrefix . '_' . $lang, | ||
| 31 | + ], [ 'data-toggle' => 'tab' ]), [ | ||
| 32 | + 'class' => $first ? 'active' : '', | ||
| 33 | + ]); | ||
| 34 | + $first = false; | ||
| 35 | + } | ||
| 36 | + ?> | ||
| 37 | + </ul> | ||
| 38 | + <div class="tab-content"> | ||
| 39 | + <?php | ||
| 40 | + $first = true; | ||
| 41 | + foreach($modelLangs as $lang => $model_lang) { | ||
| 42 | + if(!array_key_exists($lang, $languages)) { | ||
| 43 | + continue; | ||
| 44 | + } | ||
| 45 | + echo Html::tag('div', $this->render($form_view, [ | ||
| 46 | + 'model_lang' => $model_lang, | ||
| 47 | + 'language' => $languages[ $lang ], | ||
| 48 | + 'form' => $form, | ||
| 49 | + ]), [ | ||
| 50 | + 'class' => 'tab-pane' . ( $first ? ' active' : '' ), | ||
| 51 | + 'id' => $idPrefix . '_' . $lang, | ||
| 52 | + ]); | ||
| 53 | + $first = false; | ||
| 54 | + } | ||
| 55 | + ?> | ||
| 56 | + </div> | ||
| 57 | + <?php | ||
| 58 | + } else { | ||
| 59 | + $language = current($languages); | ||
| 60 | + if(isset( $modelLangs[ $language->id ] )) { | ||
| 61 | + echo $this->render($form_view, [ | ||
| 62 | + 'model_lang' => $modelLangs[ $language->id ], | ||
| 63 | + 'language' => $language, | ||
| 64 | + 'form' => $form, | ||
| 65 | + ]); | ||
| 66 | + } | ||
| 67 | + } | ||
| 68 | + ?> | ||
| 69 | +</div> |
| 1 | +++ a/widgets/views/view.php | ||
| 1 | +<?php | ||
| 2 | + use artweb\artbox\language\components\LanguageRequest; | ||
| 3 | + use artweb\artbox\language\models\Language; | ||
| 4 | + use yii\bootstrap\Html; | ||
| 5 | + | ||
| 6 | + /** | ||
| 7 | + * @var Language $current Current language | ||
| 8 | + * @var Language[] $languages Available languages | ||
| 9 | + */ | ||
| 10 | +?> | ||
| 11 | +<div id="language_picker"> | ||
| 12 | + <span id="current_language"> | ||
| 13 | + <?php | ||
| 14 | + echo $current->name; | ||
| 15 | + ?> | ||
| 16 | + <span class="show-more-language">▼</span> | ||
| 17 | + </span> | ||
| 18 | + <ul id="languages"> | ||
| 19 | + <?php | ||
| 20 | + foreach($languages as $language) { | ||
| 21 | + ?> | ||
| 22 | + <li class="item-language"> | ||
| 23 | + <?php | ||
| 24 | + /** | ||
| 25 | + * @var LanguageRequest $request | ||
| 26 | + */ | ||
| 27 | + $request = \Yii::$app->getRequest(); | ||
| 28 | + echo Html::a($language->name, '/' . $language->url . $request->getLanguageUrl()); | ||
| 29 | + ?> | ||
| 30 | + </li> | ||
| 31 | + <?php | ||
| 32 | + } | ||
| 33 | + ?> | ||
| 34 | + </ul> | ||
| 35 | +</div> |