]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Создание CRUD документации
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Tue, 11 Feb 2025 07:05:35 +0000 (10:05 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Tue, 11 Feb 2025 07:05:35 +0000 (10:05 +0300)
18 files changed:
erp24/composer.json
erp24/controllers/WikiCategoryController.php [new file with mode: 0644]
erp24/controllers/WikiController.php [new file with mode: 0644]
erp24/migrations/m250210_120558_create_wiki_tables.php [new file with mode: 0644]
erp24/records/WikiArticle.php [new file with mode: 0644]
erp24/records/WikiArticleSearch.php [new file with mode: 0644]
erp24/records/WikiCategory.php [new file with mode: 0644]
erp24/views/wiki-category/_form.php [new file with mode: 0644]
erp24/views/wiki-category/create.php [new file with mode: 0644]
erp24/views/wiki-category/index.php [new file with mode: 0644]
erp24/views/wiki-category/update.php [new file with mode: 0644]
erp24/views/wiki-category/view.php [new file with mode: 0644]
erp24/views/wiki/_form.php [new file with mode: 0644]
erp24/views/wiki/_search.php [new file with mode: 0644]
erp24/views/wiki/create.php [new file with mode: 0644]
erp24/views/wiki/index.php [new file with mode: 0644]
erp24/views/wiki/update.php [new file with mode: 0644]
erp24/views/wiki/view.php [new file with mode: 0644]

index ec2445cc15904742c448d2f728edca1e42877719..31b7b4c26838c0efc9a3b88fb96ac1355231cc61 100644 (file)
@@ -38,7 +38,8 @@
         "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",
@@ -61,7 +62,7 @@
         "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": {
diff --git a/erp24/controllers/WikiCategoryController.php b/erp24/controllers/WikiCategoryController.php
new file mode 100644 (file)
index 0000000..3adffe6
--- /dev/null
@@ -0,0 +1,152 @@
+<?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.');
+    }
+}
diff --git a/erp24/controllers/WikiController.php b/erp24/controllers/WikiController.php
new file mode 100644 (file)
index 0000000..e48902c
--- /dev/null
@@ -0,0 +1,167 @@
+<?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.');
+    }
+}
diff --git a/erp24/migrations/m250210_120558_create_wiki_tables.php b/erp24/migrations/m250210_120558_create_wiki_tables.php
new file mode 100644 (file)
index 0000000..ace6de1
--- /dev/null
@@ -0,0 +1,101 @@
+<?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;
+    }
+    */
+}
diff --git a/erp24/records/WikiArticle.php b/erp24/records/WikiArticle.php
new file mode 100644 (file)
index 0000000..14fb9c9
--- /dev/null
@@ -0,0 +1,102 @@
+<?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']);
+    }
+}
diff --git a/erp24/records/WikiArticleSearch.php b/erp24/records/WikiArticleSearch.php
new file mode 100644 (file)
index 0000000..c2642d0
--- /dev/null
@@ -0,0 +1,76 @@
+<?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;
+    }
+}
diff --git a/erp24/records/WikiCategory.php b/erp24/records/WikiCategory.php
new file mode 100644 (file)
index 0000000..7601035
--- /dev/null
@@ -0,0 +1,124 @@
+<?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']);
+    }
+}
diff --git a/erp24/views/wiki-category/_form.php b/erp24/views/wiki-category/_form.php
new file mode 100644 (file)
index 0000000..0f9b087
--- /dev/null
@@ -0,0 +1,54 @@
+<?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>
diff --git a/erp24/views/wiki-category/create.php b/erp24/views/wiki-category/create.php
new file mode 100644 (file)
index 0000000..221104d
--- /dev/null
@@ -0,0 +1,20 @@
+<?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>
diff --git a/erp24/views/wiki-category/index.php b/erp24/views/wiki-category/index.php
new file mode 100644 (file)
index 0000000..99a9c5f
--- /dev/null
@@ -0,0 +1,55 @@
+<?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>
diff --git a/erp24/views/wiki-category/update.php b/erp24/views/wiki-category/update.php
new file mode 100644 (file)
index 0000000..6c09872
--- /dev/null
@@ -0,0 +1,21 @@
+<?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>
diff --git a/erp24/views/wiki-category/view.php b/erp24/views/wiki-category/view.php
new file mode 100644 (file)
index 0000000..2797e44
--- /dev/null
@@ -0,0 +1,51 @@
+<?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>
diff --git a/erp24/views/wiki/_form.php b/erp24/views/wiki/_form.php
new file mode 100644 (file)
index 0000000..15ecddf
--- /dev/null
@@ -0,0 +1,60 @@
+<?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>
diff --git a/erp24/views/wiki/_search.php b/erp24/views/wiki/_search.php
new file mode 100644 (file)
index 0000000..30a3fef
--- /dev/null
@@ -0,0 +1,45 @@
+<?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>
diff --git a/erp24/views/wiki/create.php b/erp24/views/wiki/create.php
new file mode 100644 (file)
index 0000000..1bfcff6
--- /dev/null
@@ -0,0 +1,20 @@
+<?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>
diff --git a/erp24/views/wiki/index.php b/erp24/views/wiki/index.php
new file mode 100644 (file)
index 0000000..65bc2d1
--- /dev/null
@@ -0,0 +1,193 @@
+<?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>
+
+
diff --git a/erp24/views/wiki/update.php b/erp24/views/wiki/update.php
new file mode 100644 (file)
index 0000000..8a40a7b
--- /dev/null
@@ -0,0 +1,21 @@
+<?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>
diff --git a/erp24/views/wiki/view.php b/erp24/views/wiki/view.php
new file mode 100644 (file)
index 0000000..844b23b
--- /dev/null
@@ -0,0 +1,71 @@
+<?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>