"enqueue/amqp-lib": "^0.10.19",
"vlucas/phpdotenv": "^5.6",
"softark/yii2-dual-listbox": "^1.0",
- "kartik-v/yii2-widget-depdrop": "dev-master"
+ "kartik-v/yii2-widget-depdrop": "dev-master",
+ "sangroya/yii2-ckeditor5": "*"
},
"require-dev": {
"yiisoft/yii2-debug": "~2.1.0",
"squizlabs/php_codesniffer": "@stable"
},
"autoload": {
- "psr-4": { "OpenAPI\\Client\\" : "lib/yandex_market_api/" }
+ "psr-4": { "yii_app\\": "", "OpenAPI\\Client\\" : "lib/yandex_market_api/" }
},
"config": {
"allow-plugins": {
--- /dev/null
+<?php
+
+namespace app\controllers;
+
+use Yii;
+use yii_app\records\WikiCategory;
+use yii\data\ActiveDataProvider;
+use yii\web\Controller;
+use yii\web\NotFoundHttpException;
+use yii\filters\VerbFilter;
+
+/**
+ * WikiCategoryController implements the CRUD actions for WikiCategory model.
+ */
+class WikiCategoryController extends Controller
+{
+ /**
+ * @inheritDoc
+ */
+ public function behaviors()
+ {
+ return array_merge(
+ parent::behaviors(),
+ [
+ 'verbs' => [
+ 'class' => VerbFilter::class,
+ 'actions' => [
+ 'delete' => ['POST'],
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Lists all WikiCategory models.
+ *
+ * @return string
+ */
+ public function actionIndex()
+ {
+ $dataProvider = new ActiveDataProvider([
+ 'query' => WikiCategory::find(),
+ /*
+ 'pagination' => [
+ 'pageSize' => 50
+ ],
+ 'sort' => [
+ 'defaultOrder' => [
+ 'id' => SORT_DESC,
+ ]
+ ],
+ */
+ ]);
+
+ return $this->render('index', [
+ 'dataProvider' => $dataProvider,
+ ]);
+ }
+
+ /**
+ * Displays a single WikiCategory model.
+ * @param int $id ID категории
+ * @return string
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ public function actionView($id)
+ {
+ return $this->render('view', [
+ 'model' => $this->findModel($id),
+ ]);
+ }
+
+ /**
+ * Creates a new WikiCategory model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ * @return string|\yii\web\Response
+ */
+ public function actionCreate()
+ {
+ $model = new WikiCategory();
+
+ if ($this->request->isPost) {
+ if ($model->load($this->request->post())) {
+ if (!$model->validate()) {
+ Yii::error('Validation errors: ' . json_encode($model->errors), 'wiki');
+ }
+ if ($model->save()) {
+ return $this->redirect(['view', 'id' => $model->id]);
+ } else {
+ Yii::error('Save failed: ' . json_encode($model->errors), 'wiki');
+ }
+ }
+ } else {
+ $model->loadDefaultValues();
+ }
+
+ return $this->render('create', [
+ 'model' => $model,
+ ]);
+ }
+
+ /**
+ * Updates an existing WikiCategory model.
+ * If update is successful, the browser will be redirected to the 'view' page.
+ * @param int $id ID категории
+ * @return string|\yii\web\Response
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ public function actionUpdate($id)
+ {
+ $model = $this->findModel($id);
+
+ if ($this->request->isPost && $model->load($this->request->post()) && $model->save()) {
+ return $this->redirect(['view', 'id' => $model->id]);
+ }
+
+ return $this->render('update', [
+ 'model' => $model,
+ ]);
+ }
+
+ /**
+ * Deletes an existing WikiCategory model.
+ * If deletion is successful, the browser will be redirected to the 'index' page.
+ * @param int $id ID категории
+ * @return \yii\web\Response
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ public function actionDelete($id)
+ {
+ $this->findModel($id)->delete();
+
+ return $this->redirect(['index']);
+ }
+
+ /**
+ * Finds the WikiCategory model based on its primary key value.
+ * If the model is not found, a 404 HTTP exception will be thrown.
+ * @param int $id ID категории
+ * @return WikiCategory the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel($id)
+ {
+ if (($model = WikiCategory::findOne(['id' => $id])) !== null) {
+ return $model;
+ }
+
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+}
--- /dev/null
+<?php
+
+namespace app\controllers;
+
+use Yii;
+use yii_app\records\WikiArticle;
+use yii_app\records\WikiArticleSearch;
+use yii\web\Controller;
+use yii\web\NotFoundHttpException;
+use yii\filters\VerbFilter;
+use yii_app\records\WikiCategory;
+
+/**
+ * WikiController implements the CRUD actions for WikiArticle model.
+ */
+class WikiController extends Controller
+{
+ /**
+ * @inheritDoc
+ */
+ public function behaviors()
+ {
+ return array_merge(
+ parent::behaviors(),
+ [
+ 'verbs' => [
+ 'class' => VerbFilter::class,
+ 'actions' => [
+ 'delete' => ['POST'],
+ ],
+ ],
+ ]
+ );
+ }
+
+
+ /**
+ * Lists all WikiArticle models.
+ *
+ * @return string
+ */
+ public function actionIndex()
+ {
+ $searchModel = new WikiArticleSearch();
+ $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
+
+ $categories = WikiCategory::find()->all();
+ $tree = $this->buildTree($categories);
+
+ return $this->render('index', [
+ 'tree' => $tree,
+ 'searchModel' => $searchModel,
+ 'dataProvider' => $dataProvider,
+ ]);
+ }
+ private function buildTree($categories, $parentId = null)
+ {
+ $tree = [];
+ foreach ($categories as $category) {
+ if ($category->parent_id == $parentId) {
+ $tree[] = [
+ 'category' => $category,
+ 'articles' => WikiArticle::find()->where(['category_id' => $category->id])->all(),
+ 'children' => $this->buildTree($categories, $category->id),
+ ];
+ }
+ }
+ return $tree;
+ }
+
+ /**
+ * Displays a single WikiArticle model.
+ * @param int $id ID статьи
+ * @return string
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ public function actionView($parent_cat_slug, $article_slug)
+ {
+ $category = WikiCategory::findOne(['slug' => $parent_cat_slug]);
+ if (!$category) {
+ throw new NotFoundHttpException('Category not found.');
+ }
+
+ $model = WikiArticle::findOne(['category_id' => $category->id, 'slug' => $article_slug]);
+ if (!$model) {
+ throw new NotFoundHttpException('Article not found.');
+ }
+
+ return $this->render('view', ['model' => $model]);
+ }
+
+ /**
+ * Creates a new WikiArticle model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ * @return string|\yii\web\Response
+ */
+ public function actionCreate()
+ {
+ $model = new WikiArticle();
+
+ if ($this->request->isPost) {
+ if ($model->load($this->request->post()) && $model->save()) {
+ // Получаем slug категории и slug статьи
+ $categorySlug = $model->category->slug; // Slug категории из связанной модели
+ $articleSlug = $model->slug; // Slug созданной статьи
+
+ // Перенаправляем на страницу просмотра с правильными параметрами
+ return $this->redirect(['view', 'parent_cat_slug' => $categorySlug, 'article_slug' => $articleSlug]);
+ }
+ } else {
+ $model->loadDefaultValues();
+ }
+
+ return $this->render('create', [
+ 'model' => $model,
+ ]);
+ }
+
+ /**
+ * Updates an existing WikiArticle model.
+ * If update is successful, the browser will be redirected to the 'view' page.
+ * @param int $id ID статьи
+ * @return string|\yii\web\Response
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ public function actionUpdate($id)
+ {
+ $model = $this->findModel($id);
+
+ if ($this->request->isPost && $model->load($this->request->post()) && $model->save()) {
+ return $this->redirect(['view', 'id' => $model->id]);
+ }
+
+ return $this->render('update', [
+ 'model' => $model,
+ ]);
+ }
+
+ /**
+ * Deletes an existing WikiArticle model.
+ * If deletion is successful, the browser will be redirected to the 'index' page.
+ * @param int $id ID статьи
+ * @return \yii\web\Response
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ public function actionDelete($id)
+ {
+ WikiArticle::findOne($id)?->delete();
+ return $this->redirect(['index']);
+ }
+
+ /**
+ * Finds the WikiArticle model based on its primary key value.
+ * If the model is not found, a 404 HTTP exception will be thrown.
+ * @param int $id ID статьи
+ * @return WikiArticle the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel($id)
+ {
+ if (($model = WikiArticle::findOne(['id' => $id])) !== null) {
+ return $model;
+ }
+
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+}
--- /dev/null
+<?php
+
+use yii\db\Migration;
+
+/**
+ * Class m250210_120558_create_wiki_tables
+ */
+class m250210_120558_create_wiki_tables extends Migration
+{
+ const CATEGORY_TABLE = 'erp24.wiki_category';
+ const ARTICLE_TABLE = 'erp24.wiki_article';
+
+ /**
+ * {@inheritdoc}
+ */
+ public function safeUp()
+ {
+ $categorySchema = $this->db->getTableSchema(self::CATEGORY_TABLE);
+ $articleSchema = $this->db->getTableSchema(self::ARTICLE_TABLE);
+
+ if (!isset($categorySchema)) {
+ $this->createTable(self::CATEGORY_TABLE, [
+ 'id' => $this->primaryKey()->comment('ID категории'),
+ 'slug' => $this->string()->notNull()->unique()->comment('ЧПУ'),
+ 'title' => $this->string()->notNull()->comment('Название'),
+ 'parent_id' => $this->integer()->null()->comment('Родительская категория'),
+ 'description' => $this->text()->null()->comment('Описание'),
+ 'created_at' => $this->string()->notNull()->comment('Дата создания'),
+ 'created_by' => $this->integer()->notNull()->comment('Кем создано'),
+ 'updated_at' => $this->string()->null()->comment('Дата обновления'),
+ 'updated_by' => $this->integer()->null()->comment('Кем обновлено'),
+ 'allow_group_id' => $this->string()->null()->comment('Разрешенные группы'),
+ ]);
+
+ $this->addForeignKey(
+ 'fk-wiki_category-parent_id',
+ self::CATEGORY_TABLE,
+ 'parent_id',
+ self::CATEGORY_TABLE,
+ 'id',
+ 'SET NULL'
+ );
+ }
+
+ if (!isset($articleSchema)) {
+ $this->createTable(self::ARTICLE_TABLE, [
+ 'id' => $this->primaryKey()->comment('ID статьи'),
+ 'slug' => $this->string()->notNull()->unique()->comment('ЧПУ'),
+ 'title' => $this->string()->notNull()->comment('Название'),
+ 'category_id' => $this->integer()->notNull()->comment('Категория'),
+ 'description' => $this->text()->null()->comment('Описание'),
+ 'content' => $this->text()->notNull()->comment('Контент'),
+ 'created_at' => $this->string()->notNull()->comment('Дата создания'),
+ 'created_by' => $this->integer()->notNull()->comment('Кем создано'),
+ 'updated_at' => $this->string()->null()->comment('Дата обновления'),
+ 'updated_by' => $this->integer()->null()->comment('Кем обновлено'),
+ ]);
+
+ $this->addForeignKey(
+ 'fk-wiki_article-category_id',
+ self::ARTICLE_TABLE,
+ 'category_id',
+ self::CATEGORY_TABLE,
+ 'id',
+ 'SET NULL'
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function safeDown()
+ {
+ $categorySchema = $this->db->getTableSchema(self::CATEGORY_TABLE);
+ $articleSchema = $this->db->getTableSchema(self::ARTICLE_TABLE);
+
+ if (isset($articleSchema)) {
+ $this->dropTable(self::ARTICLE_TABLE);
+ }
+
+ if (isset($categorySchema)) {
+ $this->dropTable(self::CATEGORY_TABLE);
+ }
+ }
+
+ /*
+ // Use up()/down() to run migration code without a transaction.
+ public function up()
+ {
+
+ }
+
+ public function down()
+ {
+ echo "m250210_120558_create_wiki_tables cannot be reverted.\n";
+
+ return false;
+ }
+ */
+}
--- /dev/null
+<?php
+
+namespace yii_app\records;
+
+use Yii;
+use yii\behaviors\BlameableBehavior;
+use yii\behaviors\TimestampBehavior;
+use yii\helpers\Inflector;
+
+/**
+ * This is the model class for table "wiki_article".
+ *
+ * @property int $id ID статьи
+ * @property string $slug ЧПУ
+ * @property string $title Название
+ * @property int $category_id Категория
+ * @property string|null $description Описание
+ * @property string $content Контент
+ * @property string $created_at Дата создания
+ * @property int $created_by Кем создано
+ * @property string|null $updated_at Дата обновления
+ * @property int|null $updated_by Кем обновлено
+ *
+ * @property WikiCategory $category
+ */
+class WikiArticle extends \yii\db\ActiveRecord
+{
+ /**
+ * {@inheritdoc}
+ */
+ public static function tableName()
+ {
+ return 'wiki_article';
+ }
+
+ public function behaviors()
+ {
+ return [
+ [
+ 'class' => TimestampBehavior::class,
+ 'value' => function () {
+ return date('Y-m-d H:i:s');
+ },
+ ],
+ BlameableBehavior::class,
+ ];
+ }
+
+ public function beforeValidate()
+ {
+ if ($this->isNewRecord && !$this->slug) {
+ $this->slug = Inflector::slug($this->title);
+ }
+ return parent::beforeValidate();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rules()
+ {
+ return [
+ [[ 'title', 'category_id', 'content' ], 'required'],
+ [['category_id', 'created_by', 'updated_by'], 'default', 'value' => null],
+ [['category_id', 'created_by', 'updated_by'], 'integer'],
+ [['slug', 'created_at', 'created_by'], 'safe'],
+ [['description', 'content'], 'string'],
+ [['slug', 'title', 'created_at', 'updated_at'], 'string', 'max' => 255],
+ [['slug'], 'unique'],
+ [['category_id'], 'exist', 'skipOnError' => true, 'targetClass' => WikiCategory::class, 'targetAttribute' => ['category_id' => 'id']],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'id' => 'ID статьи',
+ 'slug' => 'ЧПУ',
+ 'title' => 'Название',
+ 'category_id' => 'Категория',
+ 'description' => 'Описание',
+ 'content' => 'Контент',
+ 'created_at' => 'Дата создания',
+ 'created_by' => 'Кем создано',
+ 'updated_at' => 'Дата обновления',
+ 'updated_by' => 'Кем обновлено',
+ ];
+ }
+
+ /**
+ * Gets query for [[Category]].
+ *
+ * @return \yii\db\ActiveQuery
+ */
+ public function getCategory()
+ {
+ return $this->hasOne(WikiCategory::class, ['id' => 'category_id']);
+ }
+}
--- /dev/null
+<?php
+
+namespace yii_app\records;
+
+use yii\base\Model;
+use yii\data\ActiveDataProvider;
+use yii_app\records\WikiArticle;
+
+/**
+ * WikiArticleSearch represents the model behind the search form of `yii_app\records\WikiArticle`.
+ */
+class WikiArticleSearch extends WikiArticle
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function rules()
+ {
+ return [
+ [['id', 'category_id', 'created_by', 'updated_by'], 'integer'],
+ [['slug', 'title', 'description', 'content', 'created_at', 'updated_at'], 'safe'],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function scenarios()
+ {
+ // bypass scenarios() implementation in the parent class
+ return Model::scenarios();
+ }
+
+ /**
+ * Creates data provider instance with search query applied
+ *
+ * @param array $params
+ *
+ * @return ActiveDataProvider
+ */
+ public function search($params)
+ {
+ $query = WikiArticle::find();
+
+ // add conditions that should always apply here
+
+ $dataProvider = new ActiveDataProvider([
+ 'query' => $query,
+ ]);
+
+ $this->load($params);
+
+ if (!$this->validate()) {
+ // uncomment the following line if you do not want to return any records when validation fails
+ // $query->where('0=1');
+ return $dataProvider;
+ }
+
+ // grid filtering conditions
+ $query->andFilterWhere([
+ 'id' => $this->id,
+ 'category_id' => $this->category_id,
+ 'created_by' => $this->created_by,
+ 'updated_by' => $this->updated_by,
+ ]);
+
+ $query->andFilterWhere(['ilike', 'slug', $this->slug])
+ ->andFilterWhere(['ilike', 'title', $this->title])
+ ->andFilterWhere(['ilike', 'description', $this->description])
+ ->andFilterWhere(['ilike', 'content', $this->content])
+ ->andFilterWhere(['ilike', 'created_at', $this->created_at])
+ ->andFilterWhere(['ilike', 'updated_at', $this->updated_at]);
+
+ return $dataProvider;
+ }
+}
--- /dev/null
+<?php
+
+namespace yii_app\records;
+
+use Yii;
+use yii\behaviors\BlameableBehavior;
+use yii\behaviors\TimestampBehavior;
+use yii\helpers\Inflector;
+
+/**
+ * This is the model class for table "wiki_category".
+ *
+ * @property int $id ID категории
+ * @property string $slug ЧПУ
+ * @property string $title Название
+ * @property int|null $parent_id Родительская категория
+ * @property string|null $description Описание
+ * @property string $created_at Дата создания
+ * @property int $created_by Кем создано
+ * @property string|null $updated_at Дата обновления
+ * @property int|null $updated_by Кем обновлено
+ * @property string|null $allow_group_id Разрешенные группы
+ *
+ * @property WikiCategory $parent
+ * @property WikiArticle[] $wikiArticles
+ * @property WikiCategory[] $wikiCategories
+ */
+class WikiCategory extends \yii\db\ActiveRecord
+{
+ /**
+ * {@inheritdoc}
+ */
+ public static function tableName()
+ {
+ return 'wiki_category';
+ }
+
+ public function behaviors()
+ {
+ return [
+ [
+ 'class' => TimestampBehavior::class,
+ 'value' => function () {
+ return date('Y-m-d H:i:s');
+ },
+ ],
+ BlameableBehavior::class,
+ ];
+ }
+
+ public function beforeValidate()
+ {
+ if ($this->isNewRecord && !$this->slug) {
+ $this->slug = Inflector::slug($this->title); // Автоматическая генерация slug
+ }
+ return parent::beforeValidate();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rules()
+ {
+ return [
+ [[ 'title'], 'required'],
+ [['parent_id', 'created_by', 'updated_by'], 'default', 'value' => null],
+ [['parent_id', 'created_by', 'updated_by'], 'integer'],
+ [['created_at', 'updated_at', 'slug', 'created_at', 'created_by'], 'safe'],
+ [['description'], 'string'],
+ [['slug', 'title', 'created_at', 'updated_at', 'allow_group_id'], 'string', 'max' => 255],
+ [['slug'], 'unique'],
+ [['parent_id'], 'exist', 'skipOnError' => true, 'targetClass' => WikiCategory::class, 'targetAttribute' => ['parent_id' => 'id']],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'id' => 'ID категории',
+ 'slug' => 'ЧПУ',
+ 'title' => 'Название',
+ 'parent_id' => 'Родительская категория',
+ 'description' => 'Описание',
+ 'created_at' => 'Дата создания',
+ 'created_by' => 'Кем создано',
+ 'updated_at' => 'Дата обновления',
+ 'updated_by' => 'Кем обновлено',
+ 'allow_group_id' => 'Разрешенные группы',
+ ];
+ }
+
+ /**
+ * Gets query for [[Parent]].
+ *
+ * @return \yii\db\ActiveQuery
+ */
+ public function getParent()
+ {
+ return $this->hasOne(WikiCategory::class, ['id' => 'parent_id']);
+ }
+
+ /**
+ * Gets query for [[WikiArticles]].
+ *
+ * @return \yii\db\ActiveQuery
+ */
+ public function getWikiArticles()
+ {
+ return $this->hasMany(WikiArticle::class, ['category_id' => 'id']);
+ }
+
+ /**
+ * Gets query for [[WikiCategories]].
+ *
+ * @return \yii\db\ActiveQuery
+ */
+ public function getWikiCategories()
+ {
+ return $this->hasMany(WikiCategory::class, ['parent_id' => 'id']);
+ }
+}
--- /dev/null
+<?php
+
+use yii\helpers\ArrayHelper;
+use yii\helpers\Html;
+use yii\widgets\ActiveForm;
+use yii_app\records\WikiCategory;
+
+/** @var yii\web\View $this */
+/** @var yii_app\records\WikiCategory $model */
+/** @var yii\widgets\ActiveForm $form */
+
+function getCategoriesWithHierarchy($categories, $parentId = null, $level = 0)
+{
+ $result = [];
+ foreach ($categories as $category) {
+ if ($category->parent_id == $parentId) {
+ $result[$category->id] = str_repeat('— ', $level) . $category->title;
+ $result = ArrayHelper::merge(
+ $result,
+ getCategoriesWithHierarchy($categories, $category->id, $level + 1)
+ );
+ }
+ }
+ return $result;
+}
+
+$categories = WikiCategory::find()->all();
+$categoryList = getCategoriesWithHierarchy($categories);
+?>
+
+<div class="wiki-category-form">
+
+ <?php $form = ActiveForm::begin(); ?>
+
+ <?= $form->field($model, 'slug')->hiddenInput(['maxlength' => true])->label(false) ?>
+
+ <?= $form->field($model, 'title')->textInput(['maxlength' => true]) ?>
+
+ <?= $form->field($model, 'parent_id')->dropDownList(
+ $categoryList,
+ ['prompt' => 'Выберите родительскую категорию']
+ ) ?>
+
+ <?= $form->field($model, 'description')->textarea(['rows' => 6]) ?>
+
+ <?= $form->field($model, 'allow_group_id')->textInput(['maxlength' => true]) ?>
+
+ <div class="form-group">
+ <?= Html::submitButton('Сохранить', ['class' => 'btn btn-success']) ?>
+ </div>
+
+ <?php ActiveForm::end(); ?>
+
+</div>
--- /dev/null
+<?php
+
+use yii\helpers\Html;
+
+/** @var yii\web\View $this */
+/** @var yii_app\records\WikiCategory $model */
+
+$this->title = 'Добавление категории документации';
+$this->params['breadcrumbs'][] = ['label' => 'Wiki Categories', 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+<div class="wiki-category-create p-4">
+ <?= Html::a('Назад', ['index'], ['class' => 'btn btn-primary my-4']) ?>
+ <h1><?= Html::encode($this->title) ?></h1>
+
+ <?= $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+</div>
--- /dev/null
+<?php
+
+use yii_app\records\WikiCategory;
+use yii\helpers\Html;
+use yii\helpers\Url;
+use yii\grid\ActionColumn;
+use yii\grid\GridView;
+
+/** @var yii\web\View $this */
+/** @var yii\data\ActiveDataProvider $dataProvider */
+
+$this->title = 'Категории документации (wiki)';
+$this->params['breadcrumbs'][] = $this->title;
+?>
+<div class="wiki-category-index p-4">
+
+ <h1><?= Html::encode($this->title) ?></h1>
+
+ <p>
+ <?= Html::a('Добавить категорию', ['create'], ['class' => 'btn btn-success']) ?>
+ </p>
+
+
+ <?= GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'columns' => [
+ ['class' => 'yii\grid\SerialColumn'],
+
+ 'id',
+ 'slug',
+ 'title',
+ [
+ 'attribute' => 'parent_id',
+ 'value' => function ($model) {
+ return $model->parent ? $model->parent->title : '';
+ },
+ 'label' => 'Родитель',
+ ],
+ 'description:ntext',
+ //'created_at',
+ //'created_by',
+ //'updated_at',
+ //'updated_by',
+ //'allow_group_id',
+ [
+ 'class' => ActionColumn::class,
+ 'urlCreator' => function ($action, WikiCategory $model, $key, $index, $column) {
+ return Url::toRoute([$action, 'id' => $model->id]);
+ }
+ ],
+ ],
+ ]); ?>
+
+
+</div>
--- /dev/null
+<?php
+
+use yii\helpers\Html;
+
+/** @var yii\web\View $this */
+/** @var yii_app\records\WikiCategory $model */
+
+$this->title = 'Редактирование: ' . $model->title;
+$this->params['breadcrumbs'][] = ['label' => 'Wiki Categories', 'url' => ['index']];
+$this->params['breadcrumbs'][] = ['label' => $model->title, 'url' => ['view', 'id' => $model->id]];
+$this->params['breadcrumbs'][] = 'Update';
+?>
+<div class="wiki-category-update p-4">
+ <?= Html::a('Назад', ['index'], ['class' => 'btn btn-primary my-4']) ?>
+ <h1><?= Html::encode($this->title) ?></h1>
+
+ <?= $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+</div>
--- /dev/null
+<?php
+
+use yii\helpers\Html;
+use yii\widgets\DetailView;
+
+/** @var yii\web\View $this */
+/** @var yii_app\records\WikiCategory $model */
+
+$this->title = $model->title;
+$this->params['breadcrumbs'][] = ['label' => 'Wiki Categories', 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+\yii\web\YiiAsset::register($this);
+?>
+<div class="wiki-category-view p-4">
+ <?= Html::a('Назад', ['index'], ['class' => 'btn btn-primary my-4']) ?>
+ <h1><?= Html::encode($this->title) ?></h1>
+
+ <p>
+ <?= Html::a('Редактировать', ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?>
+ <?= Html::a('Удалить', ['delete', 'id' => $model->id], [
+ 'class' => 'btn btn-danger',
+ 'data' => [
+ 'confirm' => 'Вы точно хотите удалить?',
+ 'method' => 'post',
+ ],
+ ]) ?>
+ </p>
+
+ <?= DetailView::widget([
+ 'model' => $model,
+ 'attributes' => [
+ 'id',
+ 'slug',
+ 'title',
+ [
+ 'attribute' => 'parent_id',
+ 'value' => function ($model) {
+ return $model->parent ? $model->parent->title : '';
+ },
+ 'label' => 'Родитель',
+ ],
+ 'description:ntext',
+ 'created_at',
+ 'created_by',
+ 'updated_at',
+ 'updated_by',
+ 'allow_group_id',
+ ],
+ ]) ?>
+
+</div>
--- /dev/null
+<?php
+
+
+use sangroya\ckeditor5\CKEditor;
+use yii\helpers\ArrayHelper;
+use yii\helpers\Html;
+use yii\widgets\ActiveForm;
+use yii_app\records\WikiCategory;
+
+/** @var yii\web\View $this */
+/** @var yii_app\records\WikiArticle $model */
+/** @var yii\widgets\ActiveForm $form */
+
+function getCategoriesWithHierarchy($categories, $parentId = null, $level = 0)
+{
+ $result = [];
+ foreach ($categories as $category) {
+ if ($category->parent_id == $parentId) {
+ $result[$category->id] = str_repeat('— ', $level) . $category->title;
+ $result = ArrayHelper::merge(
+ $result,
+ getCategoriesWithHierarchy($categories, $category->id, $level + 1)
+ );
+ }
+ }
+ return $result;
+}
+
+$categories = WikiCategory::find()->all();
+$categoryList = getCategoriesWithHierarchy($categories);
+?>
+
+<div class="wiki-article-form">
+
+ <?php $form = ActiveForm::begin(); ?>
+
+ <?= $form->field($model, 'slug')->hiddenInput(['maxlength' => true])->label(false) ?>
+
+ <?= $form->field($model, 'title')->textInput(['maxlength' => true]) ?>
+
+ <?= $form->field($model, 'category_id')->dropDownList(
+ $categoryList,
+ ['prompt' => 'Выберите родительскую категорию']
+ ) ?>
+
+ <?= $form->field($model, 'description')->textarea(['rows' => 6]) ?>
+
+ <?= $form->field($model, 'content')->widget(CKEditor::class, [
+ 'options' => ['rows' => 6],
+ 'uploadUrl' => '/uploads',
+ ]) ?>
+
+
+ <div class="form-group">
+ <?= Html::submitButton('Сохранить', ['class' => 'btn btn-success']) ?>
+ </div>
+
+ <?php ActiveForm::end(); ?>
+
+</div>
--- /dev/null
+<?php
+
+use yii\helpers\Html;
+use yii\widgets\ActiveForm;
+
+/** @var yii\web\View $this */
+/** @var yii_app\records\WikiArticleSearch $model */
+/** @var yii\widgets\ActiveForm $form */
+?>
+
+<div class="wiki-article-search">
+
+ <?php $form = ActiveForm::begin([
+ 'action' => ['index'],
+ 'method' => 'get',
+ ]); ?>
+
+ <?= $form->field($model, 'id') ?>
+
+ <?= $form->field($model, 'slug') ?>
+
+ <?= $form->field($model, 'title') ?>
+
+ <?= $form->field($model, 'category_id') ?>
+
+ <?= $form->field($model, 'description') ?>
+
+ <?php // echo $form->field($model, 'content') ?>
+
+ <?php // echo $form->field($model, 'created_at') ?>
+
+ <?php // echo $form->field($model, 'created_by') ?>
+
+ <?php // echo $form->field($model, 'updated_at') ?>
+
+ <?php // echo $form->field($model, 'updated_by') ?>
+
+ <div class="form-group">
+ <?= Html::submitButton('Search', ['class' => 'btn btn-primary']) ?>
+ <?= Html::resetButton('Reset', ['class' => 'btn btn-outline-secondary']) ?>
+ </div>
+
+ <?php ActiveForm::end(); ?>
+
+</div>
--- /dev/null
+<?php
+
+use yii\helpers\Html;
+
+/** @var yii\web\View $this */
+/** @var yii_app\records\WikiArticle $model */
+
+$this->title = 'Добавление статьи';
+$this->params['breadcrumbs'][] = ['label' => 'Wiki Articles', 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+<div class="wiki-article-create p-4">
+ <?= Html::a('Назад', ['index'], ['class' => 'btn btn-primary my-4']) ?>
+ <h1><?= Html::encode($this->title) ?></h1>
+
+ <?= $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+</div>
--- /dev/null
+<?php
+
+use yii_app\records\WikiArticle;
+use yii\helpers\Html;
+use yii\helpers\Url;
+use yii\grid\ActionColumn;
+use yii\grid\GridView;
+
+/** @var yii\web\View $this */
+/** @var yii_app\records\WikiArticleSearch $searchModel */
+/** @var yii\data\ActiveDataProvider $dataProvider */
+/** @var array $tree */
+
+$this->title = 'Документация';
+$this->params['breadcrumbs'][] = $this->title;
+$user = Yii::$app->user->identity;
+function renderTree($tree, $level = 0)
+{
+ if (empty($tree)) {
+ return '';
+ }
+
+ $html = '<ul class="tree-level-' . $level . '">';
+ foreach ($tree as $node) {
+ $category = $node['category'];
+ $articles = $node['articles'];
+ $children = $node['children'];
+
+ // Выводим категорию
+ $html .= '<li>';
+ $html .= Html::tag('div', Html::encode($category->title), ['class' => 'category-title']);
+
+ // Выводим описание категории (если есть)
+ if (!empty($category->description)) {
+ $html .= Html::tag('div', Html::encode($category->description), ['class' => 'category-description']);
+ }
+
+
+ if (!empty($articles)) {
+ $html .= '<ul class="articles-list">';
+ foreach ($articles as $article) {
+ $articleUrl = Url::to(['view', 'parent_cat_slug' => $category->slug, 'article_slug' => $article->slug], true);
+ $html .= '<li>';
+ $html .= Html::a(Html::encode($article->title), $articleUrl, ['class' => 'article-link']);
+
+ $html .= '</li>';
+ }
+ $html .= '</ul>';
+ }
+
+ if (!empty($children)) {
+ $html .= '<ul class="subcategories visible">';
+ $html .= renderTree($children, $level + 1);
+ $html .= '</ul>';
+ }
+
+ $html .= '</li>';
+ }
+ $html .= '</ul>';
+
+ return $html;
+}
+
+?>
+
+<div class="wiki-category-index p-4">
+ <h1><?= Html::encode($this->title) ?></h1>
+ <?php if ($user->group_id == 81) : ?>
+ <p>
+ <?= Html::a('Создать категорию', ['wiki-category/create'], ['class' => 'btn btn-success']) ?>
+ <?= Html::a('Создать статью', ['create'], ['class' => 'btn btn-success']) ?>
+ </p>
+ <?php endif; ?>
+ <!-- Вывод дерева -->
+ <div class="row">
+ <div class="col-3 tree-container">
+ <?= renderTree($tree); ?>
+ </div>
+ <div class="col-9">
+ <?= GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'filterModel' => $searchModel,
+ 'columns' => [
+ 'title',
+ [
+ 'attribute' => 'category_id',
+ 'value' => function ($model) {
+ return $model->category ? $model->category->title : '';
+ },
+ 'label' => 'Категория',
+ ],
+ ['class' => 'yii\grid\ActionColumn'],
+ ],
+ ]); ?>
+ </div>
+ </div>
+</div>
+
+<!-- Стили -->
+<style>
+ .tree-container {
+ font-family: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif;
+ line-height: 1.6;
+ }
+
+ .tree-level-0 {
+ margin-left: 0;
+ }
+
+ .tree-level-1 {
+ margin-left: 20px;
+ }
+
+ .tree-level-2 {
+ margin-left: 40px;
+ }
+
+ .category-title {
+ font-weight: bold;
+ cursor: pointer;
+ padding: 8px 12px;
+ background-color: #f4f4f4;
+ border-radius: 4px;
+ transition: background-color 0.3s ease;
+ }
+
+ .category-title:hover {
+ background-color: #e0e0e0;
+ }
+
+ .category-description {
+ color: #666;
+ font-size: 0.9em;
+ margin-top: 4px;
+ }
+
+ .article-link {
+ display: block;
+ padding: 6px 12px;
+ text-decoration: none;
+ color: #337ab7;
+ border-radius: 4px;
+ transition: background-color 0.3s ease, color 0.3s ease;
+ }
+
+ .article-link:hover {
+ background-color: #e0e0e0;
+ color: #23527c;
+ }
+
+ .article-description {
+ color: #666;
+ font-size: 0.9em;
+ margin-top: 4px;
+ }
+
+ .subcategories {
+ list-style-type: none;
+ padding-left: 20px;
+ transition: max-height 0.3s ease;
+ overflow: hidden;
+ }
+
+ .subcategories.hidden {
+ max-height: 0;
+ }
+
+ .subcategories.visible {
+ margin-bottom: 20px;
+ }
+
+
+</style>
+
+<!-- JavaScript для раскрывающегося дерева -->
+<script>
+ document.addEventListener('DOMContentLoaded', function () {
+ const categoryTitles = document.querySelectorAll('.category-title');
+ categoryTitles.forEach(title => {
+ title.addEventListener('click', function () {
+ const parentLi = this.closest('li');
+ const subcategories = parentLi.querySelector('.subcategories');
+
+ if (subcategories) {
+ subcategories.classList.toggle('visible');
+ subcategories.classList.toggle('hidden');
+ }
+ });
+ });
+ });
+</script>
+
+
--- /dev/null
+<?php
+
+use yii\helpers\Html;
+
+/** @var yii\web\View $this */
+/** @var yii_app\records\WikiArticle $model */
+
+$this->title = 'Редактировать статью: ' . $model->title;
+$this->params['breadcrumbs'][] = ['label' => 'Wiki Articles', 'url' => ['index']];
+$this->params['breadcrumbs'][] = ['label' => $model->title, 'url' => ['view', 'id' => $model->id]];
+$this->params['breadcrumbs'][] = 'Update';
+?>
+<div class="wiki-article-update p-4">
+ <?= Html::a('Назад', ['index'], ['class' => 'btn btn-primary my-4']) ?>
+ <h1><?= Html::encode($this->title) ?></h1>
+
+ <?= $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+</div>
--- /dev/null
+<?php
+
+use yii\helpers\Html;
+use yii\helpers\Url;
+use yii\web\YiiAsset;
+use yii\widgets\DetailView;
+use yii\web\View;
+use yii\bootstrap5\Button;
+use yii\bootstrap5\Alert;
+
+/** @var View $this */
+/** @var yii_app\records\WikiArticle $model */
+
+$this->title = $model->title;
+$this->params['breadcrumbs'][] = ['label' => 'Wiki Articles', 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+$user = Yii::$app->user->identity;
+$articleUrl = Url::to(['view', 'parent_cat_slug' => $model->category->slug, 'article_slug' => $model->slug], true);
+YiiAsset::register($this);
+?>
+
+<div class="wiki-article-view p-4 bg-white rounded shadow-sm">
+ <div class="d-flex justify-content-between align-items-center mb-3">
+ <h1 class="mb-0">
+ <?= Html::encode($this->title) ?>
+ <button class="btn btn-outline-secondary btn-sm" onclick="copyToClipboard('<?= $articleUrl ?>')">
+ <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 115.77 122.88" style="enable-background:new 0 0 115.77 122.88" xml:space="preserve">
+ <style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;}</style>
+ <g><path class="st0" d="M89.62,13.96v7.73h12.19h0.01v0.02c3.85,0.01,7.34,1.57,9.86,
+ 4.1c2.5,2.51,4.06,5.98,4.07,9.82h0.02v0.02 v73.27v0.01h-0.02c-0.01,3.84-1.57,
+ 7.33-4.1,9.86c-2.51,2.5-5.98,4.06-9.82,4.07v0.02h-0.02h-61.7H40.1v-0.02
+ c-3.84-0.01-7.34-1.57-9.86-4.1c-2.5-2.51-4.06-5.98-4.07-9.82h-0.02v-0.02V92.51H13.96h-0.01v-0.02c-3.84-0.01-7.34-1.57-9.86-4.1 c-2.5-2.51-4.06-5.98-4.07-9.82H0v-0.02V13.96v-0.01h0.02c0.01-3.85,1.58-7.34,4.1-9.86c2.51-2.5,5.98-4.06,9.82-4.07V0h0.02h61.7 h0.01v0.02c3.85,0.01,7.34,1.57,9.86,4.1c2.5,2.51,4.06,5.98,4.07,9.82h0.02V13.96L89.62,13.96z M79.04,21.69v-7.73v-0.02h0.02 c0-0.91-0.39-1.75-1.01-2.37c-0.61-0.61-1.46-1-2.37-1v0.02h-0.01h-61.7h-0.02v-0.02c-0.91,0-1.75,0.39-2.37,1.01 c-0.61,0.61-1,1.46-1,2.37h0.02v0.01v64.59v0.02h-0.02c0,0.91,0.39,1.75,1.01,2.37c0.61,0.61,1.46,1,2.37,1v-0.02h0.01h12.19V35.65 v-0.01h0.02c0.01-3.85,1.58-7.34,4.1-9.86c2.51-2.5,5.98-4.06,9.82-4.07v-0.02h0.02H79.04L79.04,21.69z M105.18,108.92V35.65v-0.02 h0.02c0-0.91-0.39-1.75-1.01-2.37c-0.61-0.61-1.46-1-2.37-1v0.02h-0.01h-61.7h-0.02v-0.02c-0.91,0-1.75,0.39-2.37,1.01 c-0.61,0.61-1,1.46-1,2.37h0.02v0.01v73.27v0.02h-0.02c0,0.91,0.39,1.75,1.01,2.37c0.61,0.61,1.46,1,2.37,1v-0.02h0.01h61.7h0.02 v0.02c0.91,0,1.75-0.39,2.37-1.01c0.61-0.61,1-1.46,1-2.37h-0.02V108.92L105.18,108.92z"/>
+ </g></svg>
+ </button>
+ </h1>
+ <?= Html::a('Назад', ['index'], ['class' => 'btn btn-secondary']) ?>
+ </div>
+
+ <div class="text-muted mb-3">
+ <strong>Автор:</strong> <?= Html::encode($model->created_by) ?> |
+ <strong>Дата создания:</strong> <?= date('d.m.Y H:i', strtotime($model->created_at)) ?>
+ </div>
+
+ <div class="article-content border p-3 rounded bg-light">
+ <?= nl2br($model->content) ?>
+ </div>
+ <?php if ($user->group_id == 81) : ?>
+ <div class="mt-4">
+ <?= Html::a('Редактировать', ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?>
+ <?= Html::a('Удалить', ['delete', 'id' => $model->id], [
+ 'class' => 'btn btn-danger',
+ 'data' => [
+ 'confirm' => 'Вы уверены, что хотите удалить статью?',
+ 'method' => 'post',
+ ],
+ ]) ?>
+ </div>
+ <?php endif; ?>
+</div>
+
+<script>
+ function copyToClipboard(text) {
+ navigator.clipboard.writeText(text).then(function() {
+ alert('Ссылка скопирована: ' + text);
+ }, function(err) {
+ console.error('Ошибка копирования: ', err);
+ });
+ }
+</script>