Browse Source

Multilanguage: pages, links, menu

LanguageBehavior, LanguageDynamicModel
Set all tables unicode collate
Remove Meta object from pages
UrlManager fromtend /en/... /ru/...
Backend top select language for control panel
master
Egorka 6 years ago
parent
commit
f2847ef5da
  1. 103
      backend/components/menu/widgets/views/_item.php
  2. 25
      backend/components/menu/widgets/views/_item_tab.php
  3. 10
      backend/components/menu/widgets/views/menu.php
  4. 41
      backend/controllers/MenuController.php
  5. 22
      backend/controllers/SiteController.php
  6. 5
      backend/messages/ru/menu.php
  7. 11
      backend/views/layouts/header.php
  8. 22
      backend/views/menu/_form.php
  9. 17
      backend/views/menu/_form_tab.php
  10. 4
      backend/views/menu/menu.php
  11. 2
      backend/views/menu/select_menu.php
  12. 4
      backend/views/menu/update.php
  13. 5
      backend/views/user/profile.php
  14. 3
      common/bootstrap/SetUp.php
  15. 33
      common/modules/blog/migrations/m180827_195932_set_blog_unicode_collate.php
  16. 2
      common/modules/links/widgets/MenuItemCreatorWidget.php
  17. 6
      common/modules/pages/PagesModule.php
  18. 28
      common/modules/pages/controllers/manage/PageController.php
  19. 43
      common/modules/pages/entities/Page.php
  20. 78
      common/modules/pages/forms/PageForm.php
  21. 13
      common/modules/pages/helpers/PageHelper.php
  22. 4
      common/modules/pages/messages/ru/pages.php
  23. 43
      common/modules/pages/migrations/m180824_202316_create_pages_lng_table.php
  24. 25
      common/modules/pages/migrations/m180825_155812_add_pages_lng_meta_json_field.php
  25. 35
      common/modules/pages/migrations/m180825_183752_add_pages_pages_lng_meta_fields.php
  26. 27
      common/modules/pages/migrations/m180826_083923_remove_pages_title_content_fields.php
  27. 33
      common/modules/pages/migrations/m180827_195748_set_pages_unicode_collate.php
  28. 3
      common/modules/pages/repositories/PageRepository.php
  29. 45
      common/modules/pages/services/PageManageService.php
  30. 16
      common/modules/pages/urls/PageMainUrlRule.php
  31. 24
      common/modules/pages/views/manage/page/_form.php
  32. 29
      common/modules/pages/views/manage/page/_form_tab.php
  33. 71
      common/modules/pages/views/manage/page/_view_tab.php
  34. 2
      common/modules/pages/views/manage/page/index.php
  35. 4
      common/modules/pages/views/manage/page/update.php
  36. 57
      common/modules/pages/views/manage/page/view.php
  37. 6
      common/modules/pages/widgets/MenuItemCreatorWidget.php
  38. 4
      composer.json
  39. 42
      console/migrations/m180824_081717_create_menu_lng_table.php
  40. 25
      console/migrations/m180826_171901_remove_menu_name_field.php
  41. 43
      console/migrations/m180826_204039_create_menu_items_lng_lng_table.php
  42. 27
      console/migrations/m180826_215830_remove_menu_items_name_title_attr_fields.php
  43. 33
      console/migrations/m180827_194913_set_core_tables_unicode_oollate.php
  44. 351
      core/behaviors/LanguageBehavior.php
  45. 64
      core/components/LanguageDynamicModel.php
  46. 15
      core/components/LanguageTranslateQuery.php
  47. 55
      core/components/LanguageTranslateTrait.php
  48. 39
      core/components/TestForm.php
  49. 13
      core/entities/Meta.php
  50. 38
      core/entities/menu/Menu.php
  51. 40
      core/entities/menu/MenuItem.php
  52. 110
      core/forms/CompositeLanguageForm.php
  53. 40
      core/forms/MetaForm-org.php
  54. 29
      core/forms/MetaForm.php
  55. 35
      core/forms/menu/MenuForm.php
  56. 54
      core/forms/menu/MenuItemForm.php
  57. 119
      core/helpers/LanguageHelper.php
  58. 3
      core/repositories/menu/MenuItemRepository.php
  59. 3
      core/repositories/menu/MenuRepository.php
  60. 6
      core/services/menu/MenuItemManageService.php
  61. 4
      core/services/menu/MenuManageService.php
  62. 9
      core/services/user/UserManageService.php
  63. 1
      core/widgets/menu/MenuWidget.php
  64. 2
      core/widgets/menu/views/menu.php
  65. 5
      frontend/bootstrap/SetUp.php
  66. 8
      frontend/components/FrontendController.php
  67. 39
      frontend/config/LanguageUrlManager.php
  68. 4
      frontend/config/urlManager.php
  69. 2
      frontend/controllers/ContactController.php
  70. 28
      frontend/web/themes/start/layouts/blank.php
  71. 12
      frontend/web/themes/start/modules/pages/views/page/view.php

103
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'];
<div class="panel-heading">
<h4 class="panel-title">
<div class="dd-handle dd3-handle"> </div>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse<?= $menu_item->id ?>"><?= $menu_item->name ?>
<i class="fa fa-angle-down pull-right" aria-hidden="true"></i> <span style="font-size: 14px; color: #999" class="pull-right"><?= $menu_item->module ?></span></a>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse<?= $menu_item->id ?>"><?= $menu_item->translation->name ?>
<i class="fa fa-angle-down pull-right" aria-hidden="true"></i> <span style="font-size: 14px; color: #999" class="pull-right"><?= Yii::t($menu_item->module, ucfirst($menu_item->module)) ?></span></a>
</h4>
</div>
<div id="collapse<?= $menu_item->id ?>" class="panel-collapse collapse">
<div class="panel-body">
<div class="row" style="font-weight: normal">
<div class="col-md-6">
<?= Html::label(Yii::t('menu','Item Name'), 'item-name', [
'style' => 'color: #555555',
]) ?>
<?= Html::input('text', 'item-name', $menu_item->name, [
'id' => 'item-name-' . $menu_item->id,
'class' => 'form-control',
]) ?>
</div>
<div class="col-md-6">
<?= Html::label(Yii::t('menu','Item Title Attribute'), 'item-title-attr', [
'style' => 'color: #555555',
]) ?>
<?= Html::input('text', 'item-title-attr', $menu_item->title_attr, [
'id' => 'item-title-attr-' . $menu_item->id,
'class' => 'form-control',
<?php $form = ActiveForm::begin([
'action' => ['/menu/save-item', 'id' => $menu_item->id],
]); ?>
<?php
$items = [];
foreach (Yii::$app->params['translatedLanguages'] as $language => $language_name) {
$items[] = [
'label' => $language_name,
'content' => $this->render('_item_tab', [
'form' => $form,
'model' => $model,
'language' => $language,
]),
];
}
?>
<div class="nav-tabs-custom">
<?= \yii\bootstrap\Tabs::widget([
'items' => $items
]) ?>
</div>
</div>
<div class="row" style="font-weight: normal; margin-top: 5px;">
<div class="row">
<div class="col-md-6">
<?= Html::label(Yii::t('menu','Style Attribute'), 'item-style', [
'style' => 'color: #555555',
]) ?>
<?= Html::input('text', 'item-style', $menu_item->style, [
'id' => 'item-style-' . $menu_item->id,
'class' => 'form-control',
<?= $form->field($model, 'target')->dropDownList([
'' => Yii::t('menu', 'Self Window'),
'_blank' => Yii::t('menu', 'Blank Window'),
]) ?>
<?= $form->field($model, 'style')->textInput(['maxlength' => true]) ?>
</div>
<div class="col-md-6">
<?= Html::label(Yii::t('menu','CSS'), 'item-css', [
'style' => 'color: #555555',
]) ?>
<?= Html::input('text', 'item-css', $menu_item->css, [
'id' => 'item-css-' . $menu_item->id,
'class' => 'form-control',
]) ?>
</div>
</div>
<div class="row" style="font-weight: normal; margin-top: 5px;">
<?= $form->field($model, 'css')->textInput(['maxlength' => true]) ?>
<?php if (!$menu_item->url_params): ?>
<div class="col-md-6">
<?= Html::label(Yii::t('menu','URL'), 'item-url', [
'style' => 'color: #555555',
]) ?>
<?= Html::input('text', 'item-url', $menu_item->url, [
'id' => 'item-url-' . $menu_item->id,
'class' => 'form-control',
]) ?>
</div>
<?php else: ?>
<?= Html::hiddenInput('item-url', $menu_item->url, [
'id' => 'item-url-' . $menu_item->id,
]) ?>
<?= $form->field($model, 'url')->textInput(['maxlength' => true]) ?>
<?php endif; ?>
<div class="col-md-6">
<?= Html::label(Yii::t('menu','Target'), 'item-target', [
'style' => 'color: #555555',
]) ?>
<?= Html::dropDownList('item-target', $menu_item->target, [
'' => Yii::t('menu', 'Self Window'),
'_blank' => Yii::t('menu', 'Blank Window'),
], [
'id' => 'item-target-' . $menu_item->id,
'class' => 'form-control',
]) ?>
</div>
</div>
<hr>
<div class="pull-right">
<?= Html::a(Yii::t('buttons', 'Delete'), '#', [
'class' => 'item-delete-button btn btn-sm btn-danger',
'data-id' => $menu_item->id,
]) ?>
<?= Html::a(Yii::t('buttons', 'Save'), '#', [
'class' => 'item-save-button btn btn-sm btn-success',
'data-id' => $menu_item->id,
<?= Html::submitButton(Yii::t('buttons', 'Save'), [
'class' => 'btn btn-success btn-sm'
]) ?>
</div>
<div style="clear: both"></div>
<?php ActiveForm::end(); ?>
</div>
</div>

25
backend/components/menu/widgets/views/_item_tab.php

@ -0,0 +1,25 @@
<?php
/**
* Created by Error202
* Date: 24.08.2018
*/
/**
* @var $this \yii\web\View
* @var $form \yii\widgets\ActiveForm
* @var $model \core\forms\menu\MenuItemForm
* @var $language string
*/
$postfix = $language == Yii::$app->params['defaultLanguage'] ? '' : '_' . $language;
?>
<div class="row">
<div class="col-md-6">
<?= $form->field($model, 'name' . $postfix)->textInput(['maxlength' => true]) ?>
</div>
<div class="col-md-6">
<?= $form->field($model, 'title_attr' . $postfix)->textInput(['maxlength' => true]) ?>
</div>
</div>

10
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 = '<ol class="dd-list">';
foreach ($items as $item) {
$menuItemForm = new MenuItemForm($item['item']);
$html.=Yii::$app->getView()->render( '_item', [
'item' => $item,
'model' => $menuItemForm,
] );
}
return $html . '</ol>';
}
$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 = <<<JS
$(".item-save-button").on('click', function(e) {
/*$(".item-save-button").on('click', function(e) {
e.preventDefault();
var id = $(this).data('id');
var name = $("#item-name-" + id).val();
@ -58,7 +62,7 @@ $js = <<<JS
else {
alert('{$name_empty_error}');
}
});
});*/
$(".item-delete-button").on('click', function(e) {
e.preventDefault();

41
backend/controllers/MenuController.php

@ -40,7 +40,11 @@ class MenuController extends Controller
'class' => 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()
{

22
backend/controllers/SiteController.php

@ -3,18 +3,28 @@ 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
*/
@ -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)

5
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',
];

11
backend/views/layouts/header.php

@ -197,14 +197,19 @@ use core\components\avatar_generator\AvatarGenerator;
<li class="dropdown messages-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<!-- <i class="fa fa-language"></i> -->
<span>Русский <i class="caret"></i></span>
<span><?= \core\helpers\LanguageHelper::getBackendName(Yii::$app->user->identity->user->backend_language) ?> <i class="caret"></i></span>
</a>
<ul class="dropdown-menu">
<li>
<!-- inner menu: contains the actual data -->
<ul class="menu">
<li><a href="#">Русский</a></li>
<li><a href="#">English</a></li>
<?php foreach (Yii::$app->params['backendTranslatedLanguages'] as $language => $language_name): ?>
<li>
<?= Html::a($language_name, ['/site/language', 'language' => $language], [
'data-method' => 'post'
]) ?>
</li>
<?php endforeach; ?>
</ul>
</li>
</ul>

22
backend/views/menu/_form.php

@ -14,7 +14,27 @@ use yii\widgets\ActiveForm;
<div class="box box-default">
<div class="box-body">
<?= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>
<?php
$items = [];
foreach (Yii::$app->params['translatedLanguages'] as $language => $language_name) {
$items[] = [
'label' => $language_name,
'content' => $this->render('_form_tab', [
'form' => $form,
'model' => $model,
'language' => $language,
]),
];
}
?>
<div class="nav-tabs-custom">
<?= \yii\bootstrap\Tabs::widget([
'items' => $items
]) ?>
</div>
</div>
</div>

17
backend/views/menu/_form_tab.php

@ -0,0 +1,17 @@
<?php
/**
* Created by Error202
* Date: 24.08.2018
*/
/**
* @var $this \yii\web\View
* @var $form \yii\widgets\ActiveForm
* @var $model \core\forms\menu\MenuForm
* @var $language string
*/
$postfix = $language == Yii::$app->params['defaultLanguage'] ? '' : '_' . $language;
?>
<?= $form->field($model, 'name' . $postfix)->textInput(['maxlength' => true]) ?>

4
backend/views/menu/menu.php

@ -17,7 +17,7 @@ use yii\helpers\Html;
*/
$this->title = Yii::t('menu', 'Menu');
$this->params['breadcrumbs'][] = Html::encode($menu->name);
$this->params['breadcrumbs'][] = Html::encode(isset($menu->translation) ? $menu->translation->name : Yii::t('menu', 'Not set'));
$this->params['breadcrumbs'][] = $this->title;
?>
@ -34,7 +34,7 @@ $this->params['breadcrumbs'][] = $this->title;
'id' => 'select_menu',
]); ?>
<?= $form->field($model, 'id')->dropDownList(ArrayHelper::map($menus, 'id', 'name'), [
<?= $form->field($model, 'id')->dropDownList($menus, [
//'prompt' => Yii::t('menu', 'Select menu...'),
'value' => $menu->id,
'onchange' => 'this.form.submit()',

2
backend/views/menu/select_menu.php

@ -31,7 +31,7 @@ $this->params['breadcrumbs'][] = $this->title;
'id' => 'select_menu',
]); ?>
<?= $form->field($model, 'id')->dropDownList(ArrayHelper::map($menus, 'id', 'name'), [
<?= $form->field($model, 'id')->dropDownList($menus, [
'prompt' => Yii::t('menu', 'Select menu...'),
'onchange' => 'this.form.submit()',
])->label(false) ?>

4
backend/views/menu/update.php

@ -4,9 +4,9 @@
/* @var $menu \core\entities\menu\Menu */
/* @var $model \core\forms\menu\MenuForm */
$this->title = Yii::t('menu', 'Update Menu: {name}', ['name' => $menu->name]);
$this->title = Yii::t('menu', 'Update Menu: {name}', ['name' => isset($menu->translation) ? $menu->translation->name : Yii::t('menu', 'Not set')]);
$this->params['breadcrumbs'][] = ['label' => Yii::t('menu', 'Menu'), 'url' => ['index']];
$this->params['breadcrumbs'][] = ['label' => $menu->name, 'url' => ['index', 'id' => $menu->id]];
$this->params['breadcrumbs'][] = ['label' => isset($menu->translation) ? $menu->translation->name : Yii::t('menu', 'Not set'), 'url' => ['index', 'id' => $menu->id]];
$this->params['breadcrumbs'][] = Yii::t('buttons', 'Editing');
?>
<div class="menu-update">

5
backend/views/user/profile.php

@ -43,9 +43,8 @@ $this->params['breadcrumbs'][] = Yii::t('user', 'Profile');
<?= $form->field($model, 'username')->textInput(['maxLength' => true]) ?>
<?= $form->field($model, 'email')->textInput(['maxLength' => true]) ?>
<?= $form->field($model, 'password')->passwordInput(['maxLength' => true]) ?>
<?= $form->field($model, 'backend_language')->dropDownList([
'ru' => 'Русский',
'en' => 'English',
<?= $form->field($model, 'backend_language')->dropDownList(Yii::$app->params['backendTranslatedLanguages'], [
'value' => Yii::$app->user->identity->user->backend_language ?: Yii::$app->params['backendDefaultLanguage'],
]) ?>
</div>
</div>

3
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

33
common/modules/blog/migrations/m180827_195932_set_blog_unicode_collate.php

@ -0,0 +1,33 @@
<?php
use yii\db\Migration;
/**
* Class m180827_195932_set_blog_unicode_collate
*/
class m180827_195932_set_blog_unicode_collate extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$tables = ['blog_tag_assignments'];
$db = Yii::$app->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;
}
}

2
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', [

6
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'],

28
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;

43
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'],
],
];
}

78
common/modules/pages/forms/PageForm.php

@ -2,50 +2,69 @@
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 [
return array_merge(
parent::rules(),
[
[['title'], 'required'],
[['parentId'], 'integer'],
[['title', 'slug'], 'string', 'max' => 255],
[['content'], 'string'],
[['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) {
@ -58,34 +77,39 @@ class PageForm extends CompositeForm
}
return $query;
}],
];
]
);
}
public function attributeLabels() {
return [
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 [
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'];
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 -'));
});
}
public function internalForms(): array
{
return ['meta'];
}
}

13
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;

4
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 -' => '- корневая страница -',
];

43
common/modules/pages/migrations/m180824_202316_create_pages_lng_table.php

@ -0,0 +1,43 @@
<?php
use yii\db\Migration;
/**
* Handles the creation of table `pages_lng`.
*/
class m180824_202316_create_pages_lng_table extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$tableOptions = null;
if ($this->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}}');
}
}

25
common/modules/pages/migrations/m180825_155812_add_pages_lng_meta_json_field.php

@ -0,0 +1,25 @@
<?php
use yii\db\Migration;
/**
* Class m180825_155812_add_pages_lng_meta_json_field
*/
class m180825_155812_add_pages_lng_meta_json_field extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->addColumn('{{%pages_lng}}', 'meta_json', $this->text());
}
/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->dropColumn('{{%pages_lng}}', 'meta_json');
}
}

35
common/modules/pages/migrations/m180825_183752_add_pages_pages_lng_meta_fields.php

@ -0,0 +1,35 @@
<?php
use yii\db\Migration;
/**
* Class m180825_183752_add_pages_pages_lng_meta_fields
*/
class m180825_183752_add_pages_pages_lng_meta_fields extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->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());
}
}

27
common/modules/pages/migrations/m180826_083923_remove_pages_title_content_fields.php

@ -0,0 +1,27 @@
<?php
use yii\db\Migration;
/**
* Class m180826_083923_remove_pages_title_content_fields
*/
class m180826_083923_remove_pages_title_content_fields extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->dropColumn('{{%pages}}', 'title');
$this->dropColumn('{{%pages}}', 'content');
}
/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->addColumn('{{%pages}}', 'title', $this->string(255)->notNull());
$this->addColumn('{{%pages}}', 'title', 'MEDIUMTEXT');
}
}

33
common/modules/pages/migrations/m180827_195748_set_pages_unicode_collate.php

@ -0,0 +1,33 @@
<?php
use yii\db\Migration;
/**
* Class m180827_195748_set_pages_unicode_collate
*/
class m180827_195748_set_pages_unicode_collate extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$tables = ['pages_lng'];
$db = Yii::$app->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;
}
}

3
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;

45
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]]);

16
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
{
@ -27,7 +29,10 @@ class PageMainUrlRule extends BaseObject implements UrlRuleInterface
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;
}

24
common/modules/pages/views/manage/page/_form.php

@ -36,12 +36,31 @@ $this->registerJs($js2);
<div class="box-header with-border"><?= Yii::t('pages', 'Common') ?></div>
<div class="box-body">
<?= $form->field($model, 'parentId')->dropDownList($model->parentsList()) ?>
<?= $form->field($model, 'title')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'slug')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'content')->widget(CKEditor::class) ?>
</div>
</div>
<?php
$items = [];
foreach (Yii::$app->params['translatedLanguages'] as $language => $language_name) {
$items[] = [
'label' => $language_name,
'content' => $this->render('_form_tab', [
'form' => $form,
'model' => $model,
'language' => $language,
]),
];
}
?>
<div class="nav-tabs-custom">
<?= \yii\bootstrap\Tabs::widget([
'items' => $items
]) ?>
</div>
<!--
<div class="box box-default">
<div class="box-header with-border"><?= Yii::t('pages', 'SEO') ?></div>
<div class="box-body">
@ -50,6 +69,7 @@ $this->registerJs($js2);
< ?= $form->field($model->meta, 'keywords')->textInput() ?>
</div>
</div>
-->
<div class="form-group">
<?= Html::submitButton(Yii::t('buttons', 'Save'), ['class' => 'btn btn-success']) ?>

29
common/modules/pages/views/manage/page/_form_tab.php

@ -0,0 +1,29 @@
<?php
/**
* Created by Error202
* Date: 24.08.2018
*/
use zertex\ckeditor\CKEditor;
/**
* @var $this \yii\web\View
* @var $form \yii\widgets\ActiveForm
* @var $model \common\modules\pages\forms\PageForm
* @var $language string
*/
$postfix = $language == Yii::$app->params['defaultLanguage'] ? '' : '_' . $language;
?>
<?= $form->field($model, 'title' . $postfix)->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'content' . $postfix)->widget(CKEditor::class) ?>
<div class="box box-default">
<div class="box-header with-border"><?= Yii::t('pages', 'SEO') ?></div>
<div class="box-body">
<?= $form->field($model, 'meta_title' . $postfix)->textInput() ?>
<?= $form->field($model, 'meta_description' . $postfix)->textarea(['rows' => 2]) ?>
<?= $form->field($model, 'meta_keywords' . $postfix)->textInput() ?>
</div>
</div>

71
common/modules/pages/views/manage/page/_view_tab.php

@ -0,0 +1,71 @@
<?php
/**
* Created by Error202
* Date: 25.08.2018
*/
use yii\widgets\DetailView;
use common\modules\pages\entities\Page;
/**
* @var $this \yii\web\View
* @var $page \common\modules\pages\entities\Page
* @var $language string
*/
?>
<?= DetailView::widget([
'model' => $page,
'attributes' => [
[
'label' => Yii::t('pages', 'Title'),
'value' => function(Page $entity) use ($language) {
return $entity->findTranslation($language)->title;
}
],
],
]) ?>
<div class="box">
<div class="box-header with-border"><?= Yii::t('pages', 'SEO') ?></div>
<div class="box-body">
<?= DetailView::widget([
'model' => $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;
}
],
],
]) ?>
</div>
</div>
<div class="box">
<div class="box-header with-border"><?= Yii::t('pages', 'Content') ?></div>
<div class="box-body">
<?= Yii::$app->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/)%',
]) ?>
</div>
</div>

2
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('&nbsp;&nbsp;', $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',
],

4
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');
?>
<div class="page-update">

57
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 = <<<CSS
.detail-view th {
width: 25%;
}
CSS;
$this->registerCss($css);
?>
<div class="user-view">
@ -35,48 +42,30 @@ $this->params['breadcrumbs'][] = $this->title;
'model' => $page,
'attributes' => [
'id',
'title',
'slug',
],
]) ?>
</div>
</div>
<div class="box">
<div class="box-header with-border"><?= Yii::t('pages', 'Content') ?></div>
<div class="box-body">
<?= Yii::$app->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/)%',
]) ?>
</div>
</div>
<?php
$items = [];
foreach (Yii::$app->params['translatedLanguages'] as $language => $language_name) {
$items[] = [
'label' => $language_name,
'content' => $this->render('_view_tab', [
'page' => $page,
'language' => $language,
]),
];
}
?>
<div class="box">
<div class="box-header with-border"><?= Yii::t('pages', 'SEO') ?></div>
<div class="box-body">
<?= DetailView::widget([
'model' => $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'),
],
],
<div class="nav-tabs-custom">
<?= \yii\bootstrap\Tabs::widget([
'items' => $items,
]) ?>
</div>
</div>
</div>

6
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';

4
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",

42
console/migrations/m180824_081717_create_menu_lng_table.php

@ -0,0 +1,42 @@
<?php
use yii\db\Migration;
/**
* Handles the creation of table `menu_lng`.
*/
class m180824_081717_create_menu_lng_table extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$tableOptions = null;
if ($this->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}}');
}
}

25
console/migrations/m180826_171901_remove_menu_name_field.php

@ -0,0 +1,25 @@
<?php
use yii\db\Migration;
/**
* Class m180826_171901_remove_menu_name_field
*/
class m180826_171901_remove_menu_name_field extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->dropColumn('{{%menu}}', 'name');
}
/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->addColumn('{{%menu}}', 'name', $this->string(255)->notNull());
}
}

43
console/migrations/m180826_204039_create_menu_items_lng_lng_table.php

@ -0,0 +1,43 @@
<?php
use yii\db\Migration;
/**
* Handles the creation of table `menu_items_lng_lng`.
*/
class m180826_204039_create_menu_items_lng_lng_table extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$tableOptions = null;
if ($this->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}}');
}
}

27
console/migrations/m180826_215830_remove_menu_items_name_title_attr_fields.php

@ -0,0 +1,27 @@
<?php
use yii\db\Migration;
/**
* Class m180826_215830_remove_menu_items_name_title_attr_fields
*/
class m180826_215830_remove_menu_items_name_title_attr_fields extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$this->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));
}
}

33
console/migrations/m180827_194913_set_core_tables_unicode_oollate.php

@ -0,0 +1,33 @@
<?php
use yii\db\Migration;
/**
* Class m180827_194913_set_core_tables_unicode_oollate
*/
class m180827_194913_set_core_tables_unicode_oollate extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$tables = ['user_networks', 'settings', 'modules', 'menu', 'menu_items', 'menu_lng', 'menu_items_lng'];
$db = Yii::$app->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;
}
}

351
core/behaviors/LanguageBehavior.php

@ -0,0 +1,351 @@
<?php
/**
* Created by Error202
* Date: 24.08.2018
*/
namespace core\behaviors;
use yii\base\Behavior;
use yii\base\InvalidConfigException;
use yii\db\ActiveRecord;
use yii\db\ActiveQuery;
class LanguageBehavior extends Behavior
{
/**
* Attributes for translate, ex.: ['name', 'content']
* @var array
*/
public $attributes;
/**
* Class name for language active record entity
* @var string
*/
public $virtualClassName = 'VirtualTranslate';
/**
* Field name for language in translate table
* @var string
*/
public $languageField = 'language';
/**
* Field name for tables relative, ex.: 'post_id'
* @var string
*/
public $relativeField;
/**
* Available languages, ex.: ['en', 'ru']
* @var array
*/
public $translatedLanguages;
/**
* Default language, ex.: 'en'
* @var string
*/
public $defaultLanguage;
/**
* Translate table name, ex.: 'post_lng'
* @var string
*/
public $tableName;
/**
* Abridge the language ID.
* @var boolean whether to abridge the language ID.
*/
public $abridge = true;
/**
* Delete relative if foreign key not set on delete: cascade
* @var bool
*/
public $forceDelete = false;
private $ownerClassName;
private $ownerClassShortName;
private $ownerPrimaryKey;
private $languageAttributes = [];
/**
* Control events firing
* @return array
*/
public function events()
{
return [
ActiveRecord::EVENT_AFTER_FIND => 'afterFind',
ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate',
ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert',
ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
//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;
}
}

64
core/components/LanguageDynamicModel.php

@ -0,0 +1,64 @@
<?php
/**
* Created by Error202
* Date: 24.08.2018
*/
namespace core\components;
use yii\base\DynamicModel;
use Yii;
class LanguageDynamicModel extends DynamicModel
{
private $new_labels = [];
private $new_hints = [];
private $new_rules = [];
public function __construct( array $attributes = [], array $config = [] )
{
parent::__construct( array_merge($this->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);
}
}

15
core/components/LanguageTranslateQuery.php

@ -0,0 +1,15 @@
<?php
/**
* Created by Error202
* Date: 26.08.2018
*/
namespace core\components;
use yii\db\ActiveQuery;
class LanguageTranslateQuery extends ActiveQuery
{
use LanguageTranslateTrait;
}

55
core/components/LanguageTranslateTrait.php

@ -0,0 +1,55 @@
<?php
/**
* Created by Error202
* Date: 26.08.2018
*/
namespace core\components;
use yii\db\ActiveQuery;
use Yii;
/**
* Translate trait.
* Modify ActiveRecord query for translate support
*
* @mixin ActiveQuery
*/
trait LanguageTranslateTrait
{
/**
* @var string the name of the lang field of the translation table. Default to 'language'.
*/
public $languageField = 'language';
/**
* Scope for querying by languages
*
* @param string $language
* @param bool $abridge
*
* @return $this
*/
public function localized($language = null, $abridge = true)
{
$language = $language ?: Yii::$app->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;
}
}

39
core/components/TestForm.php

@ -0,0 +1,39 @@
<?php
/**
* Created by Error202
* Date: 24.08.2018
*/
namespace core\components;
class TestForm extends LanguageDynamicModel
{
public $name;
public $content;
public $number;
public function rules() {
return array_merge(
parent::rules(),
[
[['name', 'content'], 'required'],
['name', 'string', 'max' => 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'),
]
);
}
}

13
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;
}
}

38
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]);
}
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());
}*/
}

40
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'],
],
];
}
}

110
core/forms/CompositeLanguageForm.php

@ -0,0 +1,110 @@
<?php
namespace core\forms;
use core\components\LanguageDynamicModel;
use yii\base\Model;
use yii\helpers\ArrayHelper;
abstract class CompositeLanguageForm extends LanguageDynamicModel
{
/**
* @var Model[]|array[]
*/
private $forms = [];
abstract protected function internalForms(): array;
public function load($data, $formName = null): bool
{
$success = parent::load($data, $formName);
foreach ($this->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);
}
}

40
core/forms/MetaForm-org.php

@ -0,0 +1,40 @@
<?php
namespace core\forms;
use core\entities\Meta;
use yii\base\Model;
use Yii;
class MetaFormOrg extends Model
{
public $title;
public $description;
public $keywords;
public function __construct(Meta $meta = null, $config = [])
{
if ($meta) {
$this->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'),
];
}
}

29
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 [
return array_merge(
parent::rules(),
[
[['title'], 'string', 'max' => 255],
[['description', 'keywords'], 'string'],
];
]
);
}
public function attributeLabels() {
return [
return array_merge(
parent::attributeLabels(),
[
'title' => Yii::t('main', 'Title'),
'description' => Yii::t('main', 'Description'),
'keywords' => Yii::t('main', 'Keywords'),
];
]
);
}
}

35
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 = [])
{
public function __construct( Menu $menu = null, array $attributes = [], array $config = [] ) {
if ($menu) {
$this->name = $menu->name;
$this->_menu = $menu;
}
parent::__construct($config);
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 [
return array_merge(
parent::rules(),
[
[['name'], 'required'],
[['name'], 'string', 'max' => 255],
];
]
);
}
public function attributeLabels() {
return [
return array_merge(
parent::attributeLabels(),
[
'name' => Yii::t('main', 'Name'),
];
]
);
}
}

54
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'],
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'),
]
);
}
}

119
core/helpers/LanguageHelper.php

@ -0,0 +1,119 @@
<?php
/**
* Created by Error202
* Date: 23.08.2018
*/
namespace core\helpers;
use common\modules\pages\forms\PageForm;
use Yii;
use yii\base\DynamicModel;
class LanguageHelper
{
public static function enabled()
{
return count(Yii::$app->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;
}
}

3
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;

3
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;

6
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,

4
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);
}

9
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);

1
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,
]);

2
core/widgets/menu/views/menu.php

@ -24,7 +24,7 @@
<?php endforeach; ?>
</div>
<?php elseif ($item->parent_id == 0): ?>
<a <?= $item->target ? 'target="'.$item->target.'"' : '' ?> href="<?= $item->getUrl() ?>" class="nav-link"><?= $item->name ?></a>
<a <?= $item->target ? 'target="'.$item->target.'"' : '' ?> href="<?= $item->getUrl() ?>" class="nav-link"><?= $item->translation->name ?></a>
<?php endif; ?>
</li>
<?php endforeach; ?>

5
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\-]+>/<id:\d+>/<_a:[\w\-]+>' => '<_c>/<_a>',
]);
// redefine home url
\Yii::$app->homeUrl = LanguageHelper::addLangToUrl(\Yii::$app->homeUrl);
}
}

8
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,

39
frontend/config/LanguageUrlManager.php

@ -0,0 +1,39 @@
<?php
/**
* Created by Error202
* Date: 23.08.2018
*/
namespace frontend\config;
use core\helpers\LanguageHelper;
use yii\web\UrlManager;
use Yii;
class LanguageUrlManager extends UrlManager
{
public function init()
{
LanguageHelper::setLanguage(Yii::$app->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);
}
}

4
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'],

2
frontend/controllers/ContactController.php

@ -8,7 +8,7 @@ use core\forms\ContactForm;
class ContactController extends FrontendController
{
public $layout = 'contacts';
public $layout = 'blank';
private $service;

28
frontend/web/themes/start/layouts/blank.php

@ -0,0 +1,28 @@
<?php $this->beginContent('@frontend/web/themes/start/layouts/main.php') ?>
<!-- Page Content -->
<div class="container">
<?= \yii\widgets\Breadcrumbs::widget([
'tag' => 'ul',
'itemTemplate' => '<li class="breadcrumb-item">{link}</li>' . "\n",
'activeItemTemplate' => '<li class="breadcrumb-item active">{link}</li>' . "\n",
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>
<div class="row">
<!-- Page Content -->
<div class="col-md-12">
<?= $content ?>
</div>
</div>
<!-- /.row -->
</div>
<!-- /.container -->
<?php $this->endContent() ?>

12
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;
?>
<article class="page-view">
<h1><?= Html::encode($page->title) ?></h1>
<h1><?= Html::encode($page->translation->title) ?></h1>
<?= $page->content(Yii::$app->formatter->asHtml($page->content, [
<?= $page->content(Yii::$app->formatter->asHtml($page->translation->content, [
'Attr.AllowedRel' => array('nofollow'),
'HTML.SafeObject' => true,
'Output.FlashCompat' => true,

Loading…
Cancel
Save