diff --git a/backend/components/menu/widgets/views/_item.php b/backend/components/menu/widgets/views/_item.php index d4a3f0f..c888c72 100644 --- a/backend/components/menu/widgets/views/_item.php +++ b/backend/components/menu/widgets/views/_item.php @@ -5,10 +5,12 @@ */ use yii\helpers\Html; +use kartik\form\ActiveForm; /** * @var $this \yii\web\View * @var $item array + * @var $model \core\forms\menu\MenuItemForm */ /* @var $menu_item \core\entities\menu\MenuItem */ @@ -23,96 +25,65 @@ $menu_item = $item['item'];

- name ?> - module ?> + translation->name ?> + module, ucfirst($menu_item->module)) ?>

-
-
- 'color: #555555', - ]) ?> - name, [ - 'id' => 'item-name-' . $menu_item->id, - 'class' => 'form-control', - ]) ?> -
-
- 'color: #555555', - ]) ?> - title_attr, [ - 'id' => 'item-title-attr-' . $menu_item->id, - 'class' => 'form-control', - ]) ?> -
-
+ ['/menu/save-item', 'id' => $menu_item->id], + ]); ?> -
-
- 'color: #555555', - ]) ?> - style, [ - 'id' => 'item-style-' . $menu_item->id, - 'class' => 'form-control', - ]) ?> -
-
- 'color: #555555', - ]) ?> - css, [ - 'id' => 'item-css-' . $menu_item->id, - 'class' => 'form-control', - ]) ?> -
+ params['translatedLanguages'] as $language => $language_name) { + $items[] = [ + 'label' => $language_name, + 'content' => $this->render('_item_tab', [ + 'form' => $form, + 'model' => $model, + 'language' => $language, + ]), + ]; + } + ?> + + -
- url_params): ?> -
- 'color: #555555', - ]) ?> - url, [ - 'id' => 'item-url-' . $menu_item->id, - 'class' => 'form-control', - ]) ?> -
- - url, [ - 'id' => 'item-url-' . $menu_item->id, - ]) ?> - +
- 'color: #555555', - ]) ?> - target, [ + field($model, 'target')->dropDownList([ '' => Yii::t('menu', 'Self Window'), '_blank' => Yii::t('menu', 'Blank Window'), - ], [ - 'id' => 'item-target-' . $menu_item->id, - 'class' => 'form-control', ]) ?> + field($model, 'style')->textInput(['maxlength' => true]) ?> +
+
+ field($model, 'css')->textInput(['maxlength' => true]) ?> + url_params): ?> + field($model, 'url')->textInput(['maxlength' => true]) ?> +
-
'item-delete-button btn btn-sm btn-danger', 'data-id' => $menu_item->id, ]) ?> - 'item-save-button btn btn-sm btn-success', - 'data-id' => $menu_item->id, + 'btn btn-success btn-sm' ]) ?>
+
+ +
diff --git a/backend/components/menu/widgets/views/_item_tab.php b/backend/components/menu/widgets/views/_item_tab.php new file mode 100644 index 0000000..b880cdf --- /dev/null +++ b/backend/components/menu/widgets/views/_item_tab.php @@ -0,0 +1,25 @@ +params['defaultLanguage'] ? '' : '_' . $language; +?> + +
+
+ field($model, 'name' . $postfix)->textInput(['maxlength' => true]) ?> +
+
+ field($model, 'title_attr' . $postfix)->textInput(['maxlength' => true]) ?> +
+
+ diff --git a/backend/components/menu/widgets/views/menu.php b/backend/components/menu/widgets/views/menu.php index 31464b2..6cafbee 100644 --- a/backend/components/menu/widgets/views/menu.php +++ b/backend/components/menu/widgets/views/menu.php @@ -11,6 +11,7 @@ */ use backend\components\menu\assets\MenuAsset; +use core\forms\menu\MenuItemForm; use yii\helpers\Html; use yii\helpers\Url; use yii\web\JsExpression; @@ -20,20 +21,23 @@ MenuAsset::register($this); function menu_generate($items) { $html = '
    '; foreach ($items as $item) { + $menuItemForm = new MenuItemForm($item['item']); $html.=Yii::$app->getView()->render( '_item', [ 'item' => $item, + 'model' => $menuItemForm, ] ); } return $html . '
'; } $name_empty_error = Yii::t('menu', 'Name must be specified'); -$item_save_url = Url::to(['menu/save-menu-item-data']); +//$item_save_url = Url::to(['menu/save-menu-item-data']); +$item_save_url = ''; // delete this $item_delete_url = Url::to(['menu/delete-menu-item']); $confirm_delete_message = Yii::t('buttons', 'Are you sure you want to delete this item?'); $current_url = Url::to(['menu/index', 'id' => $menu->id]); $js = << AccessControl::class, 'rules' => [ [ - 'actions' => ['create', 'update', 'delete', 'index', 'save-menu-items', 'save-menu-item-data', 'delete-menu-item'], + 'actions' => [ + 'create', 'update', 'delete', 'index', 'save-menu-items', + 'delete-menu-item', + 'save-item' + ], 'allow' => true, 'roles' => ['MenuManagement'], ], @@ -62,7 +66,11 @@ class MenuController extends Controller public function actionIndex($id = null) { - $menus = Menu::find()->all(); + $menus = []; // menu list + $menu_records = Menu::find()->all(); + foreach ($menu_records as $menu_record) { + $menus[$menu_record->id] = isset($menu_record->translation) && $menu_record->translation->name ? $menu_record->translation->name : $menu_record->findTranslation(Yii::$app->params['defaultLanguage'])->name; + } $form = new MenuSelectForm(); if ($form->load(Yii::$app->request->get()) && $form->validate()) { @@ -138,6 +146,22 @@ class MenuController extends Controller return $this->redirect(['index']); } + public function actionSaveItem($id) + { + $item = $this->findItemModel($id); + $form = new MenuItemForm($item); + if ($form->load(Yii::$app->request->post()) && $form->validate()) { + try { + $this->menu_item_service->edit($item->id, $form); + return $this->redirect(['index', 'id' => $item->menu_id]); + } catch (\DomainException $e) { + Yii::$app->errorHandler->logException($e); + Yii::$app->session->setFlash('error', $e->getMessage()); + } + } + return $this->redirect(['index', 'id' => $item->menu_id]); + } + public function actionDeleteMenuItem() { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; @@ -160,17 +184,7 @@ class MenuController extends Controller return ['result' => 'error', 'message' => 'Request error']; } - //private function deleteItem(MenuItem $item): void - //{ - /*if ($item->hasChildren()) { - $children = $item->children; - foreach ($children as $child) { - $this->deleteItem($child); - } - }*/ - //$item->delete(); - //} - + /* public function actionSaveMenuItemData() { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; @@ -203,6 +217,7 @@ class MenuController extends Controller } return ['result' => 'error', 'message' => 'Request error']; } + */ public function actionSaveMenuItems() { diff --git a/backend/controllers/SiteController.php b/backend/controllers/SiteController.php index 1c4d5e1..e7ee893 100644 --- a/backend/controllers/SiteController.php +++ b/backend/controllers/SiteController.php @@ -3,19 +3,29 @@ namespace backend\controllers; use core\entities\Search; use core\forms\SearchForm; +use core\helpers\LanguageHelper; +use core\services\user\UserManageService; use Yii; use yii\data\ActiveDataProvider; use yii\web\Controller; use yii\filters\VerbFilter; use yii\filters\AccessControl; use common\models\LoginForm; +use yii\web\NotFoundHttpException; /** * Site controller */ class SiteController extends Controller { - /** + private $service; + + public function __construct( string $id, $module, UserManageService $service, array $config = [] ) { + parent::__construct( $id, $module, $config ); + $this->service = $service; + } + + /** * @inheritdoc */ public function behaviors() @@ -29,7 +39,7 @@ class SiteController extends Controller 'allow' => true, ], [ - 'actions' => ['index', 'search'], + 'actions' => ['index', 'search', 'language'], 'allow' => true, 'roles' => ['Dashboard'], ], @@ -43,6 +53,7 @@ class SiteController extends Controller 'class' => VerbFilter::class, 'actions' => [ 'logout' => ['post'], + 'language' => ['post'], ], ], ]; @@ -100,6 +111,15 @@ class SiteController extends Controller //Yii::$app->session->setFlash('error', $e->getMessage()); } } + return ''; + } + + public function actionLanguage($language) + { + if ($language && in_array($language, array_keys(Yii::$app->params['backendTranslatedLanguages']))) { + $this->service->setBackendLanguage($language); + } + return $this->redirect(Yii::$app->request->referrer); } public function beforeAction($action) diff --git a/backend/messages/ru/menu.php b/backend/messages/ru/menu.php index 5b7125d..c355b1b 100644 --- a/backend/messages/ru/menu.php +++ b/backend/messages/ru/menu.php @@ -24,4 +24,9 @@ return [ 'Insert Code' => 'Код вставки', 'For template' => 'Для шаблона', 'For editor' => 'Для редактора', + 'Not set' => 'Не указано', + 'Name' => 'Название', + 'Title attribute' => 'Аттрибут Title', + 'CSS Classes' => 'Классы CSS', + 'CSS Style' => 'Стиль CSS', ]; \ No newline at end of file diff --git a/backend/views/layouts/header.php b/backend/views/layouts/header.php index 10ab7af..0cbba93 100644 --- a/backend/views/layouts/header.php +++ b/backend/views/layouts/header.php @@ -37,7 +37,7 @@ use core\components\avatar_generator\AvatarGenerator;
  • - User Image

    @@ -51,7 +51,7 @@ use core\components\avatar_generator\AvatarGenerator;
  • - user image

    @@ -64,7 +64,7 @@ use core\components\avatar_generator\AvatarGenerator;
  • - user image

    @@ -77,7 +77,7 @@ use core\components\avatar_generator\AvatarGenerator;
  • - user image

    @@ -90,7 +90,7 @@ use core\components\avatar_generator\AvatarGenerator;
  • - user image

    @@ -197,14 +197,19 @@ use core\components\avatar_generator\AvatarGenerator;

  • diff --git a/common/bootstrap/SetUp.php b/common/bootstrap/SetUp.php index c6a2fb3..0057d6e 100644 --- a/common/bootstrap/SetUp.php +++ b/common/bootstrap/SetUp.php @@ -136,9 +136,10 @@ class SetUp implements BootstrapInterface $container->set(ImageUploadBehavior::class, FlySystemImageUploadBehavior::class); */ - // Set backend language + // Set backend languages if (basename($app->getBasePath()) === 'backend') { $app->language = ! $app->user->isGuest && $app->user->identity->user->backend_language ? $app->user->identity->user->backend_language : $app->language; + $app->params['frontendLanguage'] = Yii::$app->session->get('frontendLanguage', Yii::$app->params['defaultLanguage']); } // Connect common modules diff --git a/common/modules/blog/migrations/m180827_195932_set_blog_unicode_collate.php b/common/modules/blog/migrations/m180827_195932_set_blog_unicode_collate.php new file mode 100644 index 0000000..b5568f2 --- /dev/null +++ b/common/modules/blog/migrations/m180827_195932_set_blog_unicode_collate.php @@ -0,0 +1,33 @@ +getDb(); + $db->createCommand('SET FOREIGN_KEY_CHECKS=0;')->execute(); + + foreach ($tables as $table) { + $db->createCommand( "ALTER TABLE `$table` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci" )->execute(); + } + + $db->createCommand('SET FOREIGN_KEY_CHECKS=1;')->execute(); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + true; + } +} diff --git a/common/modules/links/widgets/MenuItemCreatorWidget.php b/common/modules/links/widgets/MenuItemCreatorWidget.php index 6f127ff..1e28e43 100644 --- a/common/modules/links/widgets/MenuItemCreatorWidget.php +++ b/common/modules/links/widgets/MenuItemCreatorWidget.php @@ -16,7 +16,7 @@ class MenuItemCreatorWidget extends Widget public function run() { $form = new MenuItemForm(); - $form->module = \Yii::t('links', 'Links'); + $form->module = 'links'; $form->menu_id = $this->menu_id; return $this->render('menu-item/creator', [ diff --git a/common/modules/pages/PagesModule.php b/common/modules/pages/PagesModule.php index 69868e8..9dabea5 100644 --- a/common/modules/pages/PagesModule.php +++ b/common/modules/pages/PagesModule.php @@ -8,7 +8,7 @@ use yii\helpers\ArrayHelper; /** - * blog module definition class + * page module definition class */ class PagesModule extends \yii\base\Module implements ModuleInterface { @@ -33,7 +33,9 @@ class PagesModule extends \yii\base\Module implements ModuleInterface $app->controllerMap['migrate']['migrationPath'][] = '@common/modules/pages/migrations'; // add search rules - $app->params['search_rules'][] = "SELECT title, content, CONCAT('/pages/manage/page/view/', id) AS url FROM {{pages}}"; + $app->params['search_rules'][] = " + SELECT title, content, CONCAT('/pages/manage/page/view/', page_id) AS url FROM {{pages_lng}} + "; $app->getUrlManager()->addRules([ ['class' => 'common\modules\pages\urls\PageMainUrlRule'], diff --git a/common/modules/pages/controllers/manage/PageController.php b/common/modules/pages/controllers/manage/PageController.php index 91201a8..8b78d84 100644 --- a/common/modules/pages/controllers/manage/PageController.php +++ b/common/modules/pages/controllers/manage/PageController.php @@ -191,15 +191,37 @@ class PageController extends Controller \Yii::$app->response->format = Response::FORMAT_JSON; $out = ['results' => ['id' => '', 'text' => '']]; if (!is_null($q)) { - $data = Page::find()->select('id, title as text')->andWhere(['tree' => 1])->andWhere(['like', 'title', $q])->limit(20)->asArray()->all(); + $data = []; + $pages = Page::find() + ->with('translation') + ->leftJoin('{{%pages_lng}}', '`pages_lng`.`page_id` = `pages`.`id`') + ->andWhere(['tree' => 1]) + ->andWhere(['like', 'pages_lng.title', $q]) + ->limit(20) + ->all(); + + foreach ($pages as $page) { + $data[] = [ + 'id' => $page->id, + 'text' => isset($page->translation) ? $page->translation->title : null, + ]; + } $out['results'] = array_values($data); } elseif ($id > 0) { - $tag_name = Page::findOne($id)->title; + $tag_name = Page::findOne($id)->translate->title; $out['results'] = ['id' => $tag_name, 'text' => $tag_name]; } else { - $data = Page::find()->select('id, title as text')->andWhere(['tree' => 1])->orderBy(['id' => SORT_DESC])->limit(20)->asArray()->all(); + $data = []; + $pages = Page::find()->andWhere(['tree' => 1])->orderBy(['id' => SORT_DESC])->limit(20)->all(); + + foreach ($pages as $page) { + $data[] = [ + 'id' => $page->id, + 'text' => isset($page->translation) ? $page->translation->title : null, + ]; + } $out['results'] = array_values($data); } return $out; diff --git a/common/modules/pages/entities/Page.php b/common/modules/pages/entities/Page.php index 57f1d14..15e6851 100644 --- a/common/modules/pages/entities/Page.php +++ b/common/modules/pages/entities/Page.php @@ -3,6 +3,7 @@ namespace common\modules\pages\entities; use common\modules\pages\entities\queries\PageQuery; +use core\behaviors\LanguageBehavior; use paulzi\nestedsets\NestedSetsBehavior; use core\behaviors\MetaBehavior; use yii\behaviors\TimestampBehavior; @@ -12,15 +13,13 @@ use yii\db\ActiveRecord; use core\entities\Meta; use Yii; use yii\helpers\Inflector; +use yii\helpers\Json; /** * @property int $id - * @property string $title * @property string $slug - * @property string $content * @property int $created_at * @property int $updated_at - * @property string $meta_json * @property int $tree * @property int $lft * @property int $rgt @@ -28,7 +27,12 @@ use yii\helpers\Inflector; * @property int $type * @property int $revision_at * @property int $revision_id - * @property Meta $meta + * + * @method ActiveRecord findTranslation(string $language) + * @method void saveTranslations($translations) + * + * @property ActiveRecord[] translations + * @property ActiveRecord[] translation * * @property Page $parent * @property Page[] $parents @@ -45,29 +49,32 @@ class Page extends ActiveRecord public $meta; - public static function create($title, $slug, $content, $type = Page::TYPE_PUBLIC, Meta $meta): self + public $_form; + + public static function create($form, $slug, $type = Page::TYPE_PUBLIC): self { $page = new static(); - $page->title = $title; $page->slug = $slug; - $page->content = $content; - $page->meta = $meta; $page->type = $type; + + $page->_form = $form; return $page; } - public function edit($title, $slug, $content, $type = Page::TYPE_PUBLIC, Meta $meta): void + public function edit($form, $slug, $type = Page::TYPE_PUBLIC): void { - $this->title = $title; $this->slug = $slug; - $this->content = $content; - $this->meta = $meta; $this->type = $type; + + $this->_form = $form; } public function getSeoTitle(): string { - return $this->meta->title ?: $this->title; + return $this->translation->meta_title; + //return $this->meta->title ?: $this->title; + //$meta = Meta::createMeta($this->meta_json); + //return $meta->title; } public static function tableName(): string @@ -78,7 +85,6 @@ class Page extends ActiveRecord public function behaviors(): array { return [ - MetaBehavior::class, [ 'class' => NestedSetsBehavior::class, 'treeAttribute' => 'tree', @@ -91,6 +97,15 @@ class Page extends ActiveRecord 'ensureUnique' => true, 'preserveNonEmptyValues' => true, ], + [ + 'class' => LanguageBehavior::class, + 'virtualClassName' => 'PagesVirtualTranslate', + 'translatedLanguages' => \Yii::$app->params['translatedLanguages'], + 'relativeField' => 'page_id', + 'tableName' => "{{%pages_lng}}", + 'attributes' => ['title', 'content', 'meta_title', 'meta_description', 'meta_keywords'], + 'defaultLanguage' => \Yii::$app->params['defaultLanguage'], + ], ]; } diff --git a/common/modules/pages/forms/PageForm.php b/common/modules/pages/forms/PageForm.php index 6df8c4c..24ccac7 100644 --- a/common/modules/pages/forms/PageForm.php +++ b/common/modules/pages/forms/PageForm.php @@ -2,90 +2,114 @@ namespace common\modules\pages\forms; -use core\forms\CompositeForm; -use core\forms\MetaForm; +use core\components\LanguageDynamicModel; use common\modules\pages\entities\Page; use core\validators\SlugValidator; use yii\db\ActiveQuery; use yii\helpers\ArrayHelper; use Yii; -/** - * @property MetaForm $meta; - */ -class PageForm extends CompositeForm +class PageForm extends LanguageDynamicModel { public $type; - public $title; public $slug; public $content; public $parentId; + public $meta_title; + public $meta_description; + public $meta_keywords; + public $_page; public function __construct(Page $page = null, $config = []) { if ($page) { - $this->title = $page->title; $this->slug = $page->slug; - $this->content = $page->content; $this->parentId = $page->parent ? $page->parent->id : null; - $this->meta = new MetaForm($page->meta); + $this->_page = $page; - } else { - $this->meta = new MetaForm(); } parent::__construct($config); + // fill translate values + if ($page) { + foreach ( $page->translations as $translate ) { + //$meta_translate = Json::decode($translate->meta_json); // get meta + if ($translate->language == Yii::$app->params['backendDefaultLanguage']) { + $this->{'title'} = $translate->title; + $this->{'content'} = $translate->content; + // fill meta + $this->{'meta_title'} = $translate->meta_title; + $this->{'meta_description'} = $translate->meta_description; + $this->{'meta_keywords'} = $translate->meta_keywords; + } + else { + $this->{'title' . '_' . $translate->language} = $translate->title; + $this->{'content' . '_' . $translate->language} = $translate->content; + // fill meta + $this->{'meta_title' . '_' . $translate->language} = $translate->meta_title; + $this->{'meta_description' . '_' . $translate->language} = $translate->meta_description; + $this->{'meta_keywords' . '_' . $translate->language} = $translate->meta_keywords; + } + }; + }; } public function rules(): array { - return [ - [['title'], 'required'], - [['parentId'], 'integer'], - [['title', 'slug'], 'string', 'max' => 255], - [['content'], 'string'], - ['slug', SlugValidator::class], - [['slug'], 'unique', 'targetClass' => Page::class, 'filter' => function (ActiveQuery $query) { - if ($this->type != Page::TYPE_PUBLIC) { - $query->andWhere($this->type . '=' . Page::TYPE_PUBLIC); - } + return array_merge( + parent::rules(), + [ + [['title'], 'required'], + [['parentId'], 'integer'], + [['title', 'slug', 'meta_title'], 'string', 'max' => 255], + [['content', 'meta_description', 'meta_keywords'], 'string'], + ['slug', SlugValidator::class], + [['slug'], 'unique', 'targetClass' => Page::class, 'filter' => function (ActiveQuery $query) { + if ($this->type != Page::TYPE_PUBLIC) { + $query->andWhere($this->type . '=' . Page::TYPE_PUBLIC); + } - $query->andWhere(['type' => Page::TYPE_PUBLIC]); - if ($this->_page) { - $query->andWhere(['<>', 'id', $this->_page->id]); - } - return $query; - }], - ]; + $query->andWhere(['type' => Page::TYPE_PUBLIC]); + if ($this->_page) { + $query->andWhere(['<>', 'id', $this->_page->id]); + } + return $query; + }], + ] + ); } public function attributeLabels() { - return [ - 'title' => Yii::t('pages', 'Title'), - 'slug' => Yii::t('pages', 'Slug'), - 'id' => Yii::t('pages', 'ID'), - 'content' => Yii::t('pages', 'Content'), - 'parentId' => Yii::t('pages', 'Parent Page'), - ]; + return array_merge( + parent::attributeLabels(), + [ + 'title' => Yii::t('pages', 'Title'), + 'slug' => Yii::t('pages', 'Slug'), + 'id' => Yii::t('pages', 'ID'), + 'content' => Yii::t('pages', 'Content'), + 'parentId' => Yii::t('pages', 'Parent Page'), + 'meta_title' => Yii::t('pages', 'META Title'), + 'meta_description' => Yii::t('pages', 'META Description'), + 'meta_keywords' => Yii::t('pages', 'META Keywords'), + ] + ); } public function attributeHints() { - return [ - 'slug' => Yii::t('pages', 'SEO link will be generated automatically if not specified'), - ]; + return array_merge( + parent::attributeHints(), + [ + 'slug' => Yii::t('pages', 'SEO link will be generated automatically if not specified'), + ] + ); } public function parentsList(): array { - return ArrayHelper::map(Page::find()->andWhere(['tree' => 1])->orderBy('lft')->asArray()->all(), 'id', function (array $page) { - return ($page['depth'] > 1 ? str_repeat('-- ', $page['depth'] - 1) . ' ' : '') . $page['title']; - }); - } - - public function internalForms(): array - { - return ['meta']; + return ArrayHelper::map(Page::find()->andWhere(['tree' => 1])->orderBy('lft')->all(), 'id', function (Page $page) { + return ($page->depth > 1 ? str_repeat('-- ', $page->depth - 1) . ' ' : '') . ($page->translation ? $page->translation->title : Yii::t('pages', '- no parent -')); + }); } -} \ No newline at end of file +} diff --git a/common/modules/pages/helpers/PageHelper.php b/common/modules/pages/helpers/PageHelper.php index 583c52d..0b1684e 100644 --- a/common/modules/pages/helpers/PageHelper.php +++ b/common/modules/pages/helpers/PageHelper.php @@ -7,6 +7,7 @@ namespace common\modules\pages\helpers; use common\modules\pages\entities\Page; +use common\modules\pages\forms\PageForm; use core\entities\Meta; class PageHelper @@ -16,16 +17,12 @@ class PageHelper $model->revision_at = time(); + $pageForm = new PageForm($model); + $page = Page::create( - $model->title, + $pageForm, $model->slug, - $model->content, - Page::TYPE_REVISION, - new Meta( - $model->meta->title, - $model->meta->description, - $model->meta->keywords - ) + Page::TYPE_REVISION ); $page->revision_at = $model->updated_at; diff --git a/common/modules/pages/messages/ru/pages.php b/common/modules/pages/messages/ru/pages.php index b1e79c0..c711b33 100644 --- a/common/modules/pages/messages/ru/pages.php +++ b/common/modules/pages/messages/ru/pages.php @@ -7,6 +7,9 @@ return [ 'Common' => 'Основное', 'Content' => 'Содержание', 'SEO' => 'META теги', + 'META Title' => 'Заголовок', + 'META Description' => 'Описание', + 'META Keywords' => 'Ключевые слова', 'Title' => 'Заголовок', 'Slug' => 'ЧПУ ссылка', 'ID' => '№', @@ -24,4 +27,5 @@ return [ 'Publish' => 'Публикация', 'Preview on site' => 'Просмотр на сайте', 'SEO link will be generated automatically if not specified' => 'ЧПУ ссылка будет сгенерирована автоматически, если не указано', + '- no parent -' => '- корневая страница -', ]; \ No newline at end of file diff --git a/common/modules/pages/migrations/m180824_202316_create_pages_lng_table.php b/common/modules/pages/migrations/m180824_202316_create_pages_lng_table.php new file mode 100644 index 0000000..0e7d84f --- /dev/null +++ b/common/modules/pages/migrations/m180824_202316_create_pages_lng_table.php @@ -0,0 +1,43 @@ +db->driverName === 'mysql') { + $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB'; + } + + $this->createTable('{{%pages_lng}}', [ + 'id' => $this->primaryKey(), + 'page_id' => $this->integer()->notNull(), + 'language' => $this->string(6)->notNull(), + 'title' => $this->string(255), + 'content' => 'MEDIUMTEXT', + ], $tableOptions); + + $this->createIndex('idx_pages_lng_language', '{{%pages_lng}}', 'language'); + $this->createIndex('idx_pages_lng_page_id', '{{%pages_lng}}', 'page_id'); + $this->addForeignKey('frg_pages_lng_pages_page_id_id', '{{%pages_lng}}', 'page_id', '{{%pages}}', 'id', 'CASCADE', 'CASCADE'); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropForeignKey('frg_pages_lng_pages_page_id_id', '{{%pages_lng}}'); + $this->dropColumn('idx_pages_lng_page_id', '{{%pages_lng}}'); + $this->dropColumn('idx_pages_lng_language', '{{%pages_lng}}'); + $this->dropTable('{{%pages_lng}}'); + } +} diff --git a/common/modules/pages/migrations/m180825_155812_add_pages_lng_meta_json_field.php b/common/modules/pages/migrations/m180825_155812_add_pages_lng_meta_json_field.php new file mode 100644 index 0000000..3994038 --- /dev/null +++ b/common/modules/pages/migrations/m180825_155812_add_pages_lng_meta_json_field.php @@ -0,0 +1,25 @@ +addColumn('{{%pages_lng}}', 'meta_json', $this->text()); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropColumn('{{%pages_lng}}', 'meta_json'); + } +} diff --git a/common/modules/pages/migrations/m180825_183752_add_pages_pages_lng_meta_fields.php b/common/modules/pages/migrations/m180825_183752_add_pages_pages_lng_meta_fields.php new file mode 100644 index 0000000..b5013a5 --- /dev/null +++ b/common/modules/pages/migrations/m180825_183752_add_pages_pages_lng_meta_fields.php @@ -0,0 +1,35 @@ +addColumn('{{%pages_lng}}', 'meta_title', $this->string()); + $this->addColumn('{{%pages_lng}}', 'meta_description', $this->text()); + $this->addColumn('{{%pages_lng}}', 'meta_keywords', $this->string()); + + $this->dropColumn('{{%pages}}', 'meta_json'); + $this->dropColumn('{{%pages_lng}}', 'meta_json'); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropColumn('{{%pages_lng}}', 'meta_title'); + $this->dropColumn('{{%pages_lng}}', 'meta_description'); + $this->dropColumn('{{%pages_lng}}', 'meta_keywords'); + + $this->addColumn('{{%pages}}', 'meta_json', $this->text()); + $this->addColumn('{{%pages_lng}}', 'meta_json', $this->text()); + } +} diff --git a/common/modules/pages/migrations/m180826_083923_remove_pages_title_content_fields.php b/common/modules/pages/migrations/m180826_083923_remove_pages_title_content_fields.php new file mode 100644 index 0000000..91f63b2 --- /dev/null +++ b/common/modules/pages/migrations/m180826_083923_remove_pages_title_content_fields.php @@ -0,0 +1,27 @@ +dropColumn('{{%pages}}', 'title'); + $this->dropColumn('{{%pages}}', 'content'); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->addColumn('{{%pages}}', 'title', $this->string(255)->notNull()); + $this->addColumn('{{%pages}}', 'title', 'MEDIUMTEXT'); + } +} diff --git a/common/modules/pages/migrations/m180827_195748_set_pages_unicode_collate.php b/common/modules/pages/migrations/m180827_195748_set_pages_unicode_collate.php new file mode 100644 index 0000000..8bdcc6f --- /dev/null +++ b/common/modules/pages/migrations/m180827_195748_set_pages_unicode_collate.php @@ -0,0 +1,33 @@ +getDb(); + $db->createCommand('SET FOREIGN_KEY_CHECKS=0;')->execute(); + + foreach ($tables as $table) { + $db->createCommand( "ALTER TABLE `$table` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci" )->execute(); + } + + $db->createCommand('SET FOREIGN_KEY_CHECKS=1;')->execute(); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + true; + } +} diff --git a/common/modules/pages/repositories/PageRepository.php b/common/modules/pages/repositories/PageRepository.php index 05de05c..a9145e0 100644 --- a/common/modules/pages/repositories/PageRepository.php +++ b/common/modules/pages/repositories/PageRepository.php @@ -9,7 +9,8 @@ class PageRepository { public function get($id): Page { - if (!$page = Page::findOne($id)) { + //if (!$page = Page::findOne($id)) { + if (!$page = Page::find()->with('translations')->andWhere(['id' => $id])->one()) { throw new NotFoundException('Page is not found.'); } return $page; diff --git a/common/modules/pages/services/PageManageService.php b/common/modules/pages/services/PageManageService.php index 088bf49..7a4b208 100644 --- a/common/modules/pages/services/PageManageService.php +++ b/common/modules/pages/services/PageManageService.php @@ -3,10 +3,10 @@ namespace common\modules\pages\services; use common\modules\pages\helpers\PageHelper; -use core\entities\Meta; use common\modules\pages\entities\Page; use common\modules\pages\forms\PageForm; use common\modules\pages\repositories\PageRepository; +use yii\db\ActiveRecord; class PageManageService { @@ -21,15 +21,9 @@ class PageManageService { $parent = $this->pages->get($form->parentId); $page = Page::create( - $form->title, + $form, $form->slug, - $form->content, - $type, - new Meta( - $form->meta->title, - $form->meta->description, - $form->meta->keywords - ) + $type ); if ($type == Page::TYPE_PUBLIC) { $page->appendTo( $parent ); @@ -50,15 +44,9 @@ class PageManageService $this->assertIsNotRoot($page); $page->edit( - $form->title, + $form, $form->slug, - $form->content, - $type, - new Meta( - $form->meta->title, - $form->meta->description, - $form->meta->keywords - ) + $type ); if ($form->parentId !== $page->parent->id) { $parent = $this->pages->get($form->parentId); @@ -90,6 +78,16 @@ class PageManageService public function remove($id): void { $page = $this->pages->get($id); + + // Remove revisions + $revisions = Page::find() + ->andWhere(['revision_id' => $page->id]) + ->all(); + foreach ($revisions as $revision) { + $this->assertIsNotRoot($revision); + $this->pages->remove($revision); + } + $this->assertIsNotRoot($page); $this->pages->remove($page); } @@ -119,15 +117,24 @@ class PageManageService $page = $this->pages->get($id); $from = $this->pages->get($from_id); - $page->title = $from->title; $page->slug = $from->slug; - $page->content = $from->content; $page->created_at = $from->created_at; $page->updated_at = $from->updated_at; $page->revision_at = $from->revision_at; $this->pages->save($page); + // remove distance translation + foreach ( $page->translations as $translate ) { + /* @var $translate ActiveRecord */ + $translate->delete(); + } + // move source translation + foreach ( $from->translations as $translate ) { + /* @var $translate ActiveRecord */ + $translate->page_id = $page->id; + $translate->save(); + } // remove current revision $this->pages->remove($from); Page::deleteAll(['AND', ['revision_id' => $page->id], ['>', 'revision_at', $page->revision_at]]); diff --git a/common/modules/pages/urls/PageMainUrlRule.php b/common/modules/pages/urls/PageMainUrlRule.php index 3c29563..7aab4c9 100644 --- a/common/modules/pages/urls/PageMainUrlRule.php +++ b/common/modules/pages/urls/PageMainUrlRule.php @@ -3,12 +3,14 @@ namespace common\modules\pages\urls; use common\modules\pages\repositories\read\PageReadRepository; +use core\helpers\LanguageHelper; use yii\base\BaseObject; use yii\caching\Cache; use yii\caching\TagDependency; use yii\web\UrlNormalizerRedirectException; use yii\web\UrlRuleInterface; use InvalidArgumentException; +use yii; class PageMainUrlRule extends BaseObject implements UrlRuleInterface { @@ -25,9 +27,12 @@ class PageMainUrlRule extends BaseObject implements UrlRuleInterface $this->cache = $cache; } - public function parseRequest($manager, $request) + public function parseRequest($manager, $request) { - if (preg_match('#^' . $this->prefix . '(.*[a-z])$#is', $request->pathInfo, $matches)) { + $uri = ltrim(LanguageHelper::processLangInUrl($request->pathInfo), '/'); + //if (preg_match('#^' . $this->prefix . '(.*[a-z])$#is', $request->pathInfo, $matches)) { + //if (preg_match('#^' . $this->prefix . '([0-9a-z_\-]*)$#is', $request->pathInfo, $matches)) { + if (preg_match('#^' . $this->prefix . '([0-9a-z_\-]*)$#is', $uri, $matches)) { $path = $matches['1']; $result = $this->cache->getOrSet( [ 'page_main_route', 'path' => $path ], function () use ( $path ) { @@ -69,14 +74,7 @@ class PageMainUrlRule extends BaseObject implements UrlRuleInterface return '#'; //throw new InvalidArgumentException('Undefined id.'); } - - $url = $this->prefix . '/' . $url; - unset($params['id']); - if (!empty($params) && ($query = http_build_query($params)) !== '') { - $url .= '?' . $query; - } - - return $url; + return LanguageHelper::addLangToUrl($url, isset($params['language']) ? $params['language'] : null); } return false; } diff --git a/common/modules/pages/views/manage/page/_form.php b/common/modules/pages/views/manage/page/_form.php index 740d61e..f23ed3f 100644 --- a/common/modules/pages/views/manage/page/_form.php +++ b/common/modules/pages/views/manage/page/_form.php @@ -36,20 +36,40 @@ $this->registerJs($js2);
    field($model, 'parentId')->dropDownList($model->parentsList()) ?> - field($model, 'title')->textInput(['maxlength' => true]) ?> field($model, 'slug')->textInput(['maxlength' => true]) ?> - field($model, 'content')->widget(CKEditor::class) ?>
    + params['translatedLanguages'] as $language => $language_name) { + $items[] = [ + 'label' => $language_name, + 'content' => $this->render('_form_tab', [ + 'form' => $form, + 'model' => $model, + 'language' => $language, + ]), + ]; + } + ?> + + + +
    'btn btn-success']) ?> diff --git a/common/modules/pages/views/manage/page/_form_tab.php b/common/modules/pages/views/manage/page/_form_tab.php new file mode 100644 index 0000000..c124b20 --- /dev/null +++ b/common/modules/pages/views/manage/page/_form_tab.php @@ -0,0 +1,29 @@ +params['defaultLanguage'] ? '' : '_' . $language; +?> + +field($model, 'title' . $postfix)->textInput(['maxlength' => true]) ?> +field($model, 'content' . $postfix)->widget(CKEditor::class) ?> + +
    +
    +
    + field($model, 'meta_title' . $postfix)->textInput() ?> + field($model, 'meta_description' . $postfix)->textarea(['rows' => 2]) ?> + field($model, 'meta_keywords' . $postfix)->textInput() ?> +
    +
    diff --git a/common/modules/pages/views/manage/page/_view_tab.php b/common/modules/pages/views/manage/page/_view_tab.php new file mode 100644 index 0000000..bc6827c --- /dev/null +++ b/common/modules/pages/views/manage/page/_view_tab.php @@ -0,0 +1,71 @@ + + + $page, + 'attributes' => [ + [ + 'label' => Yii::t('pages', 'Title'), + 'value' => function(Page $entity) use ($language) { + return $entity->findTranslation($language)->title; + } + ], + ], +]) ?> + +
    +
    +
    + + $page, + 'attributes' => [ + [ + 'label' => Yii::t('pages', 'META Title'), + 'value' => function(Page $entity) use ($language) { + return $entity->findTranslation($language)->meta_title; + } + ], + [ + 'label' => Yii::t('pages', 'META Description'), + 'value' => function(Page $entity) use ($language) { + return $entity->findTranslation($language)->meta_description; + } + ], + [ + 'label' => Yii::t('pages', 'META Keywords'), + 'value' => function(Page $entity) use ($language) { + return $entity->findTranslation($language)->meta_keywords; + } + ], + ], +]) ?> +
    +
    + +
    +
    +
    + formatter->asHtml($page->findTranslation($language)->content, [ + 'Attr.AllowedRel' => array('nofollow'), + 'HTML.SafeObject' => true, + 'Output.FlashCompat' => true, + 'HTML.SafeIframe' => true, + 'URI.SafeIframeRegexp'=>'%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/)%', + ]) ?> +
    +
    \ No newline at end of file diff --git a/common/modules/pages/views/manage/page/index.php b/common/modules/pages/views/manage/page/index.php index 01dfdad..b99c454 100644 --- a/common/modules/pages/views/manage/page/index.php +++ b/common/modules/pages/views/manage/page/index.php @@ -28,7 +28,7 @@ $this->params['breadcrumbs'][] = $this->title; 'attribute' => 'title', 'value' => function (Page $model) { $indent = ($model->depth > 1 ? str_repeat('  ', $model->depth - 1) . ' ' : ''); - return $indent . Html::a(Html::encode($model->title), ['view', 'id' => $model->id]); + return $indent . Html::a(Html::encode($model->translation->title), ['view', 'id' => $model->id]); }, 'format' => 'raw', ], diff --git a/common/modules/pages/views/manage/page/update.php b/common/modules/pages/views/manage/page/update.php index cea060a..cfefaf8 100644 --- a/common/modules/pages/views/manage/page/update.php +++ b/common/modules/pages/views/manage/page/update.php @@ -4,9 +4,9 @@ /* @var $page \common\modules\pages\entities\Page */ /* @var $model \common\modules\pages\forms\PageForm */ -$this->title = Yii::t('pages', 'Update Page: {name}', ['name' => $page->title]); +$this->title = Yii::t('pages', 'Update Page: {name}', ['name' => $page->translation->title]); $this->params['breadcrumbs'][] = ['label' => Yii::t('pages', 'Pages'), 'url' => ['index']]; -$this->params['breadcrumbs'][] = ['label' => $page->title, 'url' => ['view', 'id' => $page->id]]; +$this->params['breadcrumbs'][] = ['label' => $page->translation->title, 'url' => ['view', 'id' => $page->id]]; $this->params['breadcrumbs'][] = Yii::t('buttons', 'Editing'); ?>
    diff --git a/common/modules/pages/views/manage/page/view.php b/common/modules/pages/views/manage/page/view.php index ec2920e..9dba667 100644 --- a/common/modules/pages/views/manage/page/view.php +++ b/common/modules/pages/views/manage/page/view.php @@ -7,9 +7,16 @@ use yii\widgets\DetailView; /* @var $page \common\modules\pages\entities\Page */ /* @var $history \common\modules\pages\entities\Page[] */ -$this->title = $page->title; +$this->title = $page->translation->title; $this->params['breadcrumbs'][] = ['label' => Yii::t('pages', 'Pages'), 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; + +$css = <<registerCss($css); ?>
    @@ -35,48 +42,30 @@ $this->params['breadcrumbs'][] = $this->title; 'model' => $page, 'attributes' => [ 'id', - 'title', 'slug', ], ]) ?>
    -
    -
    -
    - formatter->asHtml($page->content, [ - 'Attr.AllowedRel' => array('nofollow'), - 'HTML.SafeObject' => true, - 'Output.FlashCompat' => true, - 'HTML.SafeIframe' => true, - 'URI.SafeIframeRegexp'=>'%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/)%', - ]) ?> -
    -
    - -
    -
    -
    - $page, - 'attributes' => [ - [ - 'attribute' => 'meta.title', - 'label' => Yii::t('main', 'Title'), - ], - [ - 'attribute' => 'meta.description', - 'label' => Yii::t('main', 'Description'), - ], - [ - 'attribute' => 'meta.keywords', - 'label' => Yii::t('main', 'Keywords'), - ], - ], - ]) ?> -
    -
    + params['translatedLanguages'] as $language => $language_name) { + $items[] = [ + 'label' => $language_name, + 'content' => $this->render('_view_tab', [ + 'page' => $page, + 'language' => $language, + ]), + ]; + } + ?> + +
    diff --git a/common/modules/pages/widgets/MenuItemCreatorWidget.php b/common/modules/pages/widgets/MenuItemCreatorWidget.php index a8bcebc..a0d1c90 100644 --- a/common/modules/pages/widgets/MenuItemCreatorWidget.php +++ b/common/modules/pages/widgets/MenuItemCreatorWidget.php @@ -16,9 +16,9 @@ class MenuItemCreatorWidget extends Widget public function run() { $form = new MenuItemForm(); - $form->module = \Yii::t('pages', 'Pages'); - $form->name = \Yii::t('pages', 'Pages'); - $form->title_attr = \Yii::t('pages', 'Pages'); + $form->module = 'pages'; + $form->name = 'pages'; + $form->title_attr = 'pages'; $form->menu_id = $this->menu_id; $form->url = '/pages/page/index'; diff --git a/composer.json b/composer.json index bd4a8e8..2b00c31 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,9 @@ "paulzi/yii2-adjacency-list" : "^2.1", "yiisoft/yii2-jui": "^2.0", "zertex/yii2-elfinder": "^1.2", - "zertex/yii2-ckeditor": "^1.0" + "zertex/yii2-ckeditor": "^1.0", + "omgdef/yii2-multilingual-behavior": "^2.1", + "kartik-v/yii2-detail-view": "@dev" }, "require-dev": { "yiisoft/yii2-debug": "~2.0.0", diff --git a/console/migrations/m180824_081717_create_menu_lng_table.php b/console/migrations/m180824_081717_create_menu_lng_table.php new file mode 100644 index 0000000..87873b3 --- /dev/null +++ b/console/migrations/m180824_081717_create_menu_lng_table.php @@ -0,0 +1,42 @@ +db->driverName === 'mysql') { + $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB'; + } + + $this->createTable('{{%menu_lng}}', [ + 'id' => $this->primaryKey(), + 'menu_id' => $this->integer()->notNull(), + 'language' => $this->string(6)->notNull(), + 'name' => $this->string(255)->notNull(), + ], $tableOptions); + + $this->createIndex('idx_menu_lng_language', '{{%menu_lng}}', 'language'); + $this->createIndex('idx_menu_lng_menu_id', '{{%menu_lng}}', 'menu_id'); + $this->addForeignKey('frg_menu_lng_menu_menu_id_id', '{{%menu_lng}}', 'menu_id', '{{%menu}}', 'id', 'CASCADE', 'CASCADE'); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropForeignKey('frg_menu_lng_menu_menu_id_id', '{{%menu_lng}}'); + $this->dropColumn('idx_menu_lng_menu_id', '{{%menu_lng}}'); + $this->dropColumn('idx_menu_lng_language', '{{%menu_lng}}'); + $this->dropTable('{{%menu_lng}}'); + } +} diff --git a/console/migrations/m180826_171901_remove_menu_name_field.php b/console/migrations/m180826_171901_remove_menu_name_field.php new file mode 100644 index 0000000..b497f75 --- /dev/null +++ b/console/migrations/m180826_171901_remove_menu_name_field.php @@ -0,0 +1,25 @@ +dropColumn('{{%menu}}', 'name'); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->addColumn('{{%menu}}', 'name', $this->string(255)->notNull()); + } +} diff --git a/console/migrations/m180826_204039_create_menu_items_lng_lng_table.php b/console/migrations/m180826_204039_create_menu_items_lng_lng_table.php new file mode 100644 index 0000000..4034070 --- /dev/null +++ b/console/migrations/m180826_204039_create_menu_items_lng_lng_table.php @@ -0,0 +1,43 @@ +db->driverName === 'mysql') { + $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB'; + } + + $this->createTable('{{%menu_items_lng}}', [ + 'id' => $this->primaryKey(), + 'menu_item_id' => $this->integer()->notNull(), + 'language' => $this->string(6)->notNull(), + 'name' => $this->string(255)->notNull(), + 'title_attr' => $this->string(255), + ], $tableOptions); + + $this->createIndex('idx_menu_items_lng_language', '{{%menu_items_lng}}', 'language'); + $this->createIndex('idx_menu_items_lng_page_id', '{{%menu_items_lng}}', 'menu_item_id'); + $this->addForeignKey('frg_menu_items_lng_menu_items_menu_item_id_id', '{{%menu_items_lng}}', 'menu_item_id', '{{%menu_items}}', 'id', 'CASCADE', 'CASCADE'); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropForeignKey('frg_menu_items_lng_menu_items_menu_item_id_id', '{{%menu_items_lng}}'); + $this->dropColumn('idx_menu_items_lng_page_id', '{{%menu_items_lng}}'); + $this->dropColumn('idx_menu_items_lng_language', '{{%menu_items_lng}}'); + $this->dropTable('{{%menu_items_lng}}'); + } +} diff --git a/console/migrations/m180826_215830_remove_menu_items_name_title_attr_fields.php b/console/migrations/m180826_215830_remove_menu_items_name_title_attr_fields.php new file mode 100644 index 0000000..b07e33d --- /dev/null +++ b/console/migrations/m180826_215830_remove_menu_items_name_title_attr_fields.php @@ -0,0 +1,27 @@ +dropColumn('{{%menu_items}}', 'name'); + $this->dropColumn('{{%menu_items}}', 'title_attr'); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->addColumn('{{%menu_items}}', 'name', $this->string(255)->notNull()); + $this->addColumn('{{%menu_items}}', 'title_attr', $this->string(255)); + } +} diff --git a/console/migrations/m180827_194913_set_core_tables_unicode_oollate.php b/console/migrations/m180827_194913_set_core_tables_unicode_oollate.php new file mode 100644 index 0000000..3c6d093 --- /dev/null +++ b/console/migrations/m180827_194913_set_core_tables_unicode_oollate.php @@ -0,0 +1,33 @@ +getDb(); + $db->createCommand('SET FOREIGN_KEY_CHECKS=0;')->execute(); + + foreach ($tables as $table) { + $db->createCommand( "ALTER TABLE `$table` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci" )->execute(); + } + + $db->createCommand('SET FOREIGN_KEY_CHECKS=1;')->execute(); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + true; + } +} diff --git a/core/behaviors/LanguageBehavior.php b/core/behaviors/LanguageBehavior.php new file mode 100644 index 0000000..e62b0a8 --- /dev/null +++ b/core/behaviors/LanguageBehavior.php @@ -0,0 +1,351 @@ + 'afterFind', + ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate', + ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert', + ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete', + //ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', + ]; + } + + 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')) { + $translations = $this->indexByLanguage($owner->getRelatedRecords()['translations']); + $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; + foreach ($this->translatedLanguages as $language) { + $isDefaultLanguage = $language == $this->defaultLanguage; + if (!isset($translations[$language])) { + /** @var ActiveRecord $translation */ + $translation = new $this->virtualClassName; + $translation->{$this->languageField} = $language; + $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 */ + return $this->owner->hasMany($this->virtualClassName, [$this->relativeField => $this->ownerPrimaryKey]); + } + + public function getTranslation($language = null) + { + $language = $language ?: \Yii::$app->language; + // if translate exists + $translate = $this->virtualClassName::find() + ->andWhere([$this->relativeField => $this->owner->id]) + ->andWhere([$this->languageField => $language]) + ->one(); + + $language = $translate ? $language : \Yii::$app->params['defaultLanguage']; + + return $this->owner->hasOne($this->virtualClassName, [$this->relativeField => $this->ownerPrimaryKey]) + ->where([$this->languageField => $language]); + } + + public function findTranslation($language = null) + { + $language = $language ?: \Yii::$app->language; + //$class = call_user_func(array($this->virtualClassName, 'getInstance')); + 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; + } + +} diff --git a/core/components/LanguageDynamicModel.php b/core/components/LanguageDynamicModel.php new file mode 100644 index 0000000..68efba4 --- /dev/null +++ b/core/components/LanguageDynamicModel.php @@ -0,0 +1,64 @@ +getPublicAttributes(), $this->prepareLanguageAttributes()), $config ); + } + + private function prepareLanguageAttributes() + { + $language_attributes = []; + $labels = $this->attributeLabels(); + $hints = $this->attributeHints(); + foreach ($this->rules() as $rule) { + $attributes = is_array($rule[0]) ? $rule[0] : [$rule[0]]; + $type = $rule[1]; + if ($type == 'string') { + foreach (Yii::$app->params['translatedLanguages'] as $language => $language_name) { + $rule_attributes = []; + foreach ($attributes as $attribute) { + // add attribute + $language_attributes[] = $attribute . '_' . $language; + $this->new_labels[$attribute . '_' . $language] = isset($labels[$attribute]) ? $labels[$attribute] : null; + $this->new_hints[$attribute . '_' . $language] = isset($hints[$attribute]) ? $hints[$attribute] : null; + $rule_attributes[] = $attribute . '_' . $language; + } + // add rule + if (!empty($rule_attributes)) { + $this->new_rules[] = [ $rule_attributes, $rule[1] ]; + } + } + } + } + return $language_attributes; + } + + public function attributeLabels(){ + return $this->new_labels; + } + + public function rules() { + return $this->new_rules; + } + + public function getPublicAttributes () { + return call_user_func('get_object_vars', $this); + } + +} diff --git a/core/components/LanguageTranslateQuery.php b/core/components/LanguageTranslateQuery.php new file mode 100644 index 0000000..8978637 --- /dev/null +++ b/core/components/LanguageTranslateQuery.php @@ -0,0 +1,15 @@ +language; + if (!isset($this->with['translations'])) { + $this->with(['translation' => function ($query) use ($language, $abridge) { + /** @var ActiveQuery $query */ + $query->where([$this->languageField => $abridge ? substr($language, 0, 2) : $language]); + }]); + } + return $this; + } + /** + * Scope for querying by all languages + * @return $this + */ + public function multilingual() + { + if (isset($this->with['translation'])) { + unset($this->with['translation']); + } + $this->with('translations'); + return $this; + } +} \ No newline at end of file diff --git a/core/components/TestForm.php b/core/components/TestForm.php new file mode 100644 index 0000000..63b04de --- /dev/null +++ b/core/components/TestForm.php @@ -0,0 +1,39 @@ + 50], + ['content', 'string'], + ['number', 'integer'], + ] + ); + } + + public function attributeLabels() { + return array_merge( + parent::attributeLabels(), + [ + 'name' => \Yii::t('main', 'Name'), + 'content' => \Yii::t('main', 'Key'), + 'number' => \Yii::t('main', 'Value'), + ] + ); + } + +} \ No newline at end of file diff --git a/core/entities/Meta.php b/core/entities/Meta.php index f529925..1f39a25 100644 --- a/core/entities/Meta.php +++ b/core/entities/Meta.php @@ -2,6 +2,8 @@ namespace core\entities; +use yii\helpers\Json; + class Meta { public $title; @@ -14,4 +16,15 @@ class Meta $this->description = $description; $this->keywords = $keywords; } + + public static function createMeta($json): Meta + { + $meta = new Meta(null, null, null); + $meta_data = Json::decode($json); + $meta->title = isset($meta_data->title) ? $meta_data->title : ''; + $meta->description = isset($meta_data->description) ? $meta_data->description : ''; + $meta->keywords = isset($meta_data->keywords) ? $meta_data->keywords : ''; + return $meta; + } + } \ No newline at end of file diff --git a/core/entities/menu/Menu.php b/core/entities/menu/Menu.php index 711d6de..1d28eb3 100644 --- a/core/entities/menu/Menu.php +++ b/core/entities/menu/Menu.php @@ -6,6 +6,7 @@ namespace core\entities\menu; +use core\behaviors\LanguageBehavior; use yii\db\ActiveRecord; /** @@ -13,23 +14,30 @@ use yii\db\ActiveRecord; * @package core\entities * * @property integer $id - * @property string $name + * + * @method ActiveRecord findTranslation(string $language) + * @method void saveTranslations($translations) + * + * @property ActiveRecord[] translations + * @property ActiveRecord[] translation * * @property MenuItem[] $items */ class Menu extends ActiveRecord { - public static function create($name): self + public $_form; + + public static function create($form): self { $menu = new static(); - $menu->name = $name; + $menu->_form = $form; return $menu; } - public function edit($name): void + public function edit($form): void { - $this->name = $name; + $this->_form = $form; } public static function tableName(): string @@ -41,4 +49,24 @@ class Menu extends ActiveRecord { return $this->hasMany(MenuItem::class, ['menu_id' => 'id'])->orderBy(['sort' => SORT_ASC]); } -} \ No newline at end of file + + public function behaviors() + { + return [ + [ + 'class' => LanguageBehavior::class, + 'virtualClassName' => 'MenuVirtualTranslate', + 'translatedLanguages' => \Yii::$app->params['translatedLanguages'], + 'relativeField' => 'menu_id', + 'tableName' => "{{%menu_lng}}", + 'attributes' => ['name'], + 'defaultLanguage' => \Yii::$app->params['defaultLanguage'], + ], + ]; + } + + /*public static function find() + { + return new LanguageTranslateQuery(get_called_class()); + }*/ +} diff --git a/core/entities/menu/MenuItem.php b/core/entities/menu/MenuItem.php index f9cc849..5fc70b2 100644 --- a/core/entities/menu/MenuItem.php +++ b/core/entities/menu/MenuItem.php @@ -6,6 +6,7 @@ namespace core\entities\menu; +use core\behaviors\LanguageBehavior; use yii\db\ActiveRecord; use yii\helpers\Json; use yii\helpers\Url; @@ -27,39 +28,46 @@ use yii\helpers\Url; * @property string $url_params * @property integer $sort * + * @method ActiveRecord findTranslation(string $language) + * @method void saveTranslations($translations) + * + * @property ActiveRecord[] translations + * @property ActiveRecord[] translation + * * @property MenuItem $parent * @property MenuItem[] $children + * @property Menu $menu */ class MenuItem extends ActiveRecord { - public static function create($menu_id, $parent_id, $name, $title_attr, $target, $css, $style, $module, $url, $url_params): self + public $_form; + + public static function create($form, $menu_id, $parent_id, $target, $css, $style, $module, $url, $url_params): self { $menu = new static(); $menu->menu_id = $menu_id; $menu->parent_id = $parent_id; - $menu->name = $name; - $menu->title_attr = $title_attr; $menu->target = $target; $menu->css = $css; $menu->style = $style; $menu->module = $module; $menu->url = $url; $menu->url_params = $url_params; + $menu->_form = $form; return $menu; } - public function edit($menu_id, $parent_id, $name, $title_attr, $target, $css, $style, $module, $url, $url_params): void + public function edit($form, $menu_id, $parent_id, $target, $css, $style, $module, $url, $url_params): void { $this->menu_id = $menu_id; $this->parent_id = $parent_id; - $this->name = $name; - $this->title_attr = $title_attr; $this->target = $target; $this->css = $css; $this->style = $style; $this->module = $module; $this->url = $url; + $this->_form = $form; $this->url_params = $url_params; } @@ -124,4 +132,24 @@ class MenuItem extends ActiveRecord { return $this->hasOne(MenuItem::class, ['id' => 'parent_id']); } + + public function getMenu() + { + return $this->hasOne(Menu::class, ['id' => 'menu_id']); + } + + public function behaviors() + { + return [ + [ + 'class' => LanguageBehavior::class, + 'virtualClassName' => 'MenuItemVirtualTranslate', + 'translatedLanguages' => \Yii::$app->params['translatedLanguages'], + 'relativeField' => 'menu_item_id', + 'tableName' => "{{%menu_items_lng}}", + 'attributes' => ['name', 'title_attr'], + 'defaultLanguage' => \Yii::$app->params['defaultLanguage'], + ], + ]; + } } \ No newline at end of file diff --git a/core/forms/CompositeLanguageForm.php b/core/forms/CompositeLanguageForm.php new file mode 100644 index 0000000..9dce07e --- /dev/null +++ b/core/forms/CompositeLanguageForm.php @@ -0,0 +1,110 @@ +forms as $name => $form) { + if (is_array($form)) { + $success = Model::loadMultiple($form, $data, $formName === null ? null : $name) && $success; + } else { + $success = $form->load($data, $formName !== '' ? null : $name) && $success; + } + } + return $success; + } + + public function validate($attributeNames = null, $clearErrors = true): bool + { + $parentNames = $attributeNames !== null ? array_filter((array)$attributeNames, 'is_string') : null; + $success = parent::validate($parentNames, $clearErrors); + foreach ($this->forms as $name => $form) { + if (is_array($form)) { + $success = Model::validateMultiple($form) && $success; + } else { + $innerNames = $attributeNames !== null ? ArrayHelper::getValue($attributeNames, $name) : null; + $success = $form->validate($innerNames ?: null, $clearErrors) && $success; + } + } + return $success; + } + + public function hasErrors($attribute = null): bool + { + if ($attribute !== null) { + return parent::hasErrors($attribute); + } + if (parent::hasErrors($attribute)) { + return true; + } + foreach ($this->forms as $name => $form) { + if (is_array($form)) { + foreach ($form as $i => $item) { + if ($item->hasErrors()) { + return true; + } + } + } else { + if ($form->hasErrors()) { + return true; + } + } + } + return false; + } + + public function getFirstErrors(): array + { + $errors = parent::getFirstErrors(); + foreach ($this->forms as $name => $form) { + if (is_array($form)) { + foreach ($form as $i => $item) { + foreach ($item->getFirstErrors() as $attribute => $error) { + $errors[$name . '.' . $i . '.' . $attribute] = $error; + } + } + } else { + foreach ($form->getFirstErrors() as $attribute => $error) { + $errors[$name . '.' . $attribute] = $error; + } + } + } + return $errors; + } + + public function __get($name) + { + if (isset($this->forms[$name])) { + return $this->forms[$name]; + } + return parent::__get($name); + } + + public function __set($name, $value) + { + if (in_array($name, $this->internalForms(), true)) { + $this->forms[$name] = $value; + } else { + parent::__set($name, $value); + } + } + + public function __isset($name) + { + return isset($this->forms[$name]) || parent::__isset($name); + } +} \ No newline at end of file diff --git a/core/forms/MetaForm-org.php b/core/forms/MetaForm-org.php new file mode 100644 index 0000000..ce9cbed --- /dev/null +++ b/core/forms/MetaForm-org.php @@ -0,0 +1,40 @@ +title = $meta->title; + $this->description = $meta->description; + $this->keywords = $meta->keywords; + } + parent::__construct($config); + } + + public function rules(): array + { + return [ + [['title'], 'string', 'max' => 255], + [['description', 'keywords'], 'string'], + ]; + } + + public function attributeLabels() { + return [ + 'title' => Yii::t('main', 'Title'), + 'description' => Yii::t('main', 'Description'), + 'keywords' => Yii::t('main', 'Keywords'), + ]; + } +} \ No newline at end of file diff --git a/core/forms/MetaForm.php b/core/forms/MetaForm.php index 4927246..5e686a7 100644 --- a/core/forms/MetaForm.php +++ b/core/forms/MetaForm.php @@ -2,39 +2,56 @@ namespace core\forms; +use core\components\LanguageDynamicModel; use core\entities\Meta; -use yii\base\Model; use Yii; -class MetaForm extends Model +class MetaForm extends LanguageDynamicModel { public $title; public $description; public $keywords; + private $_meta; + public function __construct(Meta $meta = null, $config = []) { if ($meta) { $this->title = $meta->title; $this->description = $meta->description; $this->keywords = $meta->keywords; + $this->_meta = $meta; } parent::__construct($config); + // fill translate values + /*if ($meta) { + foreach ( $meta->translations as $translate ) { + $this->{'title' . '_' . $translate->language} = $translate->title; + $this->{'description' . '_' . $translate->language} = $translate->description; + $this->{'keywords' . '_' . $translate->language} = $translate->keywords; + }; + };*/ } public function rules(): array { - return [ - [['title'], 'string', 'max' => 255], - [['description', 'keywords'], 'string'], - ]; + return array_merge( + parent::rules(), + [ + [['title'], 'string', 'max' => 255], + [['description', 'keywords'], 'string'], + ] + ); } public function attributeLabels() { - return [ - 'title' => Yii::t('main', 'Title'), - 'description' => Yii::t('main', 'Description'), - 'keywords' => Yii::t('main', 'Keywords'), - ]; + return array_merge( + parent::attributeLabels(), + [ + 'title' => Yii::t('main', 'Title'), + 'description' => Yii::t('main', 'Description'), + 'keywords' => Yii::t('main', 'Keywords'), + ] + ); } } \ No newline at end of file diff --git a/core/forms/menu/MenuForm.php b/core/forms/menu/MenuForm.php index 1342aaa..d8c7383 100644 --- a/core/forms/menu/MenuForm.php +++ b/core/forms/menu/MenuForm.php @@ -2,36 +2,51 @@ namespace core\forms\menu; +use core\components\LanguageDynamicModel; use core\entities\menu\Menu; -use yii\base\Model; use Yii; -class MenuForm extends Model +class MenuForm extends LanguageDynamicModel { public $name; private $_menu; - public function __construct(Menu $menu = null, $config = []) - { - if ($menu) { - $this->name = $menu->name; - $this->_menu = $menu; - } - parent::__construct($config); + public function __construct( Menu $menu = null, array $attributes = [], array $config = [] ) { + if ($menu) { + $this->_menu = $menu; + } + parent::__construct( $attributes, $config ); + // fill translate values + if ($menu) { + foreach ( $menu->translations as $translate ) { + if ($translate->language == Yii::$app->params['backendDefaultLanguage']) { + $this->name = $translate->name; + } + else { + $this->{'name' . '_' . $translate->language} = $translate->name; + } + }; + }; } public function rules(): array { - return [ - [['name'], 'required'], - [['name'], 'string', 'max' => 255], - ]; + return array_merge( + parent::rules(), + [ + [['name'], 'required'], + [['name'], 'string', 'max' => 255], + ] + ); } public function attributeLabels() { - return [ - 'name' => Yii::t('main', 'Name'), - ]; + return array_merge( + parent::attributeLabels(), + [ + 'name' => Yii::t('main', 'Name'), + ] + ); } } diff --git a/core/forms/menu/MenuItemForm.php b/core/forms/menu/MenuItemForm.php index 1244525..eb24989 100644 --- a/core/forms/menu/MenuItemForm.php +++ b/core/forms/menu/MenuItemForm.php @@ -2,11 +2,11 @@ namespace core\forms\menu; +use core\components\LanguageDynamicModel; use core\entities\menu\MenuItem; -use yii\base\Model; use Yii; -class MenuItemForm extends Model +class MenuItemForm extends LanguageDynamicModel { public $menu_id; public $parent_id; @@ -26,8 +26,6 @@ class MenuItemForm extends Model if ($menu) { $this->menu_id = $menu->menu_id; $this->parent_id = $menu->parent_id; - $this->name = $menu->name; - $this->title_attr = $menu->title_attr; $this->target = $menu->target; $this->css = $menu->css; $this->style = $menu->style; @@ -38,31 +36,49 @@ class MenuItemForm extends Model $this->_menu = $menu; } parent::__construct($config); + if ($menu) { + foreach ( $menu->translations as $translate ) { + if ($translate->language == Yii::$app->params['backendDefaultLanguage']) { + $this->name = $translate->name; + $this->title_attr = $translate->title_attr; + } + else { + $this->{'name' . '_' . $translate->language} = $translate->name; + $this->{'title_attr' . '_' . $translate->language} = $translate->title_attr; + } + }; + } } public function rules(): array { - return [ - [['name', 'menu_id', 'url'], 'required'], - [['name', 'title_attr', 'css', 'style', 'module', 'url'], 'string', 'max' => 255], - [['target'], 'string', 'max' => 20], - ['url_params', 'string'], - [['parent_id', 'menu_id'], 'integer'], - ]; + return array_merge( + parent::rules(), + [ + [['name', 'menu_id'], 'required'], + [['name', 'title_attr', 'css', 'style', 'module', 'url'], 'string', 'max' => 255], + [['target'], 'string', 'max' => 20], + ['url_params', 'string'], + [['parent_id', 'menu_id'], 'integer'], + ] + ); } public function attributeLabels() { - return [ - 'menu_id' => Yii::t('main', 'Menu'), - 'parent id' => Yii::t('main', 'Parent menu item'), - 'name' => Yii::t('main', 'Name'), - 'title_attr' => Yii::t('main', 'Title attribute'), - 'target' => Yii::t('main', 'Target'), - 'css' => Yii::t('main', 'CSS Classes'), - 'style' => Yii::t('main', 'CSS Style'), - 'module' => Yii::t('main', 'Module'), - 'url' => Yii::t('main', 'Url'), - 'url_params' => Yii::t('main', 'Url Params'), - ]; + return array_merge( + parent::attributeLabels(), + [ + 'menu_id' => Yii::t('menu', 'Menu'), + 'parent id' => Yii::t('menu', 'Parent menu item'), + 'name' => Yii::t('menu', 'Name'), + 'title_attr' => Yii::t('menu', 'Title attribute'), + 'target' => Yii::t('menu', 'Target'), + 'css' => Yii::t('menu', 'CSS Classes'), + 'style' => Yii::t('menu', 'CSS Style'), + 'module' => Yii::t('menu', 'Module'), + 'url' => Yii::t('menu', 'Url'), + 'url_params' => Yii::t('menu', 'Url Params'), + ] + ); } } diff --git a/core/helpers/LanguageHelper.php b/core/helpers/LanguageHelper.php new file mode 100644 index 0000000..b19d42b --- /dev/null +++ b/core/helpers/LanguageHelper.php @@ -0,0 +1,119 @@ +params['translatedLanguages']) > 1; + } + + public static function suffixList() + { + $list = array(); + $enabled = self::enabled(); + + foreach (Yii::$app->params['translatedLanguages'] as $lang => $name) { + if ($lang === Yii::$app->params['defaultLanguage']) { + $suffix = ''; + $list[$suffix] = $enabled ? $name : ''; + } else { + $suffix = '_' . $lang; + $list[$suffix] = $name; + } + } + + return $list; + } + + public static function isLangExists($url): bool + { + $index = self::_getLangIndex(); + $domains = explode('/', ltrim($url, '/')); + return in_array($domains[$index], array_keys(Yii::$app->params['translatedLanguages'])); + } + + public static function setLanguage($url) + { + $index = self::_getLangIndex(); + $domains = explode('/', ltrim($url, '/')); + $isLangExists = in_array($domains[$index], array_keys(Yii::$app->params['translatedLanguages'])); + if ($isLangExists) { + Yii::$app->language = $domains[$index]; + } + else { + Yii::$app->language = Yii::$app->params['defaultLanguage']; + } + } + + public static function processLangInUrl($url): string + { + if (self::enabled()) { + $index = self::_getLangIndex(); + $domains = explode('/', ltrim($url, '/')); + $isLangExists = in_array($domains[$index], array_keys(Yii::$app->params['translatedLanguages'])); + $isDefaultLang = $domains[$index] == Yii::$app->params['defaultLanguage']; + + if ($isLangExists && !$isDefaultLang) { + array_splice($domains, $index, 1); + } + $url = '/' . implode('/', $domains); + } + return $url; + } + + public static function addLangToUrl($url, $language = null): string + { + if (self::enabled()) { + $index = self::_getLangIndex(); + $domains = explode('/', ltrim($url, '/')); + $isHasLang = in_array($language ?: $domains[$index], array_keys(Yii::$app->params['translatedLanguages'])); + $isDefaultLang = $language ? $language == Yii::$app->params['defaultLanguage'] : Yii::$app->language == Yii::$app->params['defaultLanguage']; + + if ($isHasLang && $isDefaultLang) { + array_splice($domains, $index, 1); + } + + if (!$isHasLang && !$isDefaultLang) { + array_splice($domains, $index, 0, Yii::$app->language); + } + + $domains = array_filter($domains); + $url = '/' . implode('/', $domains); + } + return $url; + } + + public static function getName($language) + { + return isset(Yii::$app->params['translatedLanguages'][$language]) ? Yii::$app->params['translatedLanguages'][$language] : $language; + } + + public static function getBackendName($language) + { + return isset(Yii::$app->params['backendTranslatedLanguages'][$language]) ? Yii::$app->params['backendTranslatedLanguages'][$language] : Yii::$app->params['backendTranslatedLanguages'][Yii::$app->params['backendDefaultLanguage']]; + } + + private static function _getLangIndex(): int + { + $index = 0; + $baseUrl = ltrim(Yii::$app->request->baseUrl, '/'); + + if (strlen($baseUrl)) { + $baseUrlChunks = explode('/', $baseUrl); + if (count($baseUrlChunks) > 0) { + $index = count( $baseUrlChunks ); + } + } + return $index; + } +} diff --git a/core/repositories/menu/MenuItemRepository.php b/core/repositories/menu/MenuItemRepository.php index a1d30d4..b487260 100644 --- a/core/repositories/menu/MenuItemRepository.php +++ b/core/repositories/menu/MenuItemRepository.php @@ -9,7 +9,8 @@ class MenuItemRepository { public function get($id): MenuItem { - if (!$item = MenuItem::findOne($id)) { + if (!$item = MenuItem::find()->with('translations')->andWhere(['id' => $id])->one()) { + //if (!$item = MenuItem::findOne($id)) { throw new NotFoundException('Menu is not found.'); } return $item; diff --git a/core/repositories/menu/MenuRepository.php b/core/repositories/menu/MenuRepository.php index 6b5e1fe..03eb61e 100644 --- a/core/repositories/menu/MenuRepository.php +++ b/core/repositories/menu/MenuRepository.php @@ -9,7 +9,8 @@ class MenuRepository { public function get($id): Menu { - if (!$menu = Menu::findOne($id)) { + //if (!$menu = Menu::findOne($id)) { + if (!$menu = Menu::find()->with('translations')->andWhere(['id' => $id])->one()) { throw new NotFoundException('Menu is not found.'); } return $menu; diff --git a/core/services/menu/MenuItemManageService.php b/core/services/menu/MenuItemManageService.php index 194c0e1..a86ed14 100644 --- a/core/services/menu/MenuItemManageService.php +++ b/core/services/menu/MenuItemManageService.php @@ -18,10 +18,9 @@ class MenuItemManageService public function create(MenuItemForm $form): MenuItem { $menu = MenuItem::create( + $form, $form->menu_id, $form->parent_id, - $form->name, - $form->title_attr, $form->target, $form->css, $form->style, @@ -37,10 +36,9 @@ class MenuItemManageService { $menu = $this->repository->get($id); $menu->edit( + $form, $form->menu_id, $form->parent_id, - $form->name, - $form->title_attr, $form->target, $form->css, $form->style, diff --git a/core/services/menu/MenuManageService.php b/core/services/menu/MenuManageService.php index b079f39..866bb32 100644 --- a/core/services/menu/MenuManageService.php +++ b/core/services/menu/MenuManageService.php @@ -18,7 +18,7 @@ class MenuManageService public function create(MenuForm $form): Menu { $menu = Menu::create( - $form->name + $form ); $this->repository->save($menu); return $menu; @@ -28,7 +28,7 @@ class MenuManageService { $menu = $this->repository->get($id); $menu->edit( - $form->name + $form ); $this->repository->save($menu); } diff --git a/core/services/user/UserManageService.php b/core/services/user/UserManageService.php index acac74e..d710103 100644 --- a/core/services/user/UserManageService.php +++ b/core/services/user/UserManageService.php @@ -68,6 +68,15 @@ class UserManageService }); } + public function setBackendLanguage($language): void + { + if (in_array($language, array_keys(\Yii::$app->params['backendTranslatedLanguages']))) { + $user = $this->repository->get(\Yii::$app->user->id); + $user->backend_language = $language; + $this->repository->save($user); + } + } + public function assignRole($id, $role): void { $user = $this->repository->get($id); diff --git a/core/widgets/menu/MenuWidget.php b/core/widgets/menu/MenuWidget.php index 7a56d05..847451d 100644 --- a/core/widgets/menu/MenuWidget.php +++ b/core/widgets/menu/MenuWidget.php @@ -15,6 +15,7 @@ class MenuWidget extends Widget public function run() { $menu = Menu::findOne($this->menu_id); + return $this->render('menu', [ 'menu' => $menu, ]); diff --git a/core/widgets/menu/views/menu.php b/core/widgets/menu/views/menu.php index 8025e30..caf8b8c 100644 --- a/core/widgets/menu/views/menu.php +++ b/core/widgets/menu/views/menu.php @@ -24,7 +24,7 @@ parent_id == 0): ?> - target ? 'target="'.$item->target.'"' : '' ?> href="getUrl() ?>" class="nav-link">name ?> + target ? 'target="'.$item->target.'"' : '' ?> href="getUrl() ?>" class="nav-link">translation->name ?> diff --git a/frontend/bootstrap/SetUp.php b/frontend/bootstrap/SetUp.php index 3b68101..f41ed32 100644 --- a/frontend/bootstrap/SetUp.php +++ b/frontend/bootstrap/SetUp.php @@ -3,8 +3,8 @@ namespace frontend\bootstrap; use core\entities\Settings; +use core\helpers\LanguageHelper; use yii\base\BootstrapInterface; -use yii\base\Theme; use yii\helpers\ArrayHelper; use yii\widgets\Breadcrumbs; @@ -36,5 +36,8 @@ class SetUp implements BootstrapInterface '<_c:[\w\-]+>/<_a:[\w-]+>' => '<_c>/<_a>', '<_c:[\w\-]+>//<_a:[\w\-]+>' => '<_c>/<_a>', ]); + + // redefine home url + \Yii::$app->homeUrl = LanguageHelper::addLangToUrl(\Yii::$app->homeUrl); } } \ No newline at end of file diff --git a/frontend/components/FrontendController.php b/frontend/components/FrontendController.php index ffaf311..3de08fc 100644 --- a/frontend/components/FrontendController.php +++ b/frontend/components/FrontendController.php @@ -7,6 +7,7 @@ namespace frontend\components; +use yii\web\Cookie; use yii\base\Theme; use yii\web\Controller; use Yii; @@ -16,8 +17,13 @@ class FrontendController extends Controller public function init() { parent::init(); - $theme = isset(Yii::$app->params['settings']['design']['theme']) ? Yii::$app->params['settings']['design']['theme'] : 'start'; + // language + $languages = ['ru', 'en']; + $language = Yii::$app->request->get('language'); + Yii::$app->language = $language && in_array($language, $languages) ? $language : Yii::$app->language; + // themes + $theme = isset(Yii::$app->params['settings']['design']['theme']) ? Yii::$app->params['settings']['design']['theme'] : 'start'; Yii::$app->view->theme = new Theme([ 'basePath' => '@webroot/themes/' . $theme, 'baseUrl' => '@web/themes/' . $theme, diff --git a/frontend/config/LanguageUrlManager.php b/frontend/config/LanguageUrlManager.php new file mode 100644 index 0000000..87a4390 --- /dev/null +++ b/frontend/config/LanguageUrlManager.php @@ -0,0 +1,39 @@ +request->getUrl()); + $langPrefix = Yii::$app->language . '/'; + $finalRules[$langPrefix] = ''; + + foreach ($this->rules as $rule => $path) { + if ( is_array($path) && isset($path['pattern']) && isset($path[0]) ) { + $finalRules[$langPrefix . ltrim($path['pattern'], '/')] = $path[0]; + } + else { + $finalRules[$langPrefix . ltrim($rule, '/')] = $path; + } + } + $this->rules = array_merge_recursive($finalRules, $this->rules); + return parent::init(); + } + + public function createUrl( $params ) + { + $url = parent::createUrl( $params ); + return LanguageHelper::addLangToUrl($url, isset($params['language']) ? $params['language'] : null); + } +} \ No newline at end of file diff --git a/frontend/config/urlManager.php b/frontend/config/urlManager.php index b084051..c8f47ec 100644 --- a/frontend/config/urlManager.php +++ b/frontend/config/urlManager.php @@ -3,7 +3,8 @@ /** @var array $params */ return [ - 'class' => 'yii\web\UrlManager', + //'class' => 'yii\web\UrlManager', + 'class' => \frontend\config\LanguageUrlManager::class, 'hostInfo' => $params['frontendHostInfo'], 'baseUrl' => '', //'suffix' => '/', @@ -17,6 +18,7 @@ return [ 'signup/<_a:[\w-]+>' => 'auth/signup/<_a>', '<_a:login|logout>' => 'auth/auth/<_a>', + //['class' => \frontend\components\LanguageUrlRule::class], //['pattern' => 'yandex-market', 'route' => 'market/index', 'suffix' => '.xml'], //['pattern' => 'sitemap', 'route' => 'sitemap/index', 'suffix' => '.xml'], diff --git a/frontend/controllers/ContactController.php b/frontend/controllers/ContactController.php index 9a27b76..4a14f1c 100644 --- a/frontend/controllers/ContactController.php +++ b/frontend/controllers/ContactController.php @@ -8,7 +8,7 @@ use core\forms\ContactForm; class ContactController extends FrontendController { - public $layout = 'contacts'; + public $layout = 'blank'; private $service; diff --git a/frontend/web/themes/start/layouts/blank.php b/frontend/web/themes/start/layouts/blank.php new file mode 100644 index 0000000..0b4d900 --- /dev/null +++ b/frontend/web/themes/start/layouts/blank.php @@ -0,0 +1,28 @@ +beginContent('@frontend/web/themes/start/layouts/main.php') ?> + + +
    + + 'ul', + 'itemTemplate' => '' . "\n", + 'activeItemTemplate' => '' . "\n", + 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + ]) ?> + +
    + + +
    + + + +
    + +
    + + +
    + +endContent() ?> + diff --git a/frontend/web/themes/start/modules/pages/views/page/view.php b/frontend/web/themes/start/modules/pages/views/page/view.php index 679a6a1..c16ad28 100644 --- a/frontend/web/themes/start/modules/pages/views/page/view.php +++ b/frontend/web/themes/start/modules/pages/views/page/view.php @@ -7,21 +7,21 @@ use yii\helpers\Html; $this->title = $page->getSeoTitle(); -$this->registerMetaTag(['name' => 'description', 'content' => $page->meta->description]); -$this->registerMetaTag(['name' => 'keywords', 'content' => $page->meta->keywords]); +$this->registerMetaTag(['name' => 'description', 'content' => $page->translation->meta_description]); +$this->registerMetaTag(['name' => 'keywords', 'content' => $page->translation->meta_keywords]); foreach ($page->parents as $parent) { if (!$parent->isRoot()) { - $this->params['breadcrumbs'][] = ['label' => $parent->title, 'url' => ['view', 'id' => $parent->id]]; + $this->params['breadcrumbs'][] = ['label' => $parent->translation->title, 'url' => ['view', 'id' => $parent->id]]; } } -$this->params['breadcrumbs'][] = $page->title; +$this->params['breadcrumbs'][] = $page->translation->title; ?>
    -

    title) ?>

    +

    translation->title) ?>

    - content(Yii::$app->formatter->asHtml($page->content, [ + content(Yii::$app->formatter->asHtml($page->translation->content, [ 'Attr.AllowedRel' => array('nofollow'), 'HTML.SafeObject' => true, 'Output.FlashCompat' => true,